package com.netflix.fabricator.component; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.collect.MapDifference.ValueDifference; import com.netflix.fabricator.ComponentConfigurationResolver; import com.netflix.fabricator.ComponentType; import com.netflix.fabricator.ConfigurationNode; import com.netflix.fabricator.TypeConfigurationResolver; import com.netflix.fabricator.component.exception.ComponentAlreadyExistsException; import com.netflix.fabricator.component.exception.ComponentCreationException; import com.netflix.governator.annotations.Configuration; import com.netflix.governator.annotations.ConfigurationVariable; import com.netflix.governator.annotations.binding.Background; /** * Service responsible for refreshing a predef * @author elandau * */ public class BaseComponentRefreshService<T> { private static final Logger LOG = LoggerFactory.getLogger(BaseComponentRefreshService.class); public final long DEFAULT_REFRESH_RATE = 60; /** * The manager for these components. */ private final ComponentManager<T> manager; /** * Executor for the refresh task */ private final ScheduledExecutorService executor; /** * Prefix based on the component type makes it possible to configure this for * any component manager */ @ConfigurationVariable(name="prefix") private final String componentName; @Configuration(value="${prefix}.refresh.refreshRateInSeconds") private long refreshRate = DEFAULT_REFRESH_RATE; @Configuration(value="${prefix}.refresh.enabled") private boolean enabled = false; /** * Future for refresh task */ private ScheduledFuture<?> refreshFuture; /** * Resolver for the core configuration for these components */ private final ComponentConfigurationResolver configResolver; /** * List of 'known' configurations. We keep this outside of what's in the ComponentManager * so we can keep track of the raw configuration */ private Map<String, ConfigurationNode> configs = ImmutableMap.of(); @Inject public BaseComponentRefreshService( ComponentManager<T> manager, ComponentType<T> type, TypeConfigurationResolver config, @Background ScheduledExecutorService executor) { this.componentName = type.getType(); this.manager = manager; this.executor = executor; this.configResolver = config.getConfigurationFactory(type.getType()); } @PostConstruct public void init() { if (enabled) { LOG.info(String.format("Starting '%s' refresh task", componentName)); manager.apply(getUpdateTask()); this.refreshFuture = executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { manager.apply(getUpdateTask()); } }, refreshRate, refreshRate, TimeUnit.SECONDS); } else { LOG.info(String.format("'%s' refresh task diabled", componentName)); } } private Runnable getUpdateTask() { return new Runnable() { @Override public void run() { // Get a snapshot of the current configuration Map<String, ConfigurationNode> newConfigs = configResolver.getAllConfigurations(); MapDifference<String, ConfigurationNode> diff = Maps.difference(newConfigs, configs); // new configs for (Entry<String, ConfigurationNode> entry : diff.entriesOnlyOnLeft().entrySet()) { LOG.info("Adding config: " + entry.getKey() + " " + entry.getValue().toString()); try { manager.load(entry.getValue()); } catch (ComponentAlreadyExistsException e) { } catch (ComponentCreationException e) { LOG.warn("Failed to create component " + entry.getKey(), e); } } // removed configs for (Entry<String, ConfigurationNode> entry : diff.entriesOnlyOnRight().entrySet()) { LOG.info("Remove config: " + entry.getKey() + " " + entry.getValue().toString()); manager.remove(entry.getKey()); } // modified configs for (Entry<String, ValueDifference<ConfigurationNode>> entry : diff.entriesDiffering().entrySet()) { LOG.info("Replace config: " + entry.getKey() + " " + entry.getValue().toString()); try { manager.replace(entry.getValue().leftValue()); } catch (ComponentCreationException e) { LOG.warn("Failed to create component " + entry.getKey(), e); } } } }; } @PreDestroy public void shutdown() { if (refreshFuture != null) { refreshFuture.cancel(true); } } }