io.druid.common.config.ConfigManager.java Source code

Java tutorial

Introduction

Here is the source code for io.druid.common.config.ConfigManager.java

Source

/*
 * Druid - a distributed column store.
 * Copyright 2012 - 2015 Metamarkets Group Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.druid.common.config;

import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.metamx.common.concurrent.ScheduledExecutors;
import com.metamx.common.lifecycle.LifecycleStart;
import com.metamx.common.lifecycle.LifecycleStop;
import com.metamx.common.logger.Logger;
import io.druid.metadata.MetadataStorageConnector;
import io.druid.metadata.MetadataStorageTablesConfig;
import org.joda.time.Duration;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;

/**
 */
public class ConfigManager {
    private static final Logger log = new Logger(ConfigManager.class);

    private final Object lock = new Object();
    private boolean started = false;

    private final MetadataStorageConnector dbConnector;
    private final Supplier<ConfigManagerConfig> config;

    private final ScheduledExecutorService exec;
    private final ConcurrentMap<String, ConfigHolder> watchedConfigs;
    private final String configTable;

    private volatile ConfigManager.PollingCallable poller;

    @Inject
    public ConfigManager(MetadataStorageConnector dbConnector, Supplier<MetadataStorageTablesConfig> dbTables,
            Supplier<ConfigManagerConfig> config) {
        this.dbConnector = dbConnector;
        this.config = config;

        this.exec = ScheduledExecutors.fixed(1, "config-manager-%s");
        this.watchedConfigs = Maps.newConcurrentMap();

        this.configTable = dbTables.get().getConfigTable();
    }

    @LifecycleStart
    public void start() {
        synchronized (lock) {
            if (started) {
                return;
            }

            poller = new PollingCallable();
            ScheduledExecutors.scheduleWithFixedDelay(exec, new Duration(0),
                    config.get().getPollDuration().toStandardDuration(), poller);

            started = true;
        }
    }

    @LifecycleStop
    public void stop() {
        synchronized (lock) {
            if (!started) {
                return;
            }

            poller.stop();
            poller = null;

            started = false;
        }
    }

    private void poll() {
        for (Map.Entry<String, ConfigHolder> entry : watchedConfigs.entrySet()) {
            try {
                if (entry.getValue()
                        .swapIfNew(dbConnector.lookup(configTable, "name", "payload", entry.getKey()))) {
                    log.info("New value for key[%s] seen.", entry.getKey());
                }
            } catch (Exception e) {
                log.warn(e, "Exception when checking property[%s]", entry.getKey());
            }
        }
    }

    @SuppressWarnings("unchecked")
    public <T> AtomicReference<T> watchConfig(final String key, final ConfigSerde<T> serde) {
        ConfigHolder<T> holder = watchedConfigs.get(key);
        if (holder == null) {
            try {
                log.info("Creating watch for key[%s]", key);

                holder = exec.submit(new Callable<ConfigHolder<T>>() {
                    @Override
                    @SuppressWarnings("unchecked")
                    public ConfigHolder<T> call() throws Exception {
                        if (!started) {
                            watchedConfigs.put(key, new ConfigHolder<T>(null, serde));
                        } else {
                            try {
                                // Multiple of these callables can be submitted at the same time, but the callables themselves
                                // are executed serially, so double check that it hasn't already been populated.
                                if (!watchedConfigs.containsKey(key)) {
                                    byte[] value = dbConnector.lookup(configTable, "name", "payload", key);
                                    ConfigHolder<T> holder = new ConfigHolder<T>(value, serde);
                                    watchedConfigs.put(key, holder);
                                }
                            } catch (Exception e) {
                                log.warn(e, "Failed loading config for key[%s]", key);
                                watchedConfigs.put(key, new ConfigHolder<T>(null, serde));
                            }
                        }

                        return watchedConfigs.get(key);
                    }
                }).get();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw Throwables.propagate(e);
            } catch (ExecutionException e) {
                throw Throwables.propagate(e);
            }
        }

        return holder.getReference();
    }

    public <T> boolean set(final String key, final ConfigSerde<T> serde, final T obj) {
        if (obj == null || !started) {
            return false;
        }

        final byte[] newBytes = serde.serialize(obj);

        try {
            return exec.submit(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    dbConnector.insertOrUpdate(configTable, "name", "payload", key, newBytes);

                    final ConfigHolder configHolder = watchedConfigs.get(key);
                    if (configHolder != null) {
                        configHolder.swapIfNew(newBytes);
                    }

                    return true;
                }
            }).get();
        } catch (Exception e) {
            log.warn(e, "Failed to set[%s]", key);
            return false;
        }
    }

    private static class ConfigHolder<T> {
        private final AtomicReference<byte[]> rawBytes;
        private final ConfigSerde<T> serde;
        private final AtomicReference<T> reference;

        ConfigHolder(byte[] rawBytes, ConfigSerde<T> serde) {
            this.rawBytes = new AtomicReference<byte[]>(rawBytes);
            this.serde = serde;
            this.reference = new AtomicReference<T>(serde.deserialize(rawBytes));
        }

        public AtomicReference<T> getReference() {
            return reference;
        }

        public boolean swapIfNew(byte[] newBytes) {
            if (!Arrays.equals(newBytes, rawBytes.get())) {
                reference.set(serde.deserialize(newBytes));
                rawBytes.set(newBytes);
                return true;
            }
            return false;
        }
    }

    private class PollingCallable implements Callable<ScheduledExecutors.Signal> {
        private volatile boolean stop = false;

        void stop() {
            stop = true;
        }

        @Override
        public ScheduledExecutors.Signal call() throws Exception {
            if (stop) {
                return ScheduledExecutors.Signal.STOP;
            }

            poll();
            return ScheduledExecutors.Signal.REPEAT;
        }
    }
}