/* * Copyright 2015-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.central.repo; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.io.BaseEncoding; import org.glowroot.central.repo.ConfigDao.AgentConfigUpdater; import org.glowroot.common.config.AgentRollupConfig; import org.glowroot.common.config.CentralStorageConfig; import org.glowroot.common.config.CentralWebConfig; import org.glowroot.common.config.EmbeddedStorageConfig; import org.glowroot.common.config.EmbeddedWebConfig; import org.glowroot.common.config.ImmutableCentralStorageConfig; import org.glowroot.common.config.ImmutableCentralWebConfig; import org.glowroot.common.config.ImmutableEmbeddedStorageConfig; import org.glowroot.common.config.ImmutableLdapConfig; import org.glowroot.common.config.ImmutableSmtpConfig; 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.Versions; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AdvancedConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AlertConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AlertConfig.AlertKind; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.GaugeConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.InstrumentationConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.PluginConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.PluginProperty; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.PluginProperty.Value.ValCase; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.SyntheticMonitorConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.TransactionConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.UiConfig; import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.UserRecordingConfig; import static com.google.common.base.Preconditions.checkState; public class ConfigRepositoryImpl implements ConfigRepository { // TODO this needs to be in sync with agents, so have agents pick up value from central private static final long GAUGE_COLLECTION_INTERVAL_MILLIS = Long.getLong("glowroot.internal.gaugeCollectionIntervalMillis", 5000); private final AgentDao agentDao; private final ConfigDao configDao; private final CentralConfigDao centralConfigDao; private final UserDao userDao; private final RoleDao roleDao; private final ImmutableList<RollupConfig> rollupConfigs; private final LazySecretKey lazySecretKey; private final Set<AgentConfigListener> agentConfigListeners = Sets.newCopyOnWriteArraySet(); public ConfigRepositoryImpl(AgentDao agentDao, ConfigDao configDao, CentralConfigDao centralConfigDao, UserDao userDao, RoleDao roleDao, String symmetricEncryptionKey) { this.agentDao = agentDao; this.configDao = configDao; this.centralConfigDao = centralConfigDao; this.userDao = userDao; this.roleDao = roleDao; rollupConfigs = ImmutableList.copyOf(RollupConfig.buildRollupConfigs()); lazySecretKey = new LazySecretKeyImpl(symmetricEncryptionKey); centralConfigDao.addKeyType(WEB_KEY, ImmutableCentralWebConfig.class); centralConfigDao.addKeyType(STORAGE_KEY, ImmutableCentralStorageConfig.class); centralConfigDao.addKeyType(SMTP_KEY, ImmutableSmtpConfig.class); centralConfigDao.addKeyType(LDAP_KEY, ImmutableLdapConfig.class); } @Override public TransactionConfig getTransactionConfig(String agentId) throws Exception { AgentConfig agentConfig = configDao.read(agentId); if (agentConfig == null) { // for some reason received data from agent, but not initial agent config throw new AgentConfigNotFoundException(agentId); } return agentConfig.getTransactionConfig(); } // central supports ui config on rollups @Override public UiConfig getUiConfig(String agentRollupId) throws Exception { AgentConfig agentConfig = configDao.read(agentRollupId); if (agentConfig == null) { // for some reason received data from agent, but not initial agent config throw new AgentConfigNotFoundException(agentRollupId); } return agentConfig.getUiConfig(); } @Override public UserRecordingConfig getUserRecordingConfig(String agentId) throws Exception { AgentConfig agentConfig = configDao.read(agentId); if (agentConfig == null) { // for some reason received data from agent, but not initial agent config throw new AgentConfigNotFoundException(agentId); } return agentConfig.getUserRecordingConfig(); } // central supports advanced config on rollups // (maxAggregateQueriesPerType and maxAggregateServiceCallsPerType) @Override public AdvancedConfig getAdvancedConfig(String agentRollupId) throws Exception { AgentConfig agentConfig = configDao.read(agentRollupId); if (agentConfig == null) { // for some reason received data from agent, but not initial agent config throw new AgentConfigNotFoundException(agentRollupId); } return agentConfig.getAdvancedConfig(); } @Override public List<GaugeConfig> getGaugeConfigs(String agentId) throws Exception { AgentConfig agentConfig = configDao.read(agentId); if (agentConfig == null) { return ImmutableList.of(); } return agentConfig.getGaugeConfigList(); } @Override public GaugeConfig getGaugeConfig(String agentId, String configVersion) throws Exception { for (GaugeConfig config : getGaugeConfigs(agentId)) { if (Versions.getVersion(config).equals(configVersion)) { return config; } } throw new IllegalStateException("Gauge config not found: " + configVersion); } // central supports synthetic monitor configs on rollups @Override public List<SyntheticMonitorConfig> getSyntheticMonitorConfigs(String agentRollupId) throws Exception { AgentConfig agentConfig = configDao.read(agentRollupId); if (agentConfig == null) { return ImmutableList.of(); } return agentConfig.getSyntheticMonitorConfigList(); } // central supports synthetic monitor configs on rollups @Override public @Nullable SyntheticMonitorConfig getSyntheticMonitorConfig(String agentRollupId, String syntheticMonitorId) throws Exception { for (SyntheticMonitorConfig config : getSyntheticMonitorConfigs(agentRollupId)) { if (config.getId().equals(syntheticMonitorId)) { return config; } } return null; } // central supports alert configs on rollups @Override public List<AlertConfig> getAlertConfigs(String agentRollupId) throws Exception { AgentConfig agentConfig = configDao.read(agentRollupId); if (agentConfig == null) { return ImmutableList.of(); } return agentConfig.getAlertConfigList(); } // central supports alert configs on rollups @Override public List<AlertConfig> getAlertConfigs(String agentRollupId, AlertKind alertKind) throws Exception { List<AlertConfig> configs = Lists.newArrayList(); for (AlertConfig config : getAlertConfigs(agentRollupId)) { if (config.getKind() == alertKind) { configs.add(config); } } return configs; } // central supports alert configs on rollups public List<AlertConfig> getAlertConfigsForSyntheticMonitorId(String agentRollupId, String syntheticMonitorId) throws Exception { List<AlertConfig> configs = Lists.newArrayList(); for (AlertConfig config : getAlertConfigs(agentRollupId)) { if (config.getKind() == AlertKind.SYNTHETIC_MONITOR && config.getSyntheticMonitorId().equals(syntheticMonitorId)) { configs.add(config); } } return configs; } // central supports alert configs on rollups @Override public @Nullable AlertConfig getAlertConfig(String agentRollupId, String configVersion) throws Exception { for (AlertConfig config : getAlertConfigs(agentRollupId)) { if (Versions.getVersion(config).equals(configVersion)) { return config; } } throw new IllegalStateException("Alert config not found: " + configVersion); } @Override public List<PluginConfig> getPluginConfigs(String agentId) throws Exception { AgentConfig agentConfig = configDao.read(agentId); if (agentConfig == null) { return ImmutableList.of(); } return agentConfig.getPluginConfigList(); } @Override public PluginConfig getPluginConfig(String agentId, String pluginId) throws Exception { for (PluginConfig config : getPluginConfigs(agentId)) { if (config.getId().equals(pluginId)) { return config; } } throw new IllegalStateException("Plugin config not found: " + pluginId); } @Override public List<InstrumentationConfig> getInstrumentationConfigs(String agentId) throws Exception { AgentConfig agentConfig = configDao.read(agentId); if (agentConfig == null) { return ImmutableList.of(); } return agentConfig.getInstrumentationConfigList(); } @Override public InstrumentationConfig getInstrumentationConfig(String agentId, String configVersion) throws Exception { for (InstrumentationConfig config : getInstrumentationConfigs(agentId)) { if (Versions.getVersion(config).equals(configVersion)) { return config; } } throw new IllegalStateException("Instrumentation config not found: " + configVersion); } @Override public @Nullable AgentRollupConfig getAgentRollupConfig(String agentRollupId) throws Exception { return agentDao.readAgentRollupConfig(agentRollupId); } @Override public List<UserConfig> getUserConfigs() throws Exception { return userDao.read(); } @Override public UserConfig getUserConfig(String username) throws Exception { UserConfig config = userDao.read(username); if (config == null) { throw new UserNotFoundException(); } return config; } @Override public @Nullable UserConfig getUserConfigCaseInsensitive(String username) throws Exception { return userDao.readCaseInsensitive(username); } @Override public boolean namedUsersExist() throws Exception { return userDao.namedUsersExist(); } @Override public List<RoleConfig> getRoleConfigs() throws Exception { return roleDao.read(); } @Override public @Nullable RoleConfig getRoleConfig(String name) throws Exception { return roleDao.read(name); } @Override public WebConfig getWebConfig() throws Exception { return getCentralWebConfig(); } @Override public EmbeddedWebConfig getEmbeddedWebConfig() throws Exception { throw new UnsupportedOperationException(); } @Override public CentralWebConfig getCentralWebConfig() throws Exception { CentralWebConfig config = (CentralWebConfig) centralConfigDao.read(WEB_KEY); if (config == null) { return ImmutableCentralWebConfig.builder().build(); } return config; } @Override public StorageConfig getStorageConfig() throws Exception { return getCentralStorageConfig(); } @Override public EmbeddedStorageConfig getEmbeddedStorageConfig() { throw new UnsupportedOperationException(); } @Override public CentralStorageConfig getCentralStorageConfig() throws Exception { CentralStorageConfig config = (CentralStorageConfig) centralConfigDao.read(STORAGE_KEY); if (config == null) { return ImmutableCentralStorageConfig.builder().build(); } if (config.hasListIssues()) { return withCorrectedLists(config); } return config; } @Override public SmtpConfig getSmtpConfig() throws Exception { SmtpConfig config = (SmtpConfig) centralConfigDao.read(SMTP_KEY); if (config == null) { return ImmutableSmtpConfig.builder().build(); } return config; } @Override public LdapConfig getLdapConfig() throws Exception { LdapConfig config = (LdapConfig) centralConfigDao.read(LDAP_KEY); if (config == null) { return ImmutableLdapConfig.builder().build(); } return config; } @Override public void updateTransactionConfig(String agentId, TransactionConfig config, String priorVersion) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { String existingVersion = Versions.getVersion(agentConfig.getTransactionConfig()); if (!priorVersion.equals(existingVersion)) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .setTransactionConfig(config) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void insertGaugeConfig(String agentId, GaugeConfig config) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { // check for duplicate mbeanObjectName for (GaugeConfig loopConfig : agentConfig.getGaugeConfigList()) { if (loopConfig.getMbeanObjectName().equals(config.getMbeanObjectName())) { throw new DuplicateMBeanObjectNameException(); } } // no need to check for exact match since redundant with dup mbean object name check return agentConfig.toBuilder() .addGaugeConfig(config) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void updateGaugeConfig(String agentId, GaugeConfig config, String priorVersion) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { List<GaugeConfig> existingConfigs = Lists.newArrayList(agentConfig.getGaugeConfigList()); ListIterator<GaugeConfig> i = existingConfigs.listIterator(); boolean found = false; while (i.hasNext()) { GaugeConfig loopConfig = i.next(); String loopVersion = Versions.getVersion(loopConfig); if (loopVersion.equals(priorVersion)) { i.set(config); found = true; } else if (loopConfig.getMbeanObjectName() .equals(config.getMbeanObjectName())) { throw new DuplicateMBeanObjectNameException(); } // no need to check for exact match since redundant with dup mbean object name // check } if (!found) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .clearGaugeConfig() .addAllGaugeConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void deleteGaugeConfig(String agentId, String version) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { List<GaugeConfig> existingConfigs = Lists.newArrayList(agentConfig.getGaugeConfigList()); ListIterator<GaugeConfig> i = existingConfigs.listIterator(); boolean found = false; while (i.hasNext()) { if (Versions.getVersion(i.next()).equals(version)) { i.remove(); found = true; break; } } if (!found) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .clearGaugeConfig() .addAllGaugeConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentId); } // central supports synthetic monitor configs on rollups @Override public String insertSyntheticMonitorConfig(String agentRollupId, SyntheticMonitorConfig configWithoutId) throws Exception { checkState(configWithoutId.getId().isEmpty()); SyntheticMonitorConfig config = configWithoutId.toBuilder() .setId(ConfigDao.generateNewId()) .build(); configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { // check for duplicate name for (SyntheticMonitorConfig loopConfig : agentConfig .getSyntheticMonitorConfigList()) { if (loopConfig.getDisplay().equals(config.getDisplay())) { throw new DuplicateSyntheticMonitorDisplayException(); } } // no need to check for exact match since redundant with duplicate name check return agentConfig.toBuilder() .addSyntheticMonitorConfig(config) .build(); } }); notifyAgentConfigListeners(agentRollupId); return config.getId(); } // central supports synthetic monitor configs on rollups @Override public void updateSyntheticMonitorConfig(String agentRollupId, SyntheticMonitorConfig config, String priorVersion) throws Exception { configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { List<SyntheticMonitorConfig> existingConfigs = Lists.newArrayList(agentConfig.getSyntheticMonitorConfigList()); ListIterator<SyntheticMonitorConfig> i = existingConfigs.listIterator(); boolean found = false; while (i.hasNext()) { SyntheticMonitorConfig loopConfig = i.next(); if (loopConfig.getId().equals(config.getId())) { if (!Versions.getVersion(loopConfig).equals(priorVersion)) { throw new OptimisticLockException(); } i.set(config); found = true; } else if (loopConfig.getDisplay().equals(config.getDisplay())) { throw new DuplicateSyntheticMonitorDisplayException(); } } if (!found) { throw new SyntheticNotFoundException(); } return agentConfig.toBuilder() .clearSyntheticMonitorConfig() .addAllSyntheticMonitorConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentRollupId); } // central supports synthetic monitor configs on rollups @Override public void deleteSyntheticMonitorConfig(String agentRollupId, String syntheticMonitorId) throws Exception { configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { if (!getAlertConfigsForSyntheticMonitorId(agentRollupId, syntheticMonitorId) .isEmpty()) { throw new IllegalStateException( "Cannot delete synthetic monitor is being used by active alert"); } List<SyntheticMonitorConfig> existingConfigs = Lists.newArrayList(agentConfig.getSyntheticMonitorConfigList()); ListIterator<SyntheticMonitorConfig> i = existingConfigs.listIterator(); boolean found = false; while (i.hasNext()) { if (i.next().getId().equals(syntheticMonitorId)) { i.remove(); found = true; break; } } if (!found) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .clearSyntheticMonitorConfig() .addAllSyntheticMonitorConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentRollupId); } // central supports alert configs on rollups @Override public void insertAlertConfig(String agentRollupId, AlertConfig config) throws Exception { configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { checkAlertConditionDoesNotExist(config, agentConfig.getAlertConfigList()); return agentConfig.toBuilder() .addAlertConfig(config) .build(); } }); notifyAgentConfigListeners(agentRollupId); } // central supports alert configs on rollups @Override public void updateAlertConfig(String agentRollupId, AlertConfig config, String priorVersion) throws Exception { AlertConfig configWithoutDestination = getAlertConfigWithoutDestination(config); configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { List<AlertConfig> existingConfigs = Lists.newArrayList(agentConfig.getAlertConfigList()); ListIterator<AlertConfig> i = existingConfigs.listIterator(); boolean found = false; while (i.hasNext()) { AlertConfig loopConfig = i.next(); if (Versions.getVersion(loopConfig).equals(priorVersion)) { i.set(config); found = true; } else if (getAlertConfigWithoutDestination(loopConfig) .equals(configWithoutDestination)) { throw new IllegalStateException( "This exact alert condition already exists"); } } if (!found) { throw new AlertNotFoundException(); } return agentConfig.toBuilder() .clearAlertConfig() .addAllAlertConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentRollupId); } // central supports alert configs on rollups @Override public void deleteAlertConfig(String agentRollupId, String version) throws Exception { configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { List<AlertConfig> existingConfigs = Lists.newArrayList(agentConfig.getAlertConfigList()); ListIterator<AlertConfig> i = existingConfigs.listIterator(); boolean found = false; while (i.hasNext()) { if (Versions.getVersion(i.next()).equals(version)) { i.remove(); found = true; break; } } if (!found) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .clearAlertConfig() .addAllAlertConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentRollupId); } // central supports ui config on rollups @Override public void updateUiConfig(String agentRollupId, UiConfig config, String priorVersion) throws Exception { configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { String existingVersion = Versions.getVersion(agentConfig.getUiConfig()); if (!priorVersion.equals(existingVersion)) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .setUiConfig(config) .build(); } }); notifyAgentConfigListeners(agentRollupId); } @Override public void updatePluginConfig(String agentId, String pluginId, List<PluginProperty> properties, String priorVersion) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { List<PluginConfig> pluginConfigs = Lists.newArrayList(agentConfig.getPluginConfigList()); ListIterator<PluginConfig> i = pluginConfigs.listIterator(); boolean found = false; while (i.hasNext()) { PluginConfig pluginConfig = i.next(); if (pluginConfig.getId().equals(pluginId)) { String existingVersion = Versions.getVersion(pluginConfig); if (!priorVersion.equals(existingVersion)) { throw new OptimisticLockException(); } i.set(buildPluginConfig(pluginConfig, properties)); found = true; break; } } if (!found) { throw new IllegalStateException("Plugin config not found: " + pluginId); } return agentConfig.toBuilder() .clearPluginConfig() .addAllPluginConfig(pluginConfigs) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void insertInstrumentationConfig(String agentId, InstrumentationConfig config) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { checkInstrumentationDoesNotExist(config, agentConfig.getInstrumentationConfigList()); return agentConfig.toBuilder() .addInstrumentationConfig(config) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void updateInstrumentationConfig(String agentId, InstrumentationConfig config, String priorVersion) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { String newVersion = Versions.getVersion(config); List<InstrumentationConfig> existingConfigs = Lists.newArrayList(agentConfig.getInstrumentationConfigList()); ListIterator<InstrumentationConfig> i = existingConfigs.listIterator(); boolean found = false; while (i.hasNext()) { String loopVersion = Versions.getVersion(i.next()); if (loopVersion.equals(priorVersion)) { i.set(config); found = true; } else if (loopVersion.equals(newVersion)) { throw new IllegalStateException( "This exact instrumentation already exists"); } } if (!found) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .clearInstrumentationConfig() .addAllInstrumentationConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void deleteInstrumentationConfigs(String agentId, List<String> versions) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { List<InstrumentationConfig> existingConfigs = Lists.newArrayList(agentConfig.getInstrumentationConfigList()); ListIterator<InstrumentationConfig> i = existingConfigs.listIterator(); List<String> remainingVersions = Lists.newArrayList(versions); while (i.hasNext()) { String currVersion = Versions.getVersion(i.next()); if (remainingVersions.contains(currVersion)) { i.remove(); remainingVersions.remove(currVersion); break; } } if (!remainingVersions.isEmpty()) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .clearInstrumentationConfig() .addAllInstrumentationConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void insertInstrumentationConfigs(String agentId, List<InstrumentationConfig> configs) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { AgentConfig.Builder builder = agentConfig.toBuilder(); List<InstrumentationConfig> existingConfigs = Lists.newArrayList(agentConfig.getInstrumentationConfigList()); for (InstrumentationConfig config : configs) { checkInstrumentationDoesNotExist(config, existingConfigs); existingConfigs.add(config); } return builder.clearInstrumentationConfig() .addAllInstrumentationConfig(existingConfigs) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void updateUserRecordingConfig(String agentId, UserRecordingConfig config, String priorVersion) throws Exception { configDao.update(agentId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws Exception { String existingVersion = Versions.getVersion(agentConfig.getUserRecordingConfig()); if (!priorVersion.equals(existingVersion)) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .setUserRecordingConfig(config) .build(); } }); notifyAgentConfigListeners(agentId); } @Override public void updateAdvancedConfig(String agentRollupId, AdvancedConfig config, String priorVersion) throws Exception { configDao.update(agentRollupId, new AgentConfigUpdater() { @Override public AgentConfig updateAgentConfig(AgentConfig agentConfig) throws OptimisticLockException { String existingVersion = Versions.getVersion(agentConfig.getAdvancedConfig()); if (!priorVersion.equals(existingVersion)) { throw new OptimisticLockException(); } return agentConfig.toBuilder() .setAdvancedConfig(config) .build(); } }); notifyAgentConfigListeners(agentRollupId); } @Override public void updateAgentRollupConfig(AgentRollupConfig config, String priorVersion) throws Exception { agentDao.update(config, priorVersion); } @Override public void deleteAgentRollupConfig(String agentRollupId) throws Exception { agentDao.delete(agentRollupId); } @Override public void insertUserConfig(UserConfig config) throws Exception { // check for case-insensitive duplicate String username = config.username(); for (UserConfig loopConfig : userDao.read()) { if (loopConfig.username().equalsIgnoreCase(username)) { throw new DuplicateUsernameException(); } } userDao.insertIfNotExists(config); } @Override public void updateUserConfig(UserConfig config, String priorVersion) throws Exception { UserConfig existingConfig = userDao.read(config.username()); if (existingConfig == null) { throw new UserNotFoundException(); } if (!existingConfig.version().equals(priorVersion)) { throw new OptimisticLockException(); } userDao.insert(config); } @Override public void deleteUserConfig(String username) throws Exception { boolean found = false; List<UserConfig> configs = userDao.read(); for (UserConfig config : configs) { if (config.username().equalsIgnoreCase(username)) { found = true; break; } } if (!found) { throw new UserNotFoundException(); } if (getSmtpConfig().host().isEmpty() && configs.size() == 1) { throw new CannotDeleteLastUserException(); } userDao.delete(username); } @Override public void insertRoleConfig(RoleConfig config) throws Exception { // check for case-insensitive duplicate String name = config.name(); for (RoleConfig loopConfig : roleDao.read()) { if (loopConfig.name().equalsIgnoreCase(name)) { throw new DuplicateRoleNameException(); } } roleDao.insertIfNotExists(config); } @Override public void updateRoleConfig(RoleConfig config, String priorVersion) throws Exception { RoleConfig existingConfig = roleDao.read(config.name()); if (existingConfig == null) { throw new RoleNotFoundException(); } if (!existingConfig.version().equals(priorVersion)) { throw new OptimisticLockException(); } roleDao.insert(config); } @Override public void deleteRoleConfig(String name) throws Exception { boolean found = false; List<RoleConfig> configs = roleDao.read(); for (RoleConfig config : configs) { if (config.name().equalsIgnoreCase(name)) { found = true; break; } } if (!found) { throw new RoleNotFoundException(); } if (configs.size() == 1) { throw new CannotDeleteLastRoleException(); } roleDao.delete(name); } @Override public void updateEmbeddedWebConfig(EmbeddedWebConfig config, String priorVersion) throws Exception { throw new UnsupportedOperationException(); } @Override public void updateCentralWebConfig(CentralWebConfig config, String priorVersion) throws Exception { centralConfigDao.write(WEB_KEY, config, priorVersion); } @Override public void updateEmbeddedStorageConfig(EmbeddedStorageConfig config, String priorVersion) { throw new UnsupportedOperationException(); } @Override public void updateCentralStorageConfig(CentralStorageConfig config, String priorVersion) throws Exception { centralConfigDao.write(STORAGE_KEY, config, priorVersion); } @Override public void updateSmtpConfig(SmtpConfig config, String priorVersion) throws Exception { centralConfigDao.write(SMTP_KEY, config, priorVersion); } @Override public void updateLdapConfig(LdapConfig config, String priorVersion) throws Exception { centralConfigDao.write(LDAP_KEY, config, priorVersion); } @Override public long getGaugeCollectionIntervalMillis() { return GAUGE_COLLECTION_INTERVAL_MILLIS; } @Override public ImmutableList<RollupConfig> getRollupConfigs() { return rollupConfigs; } @Override public LazySecretKey getLazySecretKey() throws Exception { return lazySecretKey; } public void addAgentConfigListener(AgentConfigListener listener) { agentConfigListeners.add(listener); } // the updated config is not passed to the listeners to avoid the race condition of multiple // config updates being sent out of order, instead listeners must call get*Config() which will // never return the updates out of order (at worst it may return the most recent update twice // which is ok) private void notifyAgentConfigListeners(String agentRollupId) throws Exception { for (AgentConfigListener agentConfigListener : agentConfigListeners) { agentConfigListener.onChange(agentRollupId); } } private static PluginConfig buildPluginConfig(PluginConfig existingPluginConfig, List<PluginProperty> properties) { Map<String, PluginProperty> props = buildMutablePropertiesMap(properties); PluginConfig.Builder builder = PluginConfig.newBuilder() .setId(existingPluginConfig.getId()) .setName(existingPluginConfig.getName()); for (PluginProperty existingProperty : existingPluginConfig.getPropertyList()) { PluginProperty prop = props.remove(existingProperty.getName()); if (prop == null) { throw new IllegalStateException( "Missing plugin property name: " + existingProperty.getName()); } if (!isSameType(prop.getValue(), existingProperty.getValue())) { throw new IllegalStateException("Plugin property " + prop.getName() + " has incorrect type: " + prop.getValue().getValCase()); } builder.addProperty(existingProperty.toBuilder() .setValue(prop.getValue())); } if (!props.isEmpty()) { throw new IllegalStateException( "Unexpected property name(s): " + Joiner.on(", ").join(props.keySet())); } return builder.build(); } private static Map<String, PluginProperty> buildMutablePropertiesMap( List<PluginProperty> properties) { // it would be nice to use "PluginProperty::getName", or even // "pluginProperty -> pluginProperty.getName()" as the second argument, but both of these // fail to compile under older Java 8 releases (at least 1.8.0_31) return Maps.newHashMap(Maps.uniqueIndex(properties, new Function<PluginProperty, String>() { @Override public String apply(PluginProperty pluginProperty) { return pluginProperty.getName(); } })); } private static boolean isSameType(PluginProperty.Value left, PluginProperty.Value right) { if (left.getValCase() == ValCase.DVAL && right.getValCase() == ValCase.DVAL_NULL) { return true; } if (left.getValCase() == ValCase.DVAL_NULL && right.getValCase() == ValCase.DVAL) { return true; } return left.getValCase() == right.getValCase(); } private static void checkAlertConditionDoesNotExist(AlertConfig config, List<AlertConfig> configs) { AlertConfig configWithoutDestination = getAlertConfigWithoutDestination(config); for (AlertConfig loopConfig : configs) { if (getAlertConfigWithoutDestination(loopConfig).equals(configWithoutDestination)) { throw new IllegalStateException("This exact alert condition already exists"); } } } private static void checkInstrumentationDoesNotExist(InstrumentationConfig config, List<InstrumentationConfig> configs) { for (InstrumentationConfig loopConfig : configs) { if (loopConfig.equals(config)) { throw new IllegalStateException("This exact instrumentation already exists"); } } } private static AlertConfig getAlertConfigWithoutDestination(AlertConfig config) { return AlertConfig.newBuilder(config) .clearEmailAddress() .build(); } private static CentralStorageConfig withCorrectedLists(CentralStorageConfig config) { EmbeddedStorageConfig defaultConfig = ImmutableEmbeddedStorageConfig.builder().build(); ImmutableList<Integer> rollupExpirationHours = fix(config.rollupExpirationHours(), defaultConfig.rollupExpirationHours()); return ImmutableCentralStorageConfig.builder() .copyFrom(config) .rollupExpirationHours(rollupExpirationHours) .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); } public interface AgentConfigListener { // the new config is not passed to onChange so that the receiver has to get the latest, // this avoids race condition worries that two updates may get sent to the receiver in the // wrong order void onChange(String agentRollupId) throws Exception; } private static class LazySecretKeyImpl implements LazySecretKey { private final @Nullable SecretKey secretKey; private LazySecretKeyImpl(String symmetricEncryptionKey) { if (symmetricEncryptionKey.isEmpty()) { secretKey = null; } else { byte[] bytes = BaseEncoding.base16() .decode(symmetricEncryptionKey.toUpperCase(Locale.ENGLISH)); secretKey = new SecretKeySpec(bytes, "AES"); } } @Override public @Nullable SecretKey getExisting() throws Exception { return secretKey; } @Override public SecretKey getOrCreate() throws Exception { if (secretKey == null) { throw new SymmetricEncryptionKeyMissingException(); } return secretKey; } } }