package rocks.inspectit.server.ci; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.PostConstruct; import javax.xml.bind.JAXBException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import org.xml.sax.SAXException; import rocks.inspectit.server.ci.event.AbstractAlertingDefinitionEvent; import rocks.inspectit.server.ci.event.AgentMappingsUpdateEvent; import rocks.inspectit.server.ci.event.BusinessContextDefinitionUpdateEvent; import rocks.inspectit.server.ci.event.EnvironmentUpdateEvent; import rocks.inspectit.server.ci.event.ProfileUpdateEvent; import rocks.inspectit.server.util.CollectionSubtractUtils; import rocks.inspectit.shared.all.exception.BusinessException; import rocks.inspectit.shared.all.exception.enumeration.AlertErrorCodeEnum; import rocks.inspectit.shared.all.exception.enumeration.ConfigurationInterfaceErrorCodeEnum; import rocks.inspectit.shared.all.serializer.impl.SerializationManager; import rocks.inspectit.shared.all.spring.logger.Log; import rocks.inspectit.shared.cs.ci.AgentMapping; import rocks.inspectit.shared.cs.ci.AgentMappings; import rocks.inspectit.shared.cs.ci.AlertingDefinition; import rocks.inspectit.shared.cs.ci.BusinessContextDefinition; import rocks.inspectit.shared.cs.ci.Environment; import rocks.inspectit.shared.cs.ci.Profile; import rocks.inspectit.shared.cs.ci.export.ConfigurationInterfaceImportData; import rocks.inspectit.shared.cs.jaxb.ISchemaVersionAware; import rocks.inspectit.shared.cs.jaxb.JAXBTransformator; /** * Manages all configuration interface operations. * * @author Ivan Senic * @author Marius Oehler * */ @SuppressWarnings({ "PMD.ExcessiveClassLength" }) @Component public class ConfigurationInterfaceManager { /** * The logger of this class. */ @Log Logger log; /** * Path resolver. */ @Autowired private ConfigurationInterfacePathResolver pathResolver; /** * Spring {@link ApplicationEventPublisher} for publishing the events. */ @Autowired private ApplicationEventPublisher eventPublisher; /** * The used {@link SerializationManager}. */ @Autowired SerializationManager serializationManager; /** * {@link JAXBTransformator}. */ private final JAXBTransformator transformator = new JAXBTransformator(); /** * Existing profiles in the system mapped by the id. */ private ConcurrentHashMap<String, Profile> existingProfiles; /** * Existing environments in the system mapped by the id. */ private ConcurrentHashMap<String, Environment> existingEnvironments; /** * Existing environments in the system mapped by the id. */ private ConcurrentHashMap<String, AlertingDefinition> existingAlertingDefinitions; /** * Currently used agent mapping. */ private final AtomicReference<AgentMappings> agentMappingsReference = new AtomicReference<>(); /** * Business context definition. */ private final AtomicReference<BusinessContextDefinition> businessContextDefinitionReference = new AtomicReference<>(); /** * Returns all existing profiles. * * @return Returns all existing profiles. */ public List<Profile> getAllProfiles() { return new ArrayList<>(existingProfiles.values()); } /** * Returns the profile with the given id. * * @param id * Id of profile. * @return {@link Profile} * @throws BusinessException * If profile with given id does not exist. */ public Profile getProfile(String id) throws BusinessException { Profile profile = existingProfiles.get(id); if (null == profile) { throw new BusinessException("Load profile with the id=" + id + ".", ConfigurationInterfaceErrorCodeEnum.PROFILE_DOES_NOT_EXIST); } return profile; } /** * Creates new profile. * * @param profile * Profile template. * @return Returns created profile with correctly set id. * @throws BusinessException * If attempt is made to create common profile. * @throws IOException * If {@link IOException} occurs during save. * @throws JAXBException * If {@link JAXBException} occurs during save. */ public Profile createProfile(Profile profile) throws BusinessException, JAXBException, IOException { if (null == profile.getProfileData()) { throw new BusinessException("Create new profile.", ConfigurationInterfaceErrorCodeEnum.PROFILE_DOES_NOT_HAVE_CORRECT_PROFILE_DATA); } profile.setId(getRandomUUIDString()); profile.setCreatedDate(new Date()); existingProfiles.put(profile.getId(), profile); saveProfile(profile); return profile; } /** * Imports the profile. Note that if profile with the same id already exists it will be * overwritten. * * @param profile * Profile. * @return Returns created/updated profile depending if the overwrite was executed. * @throws BusinessException * If attempt is made to import common profile or profile without the id. * @throws IOException * If {@link IOException} occurs during save. * @throws JAXBException * If {@link JAXBException} occurs during save. */ public Profile importProfile(Profile profile) throws BusinessException, JAXBException, IOException { if (null == profile.getId()) { throw new BusinessException("Import the profile '" + profile.getName() + "'.", ConfigurationInterfaceErrorCodeEnum.IMPORT_DATA_NOT_VALID); } profile.setImportDate(new Date()); if (existingProfiles.containsKey(profile.getId())) { Profile old = existingProfiles.replace(profile.getId(), profile); Files.deleteIfExists(pathResolver.getProfileFilePath(old)); } else { existingProfiles.put(profile.getId(), profile); } saveProfile(profile); return profile; } /** * Updates the given profile and saves it to the disk. Update will fail with an Exception if: * <ul> * <li>Attempt is made to update default profile. * <li>Profile does not exists on the CMR. * <li>Profile revision sequence does not match the current sequence. * </ul> * * @param profile * Profile to update. * @return updated profile instance * @throws BusinessException * If update of common profile is attempted. * @throws IOException * If {@link IOException} occurs during update. * @throws JAXBException * If {@link JAXBException} occurs during update. */ public synchronized Profile updateProfile(Profile profile) throws BusinessException, JAXBException, IOException { if (profile.isCommonProfile()) { throw new BusinessException("Update the profile '" + profile.getName() + ".", ConfigurationInterfaceErrorCodeEnum.COMMON_PROFILE_CAN_NOT_BE_ALTERED); } return updateProfileInternal(profile); } /** * Deletes the existing profile. * * @param profile * Profile to delete. * @throws IOException * If {@link IOException} occurs during update. * @throws BusinessException * If profile is common profile. */ public void deleteProfile(Profile profile) throws BusinessException, IOException { if (profile.isCommonProfile()) { throw new BusinessException("Delete the profile '" + profile.getName() + ".", ConfigurationInterfaceErrorCodeEnum.COMMON_PROFILE_CAN_NOT_BE_ALTERED); } String id = profile.getId(); Profile local = existingProfiles.get(id); if (null != local) { Files.deleteIfExists(pathResolver.getProfileFilePath(local)); for (Environment environment : existingEnvironments.values()) { if (CollectionUtils.isEmpty(environment.getProfileIds())) { continue; } if (environment.getProfileIds().contains(id)) { environment = copy(environment); environment.getProfileIds().remove(id); try { updateEnvironment(environment, false); } catch (Exception e) { log.error("Update of the environment on the profile deletion failed.", e); } } } existingProfiles.remove(id); } } /** * Returns all existing environment. * * @return Returns all existing environment. */ public Collection<Environment> getAllEnvironments() { return new ArrayList<>(existingEnvironments.values()); } /** * Returns the environment with the given id. * * @param id * Id of environment. * @return {@link Environment} * @throws BusinessException * If environment with given id does not exist. */ public Environment getEnvironment(String id) throws BusinessException { Environment environment = existingEnvironments.get(id); if (null == environment) { throw new BusinessException("Load environemnt with the id=" + id + ".", ConfigurationInterfaceErrorCodeEnum.ENVIRONMENT_DOES_NOT_EXIST); } return environment; } /** * Creates new environment. * * @param environment * Environment template. * @return Returns created environment with correctly set id * @throws IOException * If {@link IOException} occurs during create. * @throws JAXBException * If {@link JAXBException} occurs during create. */ public Environment createEnvironment(Environment environment) throws JAXBException, IOException { environment.setId(getRandomUUIDString()); // add the default include profiles Set<String> profileIds = new HashSet<>(); for (Profile profile : existingProfiles.values()) { if (profile.isDefaultProfile()) { profileIds.add(profile.getId()); } } environment.setProfileIds(profileIds); environment.setCreatedDate(new Date()); existingEnvironments.put(environment.getId(), environment); saveEnvironment(environment); return environment; } /** * Imports the environment. Note that if environment with the same id already exists it will be * overwritten. * * @param environment * Environment. * @return Returns created/updated environment depending if the overwrite was executed. * @throws BusinessException * If attempt is made to import environment without the id. * @throws IOException * If {@link IOException} occurs during save. * @throws JAXBException * If {@link JAXBException} occurs during save. */ public Environment importEnvironment(Environment environment) throws BusinessException, JAXBException, IOException { if (null == environment.getId()) { throw new BusinessException("Import the environment '" + environment.getName() + "'.", ConfigurationInterfaceErrorCodeEnum.IMPORT_DATA_NOT_VALID); } environment.setImportDate(new Date()); if (existingEnvironments.containsKey(environment.getId())) { Environment old = existingEnvironments.replace(environment.getId(), environment); Files.deleteIfExists(pathResolver.getEnvironmentFilePath(old)); } else { existingEnvironments.put(environment.getId(), environment); } checkProfiles(environment); saveEnvironment(environment); return environment; } /** * Updates the given environment and saves it to the disk. Update will fail with an Exception * if: * <ul> * <li>Environment does not exists on the CMR. * <li>Environment revision sequence does not match the current sequence. * </ul> * * @param environment * Environment to update. * @param checkProfiles * if environment should be checked for non existing profiles * @return updated environment instance * @throws IOException * If {@link IOException} occurs during update. * @throws JAXBException * If {@link JAXBException} occurs during update. * @throws BusinessException * If environment does not exists or the revision check failed. */ public synchronized Environment updateEnvironment(Environment environment, boolean checkProfiles) throws BusinessException, JAXBException, IOException { if (checkProfiles) { checkProfiles(environment); } String id = environment.getId(); environment.setRevision(environment.getRevision() + 1); Environment local = existingEnvironments.replace(id, environment); if (null == local) { existingEnvironments.remove(id); throw new BusinessException("Update of the environment '" + environment.getName() + ".", ConfigurationInterfaceErrorCodeEnum.ENVIRONMENT_DOES_NOT_EXIST); } else if ((local != environment) && ((local.getRevision() + 1) != environment.getRevision())) { // NOPMD // == check here if same object is used existingEnvironments.replace(id, local); BusinessException e = new BusinessException("Update of the environment '" + environment.getName() + ".", ConfigurationInterfaceErrorCodeEnum.REVISION_CHECK_FAILED); environment.setRevision(environment.getRevision() - 1); throw e; } environment.setUpdatedDate(new Date()); saveEnvironment(environment); // if the name changes we should also delete local from disk if (!Objects.equals(environment.getName(), local.getName())) { Files.deleteIfExists(pathResolver.getEnvironmentFilePath(local)); } publishEnvironmentUpdateEvent(local, environment); return environment; } /** * Deletes the existing environment. * * @param environment * Environment to delete. * @throws IOException * If {@link IOException} occurs during delete. */ public void deleteEnvironment(Environment environment) throws IOException { String id = environment.getId(); Environment local = existingEnvironments.remove(id); if (null != local) { Files.deleteIfExists(pathResolver.getEnvironmentFilePath(local)); AgentMappings agentMappings = agentMappingsReference.get(); if (checkEnvironments(agentMappings)) { try { saveAgentMappings(agentMappings, false); } catch (Exception e) { log.error("Update of the agent mappings on the environment deletion failed.", e); } } } } /** * Returns the currently used agent mappings. * * @return Returns the currently used agent mappings. */ public AgentMappings getAgentMappings() { return agentMappingsReference.get(); } /** * Sets the agent mappings to be used. * * @param agentMappings * {@link AgentMappings} * @param checkEnvironments * if mapping should be checked for non existing environments * @return updated {@link AgentMappings} instance * @throws IOException * If {@link IOException} occurs during update. * @throws JAXBException * If {@link JAXBException} occurs during update. * @throws BusinessException * If revision check fails. */ public AgentMappings saveAgentMappings(AgentMappings agentMappings, boolean checkEnvironments) throws BusinessException, JAXBException, IOException { // check environment if (checkEnvironments) { checkEnvironments(agentMappings); } // ensure there is not overwrite AgentMappings current; do { current = agentMappingsReference.get(); if (current.getRevision() != agentMappings.getRevision()) { throw new BusinessException("Update of the agent mappings.", ConfigurationInterfaceErrorCodeEnum.REVISION_CHECK_FAILED); } } while (!agentMappingsReference.compareAndSet(current, agentMappings)); agentMappings.setRevision(agentMappings.getRevision() + 1); saveAgentMapping(agentMappings); publishAgentMappingsUpdateEvent(); return agentMappings; } /** * Returns the current business context definition. * * @return Returns the current business context definition. */ public BusinessContextDefinition getBusinessconContextDefinition() { return businessContextDefinitionReference.get(); } /** * Updates and stores new definition of the business context. * * @param businessContextDefinition * New {@link IBusinessContextDefinition} to use. * @return the updated {@link BusinessContextDefinition} instance. * @throws BusinessException * If updating business context fails. * @throws IOException * If {@link IOException} occurs during update. * @throws JAXBException * If {@link JAXBException} occurs during update. */ public synchronized BusinessContextDefinition updateBusinessContextDefinition(BusinessContextDefinition businessContextDefinition) throws BusinessException, JAXBException, IOException { businessContextDefinition.setRevision(businessContextDefinition.getRevision() + 1); BusinessContextDefinition currentBusinessContextDefinition = businessContextDefinitionReference.get(); if ((currentBusinessContextDefinition != businessContextDefinition) && ((currentBusinessContextDefinition.getRevision() + 1) != businessContextDefinition.getRevision())) { // NOPMD throw new BusinessException("Update of the business context.", ConfigurationInterfaceErrorCodeEnum.REVISION_CHECK_FAILED); } saveBusinessContext(businessContextDefinition); eventPublisher.publishEvent(new BusinessContextDefinitionUpdateEvent(this, businessContextDefinition)); return businessContextDefinition; } /** * Returns all existing alerting definitions. * * @return {@link List} containing all {@link AlertingDefinition}s. */ public List<AlertingDefinition> getAlertingDefinitions() { return new ArrayList<>(existingAlertingDefinitions.values()); } /** * Returns the {@link AlertingDefinition} for the given id. * * @param id * the identifier of the {@link AlertingDefinition} * @return {@link AlertingDefinition} of the given id * @throws BusinessException * if no {@link AlertingDefinition} exists for the given id */ public AlertingDefinition getAlertingDefinition(String id) throws BusinessException { AlertingDefinition alertingDefinition = existingAlertingDefinitions.get(id); if (null == alertingDefinition) { throw new BusinessException("Load alerting definition with the id=" + id + ".", AlertErrorCodeEnum.ALERTING_DEFINITION_DOES_NOT_EXIST); } return alertingDefinition; } /** * Creates a new {@link AlertingDefinition}. * * @param alertingDefinition * {@link AlertingDefinition} template * @return the created {@link AlertingDefinition} * @throws IOException * if {@link IOException} occurs during create * @throws JAXBException * if {@link JAXBException} occurs during create */ public AlertingDefinition createAlertingDefinition(AlertingDefinition alertingDefinition) throws JAXBException, IOException { alertingDefinition.setId(getRandomUUIDString()); alertingDefinition.setCreatedDate(new Date()); existingAlertingDefinitions.put(alertingDefinition.getId(), alertingDefinition); saveAlertingDefinition(alertingDefinition); eventPublisher.publishEvent(new AbstractAlertingDefinitionEvent.AlertingDefinitionCreatedEvent(this, alertingDefinition)); return alertingDefinition; } /** * Updates the given {@link AlertingDefinition}. * * @param alertingDefinition * {@link AlertingDefinition} to update * @return the updated {@link AlertingDefinition} * @throws BusinessException * if {@link BusinessException} occurs during create * @throws JAXBException * if {@link JAXBException} occurs during create * @throws IOException * if {@link IOException} occurs during create */ public synchronized AlertingDefinition updateAlertingDefinition(AlertingDefinition alertingDefinition) throws BusinessException, JAXBException, IOException { String id = alertingDefinition.getId(); if (id == null) { throw new BusinessException("Update of an uncreated alerting definition.", AlertErrorCodeEnum.MISSING_ID); } alertingDefinition.setRevision(alertingDefinition.getRevision() + 1); AlertingDefinition local = existingAlertingDefinitions.replace(id, alertingDefinition); if (null == local) { existingAlertingDefinitions.remove(id); throw new BusinessException("Update of the alerting definition '" + alertingDefinition.getName() + ".", AlertErrorCodeEnum.ALERTING_DEFINITION_DOES_NOT_EXIST); } else if ((local != alertingDefinition) && ((local.getRevision() + 1) != alertingDefinition.getRevision())) { // NOPMD existingAlertingDefinitions.replace(id, local); BusinessException e = new BusinessException("Update of the alerting definition '" + alertingDefinition.getName() + ".", AlertErrorCodeEnum.REVISION_CHECK_FAILED); alertingDefinition.setRevision(alertingDefinition.getRevision() - 1); throw e; } Date currentDate = new Date(); alertingDefinition.setUpdatedDate(currentDate); // if the name changes we should also delete local from disk if (!Objects.equals(alertingDefinition.getName(), local.getName())) { Files.deleteIfExists(pathResolver.getAlertingDefinitionFilePath(local)); } saveAlertingDefinition(alertingDefinition); eventPublisher.publishEvent(new AbstractAlertingDefinitionEvent.AlertingDefinitionUpdateEvent(this, alertingDefinition)); return alertingDefinition; } /** * Deletes the alerting definition. * * @param alertingDefinition * AlertingDefinition to delete. * @throws IOException * If {@link IOException} occurs during delete. */ public void deleteAlertingDefinition(AlertingDefinition alertingDefinition) throws IOException { String id = alertingDefinition.getId(); AlertingDefinition local = existingAlertingDefinitions.remove(id); if (local != null) { Files.deleteIfExists(pathResolver.getAlertingDefinitionFilePath(local)); eventPublisher.publishEvent(new AbstractAlertingDefinitionEvent.AlertingDefinitionDeletedEvent(this, local)); } } /** * Returns the bytes for the given import data consisted out of given environments and profiles. * These bytes can be saved directly to export file. * * @param environments * Environments to export. * @param profiles * Profiles to export. * @return Byte array. * @throws IOException * If {@link IOException} occurs during marshall. * @throws JAXBException * If {@link JAXBException} occurs during marshall. */ public byte[] getExportData(Collection<Environment> environments, Collection<Profile> profiles) throws JAXBException, IOException { ConfigurationInterfaceImportData importData = new ConfigurationInterfaceImportData(); if (CollectionUtils.isNotEmpty(environments)) { Collection<Environment> exportedEnvironments = new ArrayList<>(environments.size()); for (Environment environment : environments) { try { exportedEnvironments.add(getEnvironment(environment.getId())); } catch (BusinessException e) { log.warn("Environment trying to export does not exists.", e); } } importData.setEnvironments(exportedEnvironments); } if (CollectionUtils.isNotEmpty(profiles)) { Collection<Profile> exportedProfiles = new ArrayList<>(profiles.size()); for (Profile profile : profiles) { try { exportedProfiles.add(getProfile(profile.getId())); } catch (BusinessException e) { log.warn("Profile trying to export does not exists.", e); } } importData.setProfiles(exportedProfiles); } return transformator.marshall(importData, null); } /** * Returns the {@link ConfigurationInterfaceImportData} from the given import data bytes. * * @param importData * bytes that were exported. * @return {@link ConfigurationInterfaceImportData}. * @throws SAXException * IF {@link SAXException} occurs during unmarshall. * @throws IOException * If {@link IOException} occurs during unmarshall. * @throws JAXBException * If {@link JAXBException} occurs during unmarshall. */ public ConfigurationInterfaceImportData getImportData(byte[] importData) throws JAXBException, IOException, SAXException { return transformator.unmarshall(importData, pathResolver.getSchemaPath(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION, pathResolver.getMigrationPath(), ConfigurationInterfaceImportData.class); } /** * Internal process of updating the profile. * * @param profile * Profile being updated. * @return Updated instance. * @throws IOException * If {@link IOException} occurs during save. * @throws JAXBException * If {@link JAXBException} occurs during save. * @throws BusinessException * If profile does not exist or revision check fails. */ private Profile updateProfileInternal(Profile profile) throws BusinessException, JAXBException, IOException { String id = profile.getId(); profile.setRevision(profile.getRevision() + 1); Profile local = existingProfiles.replace(id, profile); if (null == local) { existingProfiles.remove(id); throw new BusinessException("Update of the profile '" + profile.getName() + ".", ConfigurationInterfaceErrorCodeEnum.PROFILE_DOES_NOT_EXIST); } else if ((local != profile) && ((local.getRevision() + 1) != profile.getRevision())) { // NOPMD // == check here if same object is used existingProfiles.replace(id, local); BusinessException e = new BusinessException("Update of the profile '" + profile.getName() + ".", ConfigurationInterfaceErrorCodeEnum.REVISION_CHECK_FAILED); profile.setRevision(profile.getRevision() - 1); throw e; } profile.setUpdatedDate(new Date()); saveProfile(profile); // if the name changes we should also delete local from disk if (!Objects.equals(profile.getName(), local.getName())) { Files.deleteIfExists(pathResolver.getProfileFilePath(local)); } // notify listeners publishProfileUpdateEvent(local, profile); return profile; } /** * Notifies listeners about profile update. * * @param old * Old profile instance. * @param updated * Updated profile instance. */ private void publishProfileUpdateEvent(Profile old, Profile updated) { ProfileUpdateEvent profileUpdateEvent = new ProfileUpdateEvent(this, old, updated); eventPublisher.publishEvent(profileUpdateEvent); } /** * Notifies listeners about environment update. * * @param old * Old environment instance. * @param updated * Updated environment instance. */ private void publishEnvironmentUpdateEvent(Environment old, Environment updated) { Collection<Profile> removedProfiles = getProfileDifference(old, updated); Collection<Profile> addedProfiles = getProfileDifference(updated, old); EnvironmentUpdateEvent event = new EnvironmentUpdateEvent(this, old, updated, addedProfiles, removedProfiles); eventPublisher.publishEvent(event); } /** * Returns profile differences between two profiles. The result collection will contain profiles * that exists in the e1 and do not exist in e2. * * @param e1 * First environment. * @param e2 * Second environment. * @return Collection of profiles existing in first environment and not in the second. */ private Collection<Profile> getProfileDifference(Environment e1, Environment e2) { Collection<Profile> profiles = new ArrayList<>(); Collection<String> profilesIds = CollectionSubtractUtils.subtractSafe(e1.getProfileIds(), e2.getProfileIds()); for (String id : profilesIds) { try { profiles.add(getProfile(id)); } catch (BusinessException e) { if (log.isDebugEnabled()) { log.debug("Profile with id " + id + " ignored during profile difference calculation as it does not exist anymore.", e); } continue; } } return profiles; } /** * Notifies listeners about mappings update. */ private void publishAgentMappingsUpdateEvent() { AgentMappingsUpdateEvent event = new AgentMappingsUpdateEvent(this); eventPublisher.publishEvent(event); } /** * Cleans the non-existing profiles from the {@link Environment}. * * @param environment * {@link Environment}. * @return if environment was changed during the check process */ private boolean checkProfiles(Environment environment) { boolean changed = false; if (CollectionUtils.isNotEmpty(environment.getProfileIds())) { for (Iterator<String> it = environment.getProfileIds().iterator(); it.hasNext();) { String profileId = it.next(); if (!existingProfiles.containsKey(profileId)) { it.remove(); changed = true; } } } return changed; } /** * Cleans the non-existing environments from the {@link AgentMappings}. * * @param agentMappings * {@link AgentMappings}. * @return if mappings where changed during the check process */ private boolean checkEnvironments(AgentMappings agentMappings) { boolean changed = false; if (CollectionUtils.isNotEmpty(agentMappings.getMappings())) { for (Iterator<AgentMapping> it = agentMappings.getMappings().iterator(); it.hasNext();) { AgentMapping agentMapping = it.next(); if (!existingEnvironments.containsKey(agentMapping.getEnvironmentId())) { it.remove(); changed = true; } } } return changed; } /** * Saves profile and persists it to the list. * * @param profile * Profile to be saved. * @throws IOException * If {@link IOException} occurs during save. * @throws JAXBException * If {@link JAXBException} occurs during save. * @throws BusinessException * If saving of the common profile is requested. */ private void saveProfile(Profile profile) throws BusinessException, JAXBException, IOException { if (profile.isCommonProfile()) { throw new BusinessException("Save the profile '" + profile.getName() + " to disk.", ConfigurationInterfaceErrorCodeEnum.COMMON_PROFILE_CAN_NOT_BE_ALTERED); } transformator.marshall(pathResolver.getProfileFilePath(profile), profile, getRelativeToSchemaPath(pathResolver.getProfilesPath()).toString(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION); } /** * Saves {@link Environment} to the disk. * * @param environment * {@link Environment} to save. * @throws IOException * If {@link IOException} occurs. * @throws JAXBException * If {@link JAXBException} occurs. If saving fails. */ private void saveEnvironment(Environment environment) throws JAXBException, IOException { transformator.marshall(pathResolver.getEnvironmentFilePath(environment), environment, getRelativeToSchemaPath(pathResolver.getEnvironmentPath()).toString(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION); } /** * Saves agent mapping. * * @param agentMappings * To save * @throws IOException * If {@link IOException} occurs. * @throws JAXBException * If {@link JAXBException} occurs. If saving fails. */ private void saveAgentMapping(AgentMappings agentMappings) throws JAXBException, IOException { transformator.marshall(pathResolver.getAgentMappingFilePath(), agentMappings, getRelativeToSchemaPath(pathResolver.getDefaultCiPath()).toString(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION); } /** * Saves the passed {@link IBusinessContextDefinition}. * * @param businessContextDefinition * {@link IBusinessContextDefinition} to save * @throws IOException * If {@link IOException} occurs. * @throws JAXBException * If {@link JAXBException} occurs. If saving fails. */ private void saveBusinessContext(BusinessContextDefinition businessContextDefinition) throws JAXBException, IOException { businessContextDefinitionReference.set(businessContextDefinition); transformator.marshall(pathResolver.getBusinessContextFilePath(), businessContextDefinition, getRelativeToSchemaPath(pathResolver.getDefaultCiPath()).toString(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION); } /** * Save the given {@link AlertingDefinition}. * * @param alertingDefinition * the {@link AlertingDefinition} to save * @throws IOException * if {@link IOException} occurs * @throws JAXBException * if {@link JAXBException} occurs. If saving fails */ private void saveAlertingDefinition(AlertingDefinition alertingDefinition) throws JAXBException, IOException { transformator.marshall(pathResolver.getAlertingDefinitionFilePath(alertingDefinition), alertingDefinition, getRelativeToSchemaPath(pathResolver.getDefaultCiPath()).toString()); } /** * Returns given path relative to schema part. * * @param path * path to relativize * @return path relative to schema part * @see #getSchemaPath() */ private Path getRelativeToSchemaPath(Path path) { return path.relativize(pathResolver.getSchemaPath()); } /** * Creates a deep copy of the given object. * * @param object * object to clone * @param <T> * the class of the given object * @return the new object */ private synchronized <T> T copy(T object) { return serializationManager.copy(object); } /** * Initialize. */ @PostConstruct public void init() { // WARNING loading must be done in this order loadExistingProfiles(); loadExistingEnvironments(); loadAgentMappings(); loadBusinessContextDefinition(); loadExistingAlertingDefinitions(); } /** * Loads all existing profiles. */ private void loadExistingProfiles() { log.info("|-Loading the existing Configuration interface profiles.."); existingProfiles = new ConcurrentHashMap<>(16, 0.75f, 2); Path path = pathResolver.getProfilesPath(); final Path schemaPath = pathResolver.getSchemaPath(); if (Files.notExists(path)) { log.info("Default configuration interface profiles path does not exists. No profile is loaded."); return; } try { Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (isXmlFile(file)) { try { Profile profile = transformator.unmarshall(file, schemaPath, ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION, pathResolver.getMigrationPath(), Profile.class); existingProfiles.put(profile.getId(), profile); } catch (JAXBException | SAXException e) { log.error("Error reading existing Configuration interface profile file. File path: " + file.toString() + ".", e); } } return FileVisitResult.CONTINUE; } }); } catch (IOException e) { log.error("Error exploring Configuration interface profiles directory. Directory path: " + path.toString() + ".", e); } if (MapUtils.isEmpty(existingProfiles)) { log.info("No profile exists in the default profiles path."); } } /** * Loads all existing profiles. */ private void loadExistingEnvironments() { log.info("|-Loading the existing Configuration interface environments.."); existingEnvironments = new ConcurrentHashMap<>(16, 0.75f, 2); Path path = pathResolver.getEnvironmentPath(); final Path schemaPath = pathResolver.getSchemaPath(); if (Files.notExists(path)) { // create at least one default environment on start-up log.info("||-Default configuration interface environment path does not exists. Creating default environment."); Environment environment = new Environment(); environment.setName("Default Environment"); environment.setDescription("Environment that contains the default inspectIT monitoring settings and all default profiles."); try { createEnvironment(environment); } catch (Exception e) { log.error("Error creating default Configuration interface environment on the CMR.", e); } return; } try { Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (isXmlFile(file)) { try { Environment environment = transformator.unmarshall(file, schemaPath, ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION, pathResolver.getMigrationPath(), Environment.class); existingEnvironments.put(environment.getId(), environment); // if checking of the profile made a change, save it if (checkProfiles(environment)) { try { saveEnvironment(environment); log.info("Environment '" + environment.getName() + "' was auto-updated as it was referencing the non-existing profile(s)."); } catch (IOException | JAXBException e) { log.error("Error updating existing Configuration interface environment file. File path: " + file.toString() + ".", e); } } } catch (JAXBException | SAXException e) { log.error("Error reading existing Configuration interface environment file. File path: " + file.toString() + ".", e); } } return FileVisitResult.CONTINUE; } }); } catch (IOException e) { log.error("Error exploring Configuration interface environments directory. Directory path: " + path.toString() + ".", e); } } /** * Loads currently used agent mapping file. */ private void loadAgentMappings() { log.info("|-Loading the existing Configuration interface agent mappings.."); AgentMappings agentMappings; Path path = pathResolver.getAgentMappingFilePath(); if (Files.notExists(path)) { log.info("||-The agent mappings file does not exists. Creating default mapping."); agentMappings = new AgentMappings(Collections.<AgentMapping> emptyList()); if (MapUtils.isNotEmpty(existingEnvironments)) { Environment environment = existingEnvironments.values().iterator().next(); if (null != environment) { // we expect only one mapping here - the default one AgentMapping mapping = new AgentMapping("*", "*"); mapping.setEnvironmentId(environment.getId()); Collection<AgentMapping> mappings = new ArrayList<>(); mappings.add(mapping); agentMappings.setMappings(mappings); } } } else { try { agentMappings = transformator.unmarshall(path, pathResolver.getSchemaPath(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION, pathResolver.getMigrationPath(), AgentMappings.class); } catch (JAXBException | IOException | SAXException e) { agentMappings = new AgentMappings(Collections.<AgentMapping> emptyList()); log.error("Error loading Configuration interface agent mappings file. File path: " + path.toString() + ".", e); } } // set atomic reference agentMappingsReference.set(agentMappings); // check that mapped Environment exists if (checkEnvironments(agentMappings)) { try { saveAgentMapping(agentMappings); log.info("Agent mappings configuration is auto-updated as it was referencing the non-existing environment(s)."); } catch (JAXBException | IOException e) { log.error("Error save Configuration interface agent mappings file. File path: " + path.toString() + ".", e); } } } /** * Loads the business context definition if it is not already loaded. If successfully loaded * definition will be placed in the {@link #businessContextDefinition} field. */ private void loadBusinessContextDefinition() { log.info("|-Loading the business context definition"); Path path = pathResolver.getBusinessContextFilePath(); BusinessContextDefinition businessContextDefinition = null; if (Files.exists(path)) { try { businessContextDefinition = transformator.unmarshall(path, pathResolver.getSchemaPath(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION, pathResolver.getMigrationPath(), BusinessContextDefinition.class); } catch (JAXBException | IOException | SAXException e) { log.error("Error loading Configuration interface business context file. File path: " + path.toString() + ".", e); } } if (null == businessContextDefinition) { businessContextDefinition = new BusinessContextDefinition(); try { saveBusinessContext(businessContextDefinition); } catch (JAXBException | IOException e) { log.error("Error saving Configuration interface business context file. File path: " + path.toString() + ".", e); } } businessContextDefinitionReference.set(businessContextDefinition); } /** * Loads all existing alerting definitions. */ private void loadExistingAlertingDefinitions() { log.info("|-Loading the existing alerting definitions.."); existingAlertingDefinitions = new ConcurrentHashMap<>(16, 0.75f, 2); Path path = pathResolver.getAlertingDefinitionsPath(); if (Files.notExists(path)) { log.info("Default alerting definitions path does not exists. No alerting definitions are loaded."); return; } try { Files.walkFileTree(path, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (isXmlFile(file)) { try { AlertingDefinition alertingDefinition = transformator.unmarshall(file, pathResolver.getSchemaPath(), ISchemaVersionAware.ConfigurationInterface.SCHEMA_VERSION, pathResolver.getMigrationPath(), AlertingDefinition.class); existingAlertingDefinitions.put(alertingDefinition.getId(), alertingDefinition); } catch (JAXBException | SAXException e) { log.error("Error reading existing alerting definition file. File path: " + file.toString() + ".", e); } } return FileVisitResult.CONTINUE; } }); } catch (IOException e) { log.error("Error exploring alerting definitions directory. Directory path: " + path.toString() + ".", e); } if (MapUtils.isEmpty(existingAlertingDefinitions)) { log.info("No alerting definitions are in the default path."); } eventPublisher.publishEvent(new AbstractAlertingDefinitionEvent.AlertingDefinitionLoadedEvent(this, new ArrayList<>(existingAlertingDefinitions.values()))); } /** * If path is a file that ends with the <i>.xml</i> extension. * * @param path * Path to the file. * @return If path is a file that ends with the <i>.xml</i> extension. */ private boolean isXmlFile(Path path) { return !Files.isDirectory(path) && path.toString().endsWith(".xml"); } /** * Returns the unique String that will be used for IDs. * * @return Returns unique string based on the {@link UUID}. */ private String getRandomUUIDString() { return UUID.randomUUID().toString(); } }