org.glowroot.agent.embedded.init.ConfigRepositoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.glowroot.agent.embedded.init.ConfigRepositoryImpl.java

Source

/*
 * Copyright 2011-2017 the original author or authors.
 *
 * 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 org.glowroot.agent.embedded.init;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import javax.annotation.Nullable;
import javax.crypto.SecretKey;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.glowroot.agent.config.AdvancedConfig;
import org.glowroot.agent.config.ConfigService;
import org.glowroot.agent.config.GaugeConfig;
import org.glowroot.agent.config.InstrumentationConfig;
import org.glowroot.agent.config.PluginCache;
import org.glowroot.agent.config.PluginConfig;
import org.glowroot.agent.config.PluginDescriptor;
import org.glowroot.agent.config.TransactionConfig;
import org.glowroot.agent.config.UiConfig;
import org.glowroot.agent.config.UserRecordingConfig;
import org.glowroot.common.config.AgentRollupConfig;
import org.glowroot.common.config.CentralStorageConfig;
import org.glowroot.common.config.FatStorageConfig;
import org.glowroot.common.config.ImmutableFatStorageConfig;
import org.glowroot.common.config.ImmutableLdapConfig;
import org.glowroot.common.config.ImmutableRoleConfig;
import org.glowroot.common.config.ImmutableUserConfig;
import org.glowroot.common.config.ImmutableWebConfig;
import org.glowroot.common.config.LdapConfig;
import org.glowroot.common.config.RoleConfig;
import org.glowroot.common.config.SmtpConfig;
import org.glowroot.common.config.StorageConfig;
import org.glowroot.common.config.UserConfig;
import org.glowroot.common.config.WebConfig;
import org.glowroot.common.repo.ConfigRepository;
import org.glowroot.common.repo.util.LazySecretKey;
import org.glowroot.common.util.OnlyUsedByTests;
import org.glowroot.common.util.Versions;
import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig;
import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.PluginProperty;

import static com.google.common.base.Preconditions.checkState;

class ConfigRepositoryImpl implements ConfigRepository {

    private static final Logger logger = LoggerFactory.getLogger(ConfigRepositoryImpl.class);

    private final ConfigService configService;
    private final PluginCache pluginCache;

    private final ImmutableList<RollupConfig> rollupConfigs;

    private final LazySecretKey secretKey;

    private final Object writeLock = new Object();

    private volatile ImmutableList<UserConfig> userConfigs;
    private volatile ImmutableList<RoleConfig> roleConfigs;
    private volatile WebConfig webConfig;
    private volatile FatStorageConfig storageConfig;
    private volatile LdapConfig ldapConfig;

    static ConfigRepository create(File glowrootDir, ConfigService configService, PluginCache pluginCache) {
        ConfigRepositoryImpl configRepository = new ConfigRepositoryImpl(glowrootDir, configService, pluginCache);
        // it's nice to update admin.json on startup if it is missing some/all config
        // properties so that the file contents can be reviewed/updated/copied if desired
        try {
            configRepository.writeAll();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return configRepository;
    }

    private ConfigRepositoryImpl(File glowrootDir, ConfigService configService, PluginCache pluginCache) {
        this.configService = configService;
        this.pluginCache = pluginCache;
        rollupConfigs = ImmutableList.copyOf(RollupConfig.buildRollupConfigs());
        secretKey = new LazySecretKey(new File(glowrootDir, "secret"));

        List<ImmutableUserConfig> userConfigs = configService.getAdminConfig(USERS_KEY,
                new TypeReference<List<ImmutableUserConfig>>() {
                });
        if (userConfigs == null) {
            this.userConfigs = ImmutableList.of();
        } else {
            this.userConfigs = ImmutableList.<UserConfig>copyOf(userConfigs);
        }
        if (this.userConfigs.isEmpty()) {
            this.userConfigs = ImmutableList.<UserConfig>of(
                    ImmutableUserConfig.builder().username("anonymous").addRoles("Administrator").build());
        }
        List<ImmutableRoleConfig> roleConfigs = configService.getAdminConfig(ROLES_KEY,
                new TypeReference<List<ImmutableRoleConfig>>() {
                });
        if (roleConfigs == null) {
            this.roleConfigs = ImmutableList.of();
        } else {
            this.roleConfigs = ImmutableList.<RoleConfig>copyOf(roleConfigs);
        }
        if (this.roleConfigs.isEmpty()) {
            this.roleConfigs = ImmutableList.<RoleConfig>of(ImmutableRoleConfig.builder().name("Administrator")
                    .addPermissions("agent:transaction", "agent:error", "agent:jvm", "agent:config", "admin")
                    .build());
        }
        WebConfig webConfig = configService.getAdminConfig(WEB_KEY, ImmutableWebConfig.class);
        if (webConfig == null) {
            this.webConfig = ImmutableWebConfig.builder().build();
        } else {
            this.webConfig = webConfig;
        }
        FatStorageConfig storageConfig = configService.getAdminConfig(STORAGE_KEY, ImmutableFatStorageConfig.class);
        if (storageConfig == null) {
            this.storageConfig = ImmutableFatStorageConfig.builder().build();
        } else if (storageConfig.hasListIssues()) {
            this.storageConfig = withCorrectedLists(storageConfig);
        } else {
            this.storageConfig = storageConfig;
        }
        LdapConfig ldapConfig = configService.getAdminConfig(LDAP_KEY, ImmutableLdapConfig.class);
        if (ldapConfig == null) {
            this.ldapConfig = ImmutableLdapConfig.builder().build();
        } else {
            this.ldapConfig = ldapConfig;
        }
    }

    @Override
    public AgentConfig.TransactionConfig getTransactionConfig(String agentId) {
        return configService.getTransactionConfig().toProto();
    }

    @Override
    public AgentConfig.UiConfig getUiConfig(String agentId) {
        return configService.getUiConfig().toProto();
    }

    @Override
    public AgentConfig.UserRecordingConfig getUserRecordingConfig(String agentId) {
        return configService.getUserRecordingConfig().toProto();
    }

    @Override
    public AgentConfig.AdvancedConfig getAdvancedConfig(String agentId) {
        return configService.getAdvancedConfig().toProto();
    }

    @Override
    public List<AgentConfig.GaugeConfig> getGaugeConfigs(String agentId) {
        List<AgentConfig.GaugeConfig> gaugeConfigs = Lists.newArrayList();
        for (GaugeConfig gaugeConfig : configService.getGaugeConfigs()) {
            gaugeConfigs.add(gaugeConfig.toProto());
        }
        return gaugeConfigs;
    }

    @Override
    public @Nullable AgentConfig.GaugeConfig getGaugeConfig(String agentId, String version) {
        for (GaugeConfig gaugeConfig : configService.getGaugeConfigs()) {
            AgentConfig.GaugeConfig config = gaugeConfig.toProto();
            if (Versions.getVersion(config).equals(version)) {
                return config;
            }
        }
        return null;
    }

    @Override
    public List<AgentConfig.SyntheticMonitorConfig> getSyntheticMonitorConfigs(String agentRollupId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public @Nullable AgentConfig.SyntheticMonitorConfig getSyntheticMonitorConfig(String agentRollupId,
            String syntheticMonitorId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<AgentConfig.AlertConfig> getAlertConfigs(String agentRollupId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public @Nullable AgentConfig.AlertConfig getAlertConfig(String agentRollupId, String alertId) {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<AgentConfig.PluginConfig> getPluginConfigs(String agentId) {
        List<AgentConfig.PluginConfig> configs = Lists.newArrayList();
        for (PluginConfig config : configService.getPluginConfigs()) {
            configs.add(config.toProto());
        }
        return configs;
    }

    @Override
    public @Nullable AgentConfig.PluginConfig getPluginConfig(String agentId, String pluginId) {
        PluginConfig pluginConfig = configService.getPluginConfig(pluginId);
        if (pluginConfig == null) {
            return null;
        }
        return pluginConfig.toProto();
    }

    @Override
    public List<AgentConfig.InstrumentationConfig> getInstrumentationConfigs(String agentId) {
        List<AgentConfig.InstrumentationConfig> configs = Lists.newArrayList();
        for (InstrumentationConfig config : configService.getInstrumentationConfigs()) {
            configs.add(config.toProto());
        }
        return configs;
    }

    @Override
    public @Nullable AgentConfig.InstrumentationConfig getInstrumentationConfig(String agentId, String version) {
        for (InstrumentationConfig config : configService.getInstrumentationConfigs()) {
            AgentConfig.InstrumentationConfig protoConfig = config.toProto();
            if (Versions.getVersion(protoConfig).equals(version)) {
                return protoConfig;
            }
        }
        return null;
    }

    @Override
    public AgentRollupConfig getAgentRollupConfig(String agentRollup) {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<UserConfig> getUserConfigs() {
        return userConfigs;
    }

    @Override
    public @Nullable UserConfig getUserConfig(String username) {
        for (UserConfig config : userConfigs) {
            if (config.username().equals(username)) {
                return config;
            }
        }
        return null;
    }

    @Override
    public @Nullable UserConfig getUserConfigCaseInsensitive(String username) {
        for (UserConfig config : userConfigs) {
            if (config.username().equalsIgnoreCase(username)) {
                return config;
            }
        }
        return null;
    }

    @Override
    public boolean namedUsersExist() {
        for (UserConfig config : userConfigs) {
            if (!config.username().equalsIgnoreCase("anonymous")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public List<RoleConfig> getRoleConfigs() {
        return roleConfigs;
    }

    @Override
    public @Nullable RoleConfig getRoleConfig(String name) {
        for (RoleConfig config : roleConfigs) {
            if (config.name().equals(name)) {
                return config;
            }
        }
        return null;
    }

    @Override
    public WebConfig getWebConfig() {
        return webConfig;
    }

    @Override
    public FatStorageConfig getFatStorageConfig() {
        return storageConfig;
    }

    @Override
    public CentralStorageConfig getCentralStorageConfig() {
        throw new UnsupportedOperationException();
    }

    @Override
    public SmtpConfig getSmtpConfig() {
        throw new UnsupportedOperationException();
    }

    @Override
    public LdapConfig getLdapConfig() {
        return ldapConfig;
    }

    @Override
    public void updateTransactionConfig(String agentId, AgentConfig.TransactionConfig protoConfig,
            String priorVersion) throws Exception {
        TransactionConfig config = TransactionConfig.create(protoConfig);
        synchronized (writeLock) {
            String currVersion = Versions.getVersion(configService.getTransactionConfig().toProto());
            checkVersionsEqual(currVersion, priorVersion);
            configService.updateTransactionConfig(config);
        }
    }

    @Override
    public void insertGaugeConfig(String agentId, AgentConfig.GaugeConfig protoConfig) throws Exception {
        GaugeConfig config = GaugeConfig.create(protoConfig);
        synchronized (writeLock) {
            List<GaugeConfig> configs = Lists.newArrayList(configService.getGaugeConfigs());
            // check for duplicate mbeanObjectName
            for (GaugeConfig loopConfig : configs) {
                if (loopConfig.mbeanObjectName().equals(protoConfig.getMbeanObjectName())) {
                    throw new DuplicateMBeanObjectNameException();
                }
            }
            // no need to check for exact match since redundant with dup mbean object name check
            configs.add(config);
            configService.updateGaugeConfigs(configs);
        }
    }

    @Override
    public void updateGaugeConfig(String agentId, AgentConfig.GaugeConfig protoConfig, String priorVersion)
            throws Exception {
        GaugeConfig config = GaugeConfig.create(protoConfig);
        synchronized (writeLock) {
            List<GaugeConfig> configs = Lists.newArrayList(configService.getGaugeConfigs());
            boolean found = false;
            for (ListIterator<GaugeConfig> i = configs.listIterator(); i.hasNext();) {
                GaugeConfig loopConfig = i.next();
                String loopVersion = Versions.getVersion(loopConfig.toProto());
                if (loopVersion.equals(priorVersion)) {
                    i.set(config);
                    found = true;
                } else if (loopConfig.mbeanObjectName().equals(protoConfig.getMbeanObjectName())) {
                    throw new DuplicateMBeanObjectNameException();
                }
                // no need to check for exact match since redundant with dup mbean object name check
            }
            if (!found) {
                throw new OptimisticLockException();
            }
            configService.updateGaugeConfigs(configs);
        }
    }

    @Override
    public void deleteGaugeConfig(String agentId, String version) throws Exception {
        synchronized (writeLock) {
            List<GaugeConfig> configs = Lists.newArrayList(configService.getGaugeConfigs());
            boolean found = false;
            for (ListIterator<GaugeConfig> i = configs.listIterator(); i.hasNext();) {
                GaugeConfig loopConfig = i.next();
                String loopVersion = Versions.getVersion(loopConfig.toProto());
                if (loopVersion.equals(version)) {
                    i.remove();
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new OptimisticLockException();
            }
            configService.updateGaugeConfigs(configs);
        }
    }

    @Override
    public String insertSyntheticMonitorConfig(String agentRollupId,
            AgentConfig.SyntheticMonitorConfig configWithoutId) throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    public void updateSyntheticMonitorConfig(String agentRollupId, AgentConfig.SyntheticMonitorConfig config,
            String priorVersion) throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    public void deleteSyntheticMonitorConfig(String agentRollupId, String syntheticMonitorId) throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    public String insertAlertConfig(String agentRollupId, AgentConfig.AlertConfig configWithoutId)
            throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    public void updateAlertConfig(String agentRollupId, AgentConfig.AlertConfig config, String priorVersion)
            throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    public void deleteAlertConfig(String agentRollupId, String alertId) throws Exception {
        throw new UnsupportedOperationException();
    }

    @Override
    public void updateUiConfig(String agentId, AgentConfig.UiConfig protoConfig, String priorVersion)
            throws Exception {
        UiConfig config = UiConfig.create(protoConfig);
        synchronized (writeLock) {
            String currVersion = Versions.getVersion(configService.getUiConfig().toProto());
            checkVersionsEqual(currVersion, priorVersion);
            configService.updateUiConfig(config);
        }
    }

    @Override
    public void updatePluginConfig(String agentId, String pluginId, List<PluginProperty> properties,
            String priorVersion) throws Exception {
        PluginDescriptor pluginDescriptor = getPluginDescriptor(pluginId);
        PluginConfig config = PluginConfig.create(pluginDescriptor, properties);
        synchronized (writeLock) {
            List<PluginConfig> configs = Lists.newArrayList(configService.getPluginConfigs());
            boolean found = false;
            for (ListIterator<PluginConfig> i = configs.listIterator(); i.hasNext();) {
                PluginConfig loopPluginConfig = i.next();
                if (pluginId.equals(loopPluginConfig.id())) {
                    String loopVersion = Versions.getVersion(loopPluginConfig.toProto());
                    checkVersionsEqual(loopVersion, priorVersion);
                    i.set(config);
                    found = true;
                    break;
                }
            }
            checkState(found, "Plugin config not found: %s", pluginId);
            configService.updatePluginConfigs(configs);
        }
    }

    @Override
    public void insertInstrumentationConfig(String agentId, AgentConfig.InstrumentationConfig protoConfig)
            throws Exception {
        InstrumentationConfig config = InstrumentationConfig.create(protoConfig);
        synchronized (writeLock) {
            List<InstrumentationConfig> configs = Lists.newArrayList(configService.getInstrumentationConfigs());
            checkInstrumentationDoesNotExist(config, configs);
            configs.add(config);
            configService.updateInstrumentationConfigs(configs);
        }
    }

    @Override
    public void updateInstrumentationConfig(String agentId, AgentConfig.InstrumentationConfig protoConfig,
            String priorVersion) throws Exception {
        InstrumentationConfig config = InstrumentationConfig.create(protoConfig);
        synchronized (writeLock) {
            List<InstrumentationConfig> configs = Lists.newArrayList(configService.getInstrumentationConfigs());
            boolean found = false;
            for (ListIterator<InstrumentationConfig> i = configs.listIterator(); i.hasNext();) {
                InstrumentationConfig loopConfig = i.next();
                String loopVersion = Versions.getVersion(loopConfig.toProto());
                if (loopVersion.equals(priorVersion)) {
                    i.set(config);
                    found = true;
                } else if (loopConfig.equals(config)) {
                    throw new IllegalStateException("This exact instrumentation already exists");
                }
            }
            if (!found) {
                throw new OptimisticLockException();
            }
            configService.updateInstrumentationConfigs(configs);
        }
    }

    @Override
    public void deleteInstrumentationConfigs(String agentId, List<String> versions) throws Exception {
        synchronized (writeLock) {
            List<InstrumentationConfig> configs = Lists.newArrayList(configService.getInstrumentationConfigs());
            List<String> remainingVersions = Lists.newArrayList(versions);
            for (ListIterator<InstrumentationConfig> i = configs.listIterator(); i.hasNext();) {
                InstrumentationConfig loopConfig = i.next();
                String loopVersion = Versions.getVersion(loopConfig.toProto());
                if (remainingVersions.contains(loopVersion)) {
                    i.remove();
                    remainingVersions.remove(loopVersion);
                }
            }
            if (!remainingVersions.isEmpty()) {
                throw new OptimisticLockException();
            }
            configService.updateInstrumentationConfigs(configs);
        }
    }

    @Override
    public void insertInstrumentationConfigs(String agentId, List<AgentConfig.InstrumentationConfig> protoConfigs)
            throws Exception {
        List<InstrumentationConfig> configs = Lists.newArrayList();
        for (AgentConfig.InstrumentationConfig instrumentationConfig : protoConfigs) {
            InstrumentationConfig config = InstrumentationConfig.create(instrumentationConfig);
            configs.add(config);
        }
        synchronized (writeLock) {
            List<InstrumentationConfig> existingConfigs = Lists
                    .newArrayList(configService.getInstrumentationConfigs());
            for (InstrumentationConfig config : configs) {
                checkInstrumentationDoesNotExist(config, existingConfigs);
                existingConfigs.add(config);
            }
            configService.updateInstrumentationConfigs(existingConfigs);
        }
    }

    @Override
    public void updateUserRecordingConfig(String agentId, AgentConfig.UserRecordingConfig protoConfig,
            String priorVersion) throws Exception {
        UserRecordingConfig config = UserRecordingConfig.create(protoConfig);
        synchronized (writeLock) {
            String currVersion = Versions.getVersion(configService.getUserRecordingConfig().toProto());
            checkVersionsEqual(currVersion, priorVersion);
            configService.updateUserRecordingConfig(config);
        }
    }

    @Override
    public void updateAdvancedConfig(String agentId, AgentConfig.AdvancedConfig protoConfig, String priorVersion)
            throws Exception {
        AdvancedConfig config = AdvancedConfig.create(protoConfig);
        synchronized (writeLock) {
            String currVersion = Versions.getVersion(configService.getAdvancedConfig().toProto());
            checkVersionsEqual(currVersion, priorVersion);
            configService.updateAdvancedConfig(config);
        }
    }

    @Override
    public void updateAgentRollupConfig(AgentRollupConfig agentRollupConfig, String priorVersion) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void deleteAgentRollupConfig(String agentRollup) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void insertUserConfig(UserConfig config) throws Exception {
        synchronized (writeLock) {
            List<UserConfig> configs = Lists.newArrayList(userConfigs);
            // check for case-insensitive duplicate
            String username = config.username();
            for (UserConfig loopConfig : configs) {
                if (loopConfig.username().equalsIgnoreCase(username)) {
                    throw new DuplicateUsernameException();
                }
            }
            configs.add(config);
            configService.updateAdminConfig(USERS_KEY, configs);
            userConfigs = ImmutableList.copyOf(configs);
        }
    }

    @Override
    public void updateUserConfig(UserConfig config, String priorVersion) throws Exception {
        synchronized (writeLock) {
            List<UserConfig> configs = Lists.newArrayList(userConfigs);
            String username = config.username();
            boolean found = false;
            for (ListIterator<UserConfig> i = configs.listIterator(); i.hasNext();) {
                UserConfig loopConfig = i.next();
                if (loopConfig.username().equals(username)) {
                    if (loopConfig.version().equals(priorVersion)) {
                        i.set(config);
                        found = true;
                        break;
                    } else {
                        throw new OptimisticLockException();
                    }
                }
            }
            if (!found) {
                throw new UserNotFoundException();
            }
            configService.updateAdminConfig(USERS_KEY, configs);
            userConfigs = ImmutableList.copyOf(configs);
        }
    }

    @Override
    public void deleteUserConfig(String username) throws Exception {
        synchronized (writeLock) {
            List<UserConfig> configs = Lists.newArrayList(userConfigs);
            boolean found = false;
            for (ListIterator<UserConfig> i = configs.listIterator(); i.hasNext();) {
                UserConfig loopConfig = i.next();
                if (loopConfig.username().equals(username)) {
                    i.remove();
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new UserNotFoundException();
            }
            if (getLdapConfig().host().isEmpty() && configs.isEmpty()) {
                throw new CannotDeleteLastUserException();
            }
            configService.updateAdminConfig(USERS_KEY, configs);
            userConfigs = ImmutableList.copyOf(configs);
        }
    }

    @Override
    public void insertRoleConfig(RoleConfig config) throws Exception {
        synchronized (writeLock) {
            List<RoleConfig> configs = Lists.newArrayList(roleConfigs);
            // check for case-insensitive duplicate
            String name = config.name();
            for (RoleConfig loopConfig : configs) {
                if (loopConfig.name().equalsIgnoreCase(name)) {
                    throw new DuplicateRoleNameException();
                }
            }
            configs.add(config);
            configService.updateAdminConfig(ROLES_KEY, configs);
            roleConfigs = ImmutableList.copyOf(configs);
        }
    }

    @Override
    public void updateRoleConfig(RoleConfig config, String priorVersion) throws Exception {
        synchronized (writeLock) {
            List<RoleConfig> configs = Lists.newArrayList(roleConfigs);
            String name = config.name();
            boolean found = false;
            for (ListIterator<RoleConfig> i = configs.listIterator(); i.hasNext();) {
                RoleConfig loopConfig = i.next();
                if (loopConfig.name().equals(name)) {
                    if (loopConfig.version().equals(priorVersion)) {
                        i.set(config);
                        found = true;
                        break;
                    } else {
                        throw new OptimisticLockException();
                    }
                }
            }
            if (!found) {
                throw new RoleNotFoundException();
            }
            configService.updateAdminConfig(ROLES_KEY, configs);
            roleConfigs = ImmutableList.copyOf(configs);
        }
    }

    @Override
    public void deleteRoleConfig(String name) throws Exception {
        synchronized (writeLock) {
            List<RoleConfig> configs = Lists.newArrayList(roleConfigs);
            boolean found = false;
            for (ListIterator<RoleConfig> i = configs.listIterator(); i.hasNext();) {
                RoleConfig loopConfig = i.next();
                if (loopConfig.name().equals(name)) {
                    i.remove();
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new RoleNotFoundException();
            }
            if (configs.isEmpty()) {
                throw new CannotDeleteLastRoleException();
            }
            configService.updateAdminConfig(ROLES_KEY, configs);
            roleConfigs = ImmutableList.copyOf(configs);
        }
    }

    @Override
    public void updateWebConfig(WebConfig config, String priorVersion) throws Exception {
        synchronized (writeLock) {
            checkVersionsEqual(webConfig.version(), priorVersion);
            configService.updateAdminConfig(WEB_KEY, config);
            webConfig = config;
        }
    }

    @Override
    public void updateFatStorageConfig(FatStorageConfig config, String priorVersion) throws Exception {
        synchronized (writeLock) {
            checkVersionsEqual(storageConfig.version(), priorVersion);
            configService.updateAdminConfig(STORAGE_KEY, config);
            storageConfig = config;
        }
    }

    @Override
    public void updateCentralStorageConfig(CentralStorageConfig config, String priorVersion) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void updateSmtpConfig(SmtpConfig config, String priorVersion) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void updateLdapConfig(LdapConfig config, String priorVersion) throws Exception {
        synchronized (writeLock) {
            checkVersionsEqual(ldapConfig.version(), priorVersion);
            configService.updateAdminConfig(LDAP_KEY, config);
            ldapConfig = config;
        }
    }

    @Override
    public StorageConfig getStorageConfig() {
        return getFatStorageConfig();
    }

    @Override
    public long getGaugeCollectionIntervalMillis() {
        return configService.getGaugeCollectionIntervalMillis();
    }

    @Override
    public ImmutableList<RollupConfig> getRollupConfigs() {
        return rollupConfigs;
    }

    // lazy create secret file only when needed
    @Override
    public SecretKey getSecretKey() throws Exception {
        return secretKey.get();
    }

    private PluginDescriptor getPluginDescriptor(String pluginId) {
        for (PluginDescriptor pluginDescriptor : pluginCache.pluginDescriptors()) {
            if (pluginDescriptor.id().equals(pluginId)) {
                return pluginDescriptor;
            }
        }
        throw new IllegalStateException("Could not find plugin descriptor: " + pluginId);
    }

    private void checkVersionsEqual(String version, String priorVersion) throws OptimisticLockException {
        if (!version.equals(priorVersion)) {
            throw new OptimisticLockException();
        }
    }

    @OnlyUsedByTests
    public void resetAdminConfig() throws IOException {
        userConfigs = ImmutableList.<UserConfig>of(
                ImmutableUserConfig.builder().username("anonymous").addRoles("Administrator").build());
        roleConfigs = ImmutableList.<RoleConfig>of(
                ImmutableRoleConfig.builder().name("Administrator").addPermissions("agent:transaction",
                        "agent:error", "agent:jvm", "agent:config:view", "agent:config:edit", "admin").build());
        webConfig = ImmutableWebConfig.builder().build();
        storageConfig = ImmutableFatStorageConfig.builder().build();
        ldapConfig = ImmutableLdapConfig.builder().build();
        writeAll();
    }

    private void writeAll() throws IOException {
        // linked hash map to preserve ordering when writing to config file
        Map<String, Object> configs = Maps.newLinkedHashMap();
        configs.put(USERS_KEY, userConfigs);
        configs.put(ROLES_KEY, roleConfigs);
        configs.put(WEB_KEY, webConfig);
        configs.put(STORAGE_KEY, storageConfig);
        configs.put(LDAP_KEY, ldapConfig);
        configService.updateAdminConfigs(configs);
    }

    private static void checkInstrumentationDoesNotExist(InstrumentationConfig instrumentationConfig,
            List<InstrumentationConfig> instrumentationConfigs) {
        for (InstrumentationConfig loopInstrumentationConfig : instrumentationConfigs) {
            if (loopInstrumentationConfig.equals(instrumentationConfig)) {
                throw new IllegalStateException("This exact instrumentation already exists");
            }
        }
    }

    private static FatStorageConfig withCorrectedLists(FatStorageConfig config) {
        FatStorageConfig defaultConfig = ImmutableFatStorageConfig.builder().build();
        ImmutableList<Integer> rollupExpirationHours = fix(config.rollupExpirationHours(),
                defaultConfig.rollupExpirationHours());
        ImmutableList<Integer> rollupCappedDatabaseSizesMb = fix(config.rollupCappedDatabaseSizesMb(),
                defaultConfig.rollupCappedDatabaseSizesMb());
        return ImmutableFatStorageConfig.builder().copyFrom(config).rollupExpirationHours(rollupExpirationHours)
                .rollupCappedDatabaseSizesMb(rollupCappedDatabaseSizesMb).build();
    }

    private static ImmutableList<Integer> fix(ImmutableList<Integer> thisList, List<Integer> defaultList) {
        if (thisList.size() >= defaultList.size()) {
            return thisList.subList(0, defaultList.size());
        }
        List<Integer> correctedList = Lists.newArrayList(thisList);
        for (int i = thisList.size(); i < defaultList.size(); i++) {
            correctedList.add(defaultList.get(i));
        }
        return ImmutableList.copyOf(correctedList);
    }
}