/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.ambari.server.state.stack; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import org.apache.ambari.server.state.stack.upgrade.ConfigUpgradeChangeDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a pack of changes that should be applied to configs * when upgrading from a previous stack. In other words, it's a config delta * from prev stack. * * After first call of enumerateConfigChangesByID() method, instance contains * a cache of data, so it should not be modified in runtime (otherwise * the cache will become outdated). */ @XmlRootElement(name="upgrade-config-changes") @XmlAccessorType(XmlAccessType.FIELD) public class ConfigUpgradePack { /** * Defines per-service config changes. */ @XmlElementWrapper(name="services") @XmlElement(name="service") public List<AffectedService> services; /** * Contains a cached mapping of <change id, change definition>. */ private Map<String, ConfigUpgradeChangeDefinition> changesById; private static Logger LOG = LoggerFactory.getLogger(ConfigUpgradePack.class); /** * no-arg default constructor for JAXB */ public ConfigUpgradePack() { } public ConfigUpgradePack(List<AffectedService> services) { this.services = services; } /** * @return a map of <service name, AffectedService>. */ public Map<String, AffectedService> getServiceMap() { Map<String, AffectedService> result = new HashMap<>(); for (AffectedService service : services) { result.put(service.name, service); } return result; } /** * @return a map of <change id, change definition>. Map is built once and * cached */ public Map<String, ConfigUpgradeChangeDefinition> enumerateConfigChangesByID() { if (changesById == null) { changesById = new HashMap<>(); for(AffectedService service : services) { for(AffectedComponent component: service.components) { for (ConfigUpgradeChangeDefinition changeDefinition : component.changes) { if (changeDefinition.id == null) { LOG.warn(String.format("Config upgrade change definition for service %s," + " component %s has no id", service.name, component.name)); } else if (changesById.containsKey(changeDefinition.id)) { LOG.warn("Duplicate config upgrade change definition with ID " + changeDefinition.id); } changesById.put(changeDefinition.id, changeDefinition); } } } } return changesById; } /** * Merges few config upgrade packs into one and returs result. During merge, * a deep copy of AffectedService and AffectedComponent lists is added to resulting * config upgrade pack. The only level that is not copied deeply is a list of * per-component config changes. * @param cups list of source config upgrade packs * @return merged config upgrade pack that is a deep copy of source * config upgrade packs */ public static ConfigUpgradePack merge(ArrayList<ConfigUpgradePack> cups) { // Map <service_name, <component_name, component_changes>> Map<String, Map<String, AffectedComponent>> mergedServiceMap = new HashMap<>(); for (ConfigUpgradePack configUpgradePack : cups) { for (AffectedService service : configUpgradePack.services) { if (! mergedServiceMap.containsKey(service.name)) { mergedServiceMap.put(service.name, new HashMap<String, AffectedComponent>()); } Map<String, AffectedComponent> mergedComponentMap = mergedServiceMap.get(service.name); for (AffectedComponent component : service.components) { if (! mergedComponentMap.containsKey(component.name)) { AffectedComponent mergedComponent = new AffectedComponent(); mergedComponent.name = component.name; mergedComponent.changes = new ArrayList<>(); mergedComponentMap.put(component.name, mergedComponent); } AffectedComponent mergedComponent = mergedComponentMap.get(component.name); mergedComponent.changes.addAll(component.changes); } } } // Convert merged maps into new ConfigUpgradePack ArrayList<AffectedService> mergedServices = new ArrayList<>(); for (String serviceName : mergedServiceMap.keySet()) { AffectedService mergedService = new AffectedService(); Map<String, AffectedComponent> mergedComponentMap = mergedServiceMap.get(serviceName); mergedService.name = serviceName; mergedService.components = new ArrayList<>(mergedComponentMap.values()); mergedServices.add(mergedService); } return new ConfigUpgradePack(mergedServices); } /** * A service definition in the 'services' element. */ public static class AffectedService { @XmlAttribute public String name; @XmlElement(name="component") public List<AffectedComponent> components; /** * @return a map of <component name, AffectedService> */ public Map<String, AffectedComponent> getComponentMap() { Map<String, AffectedComponent> result = new HashMap<>(); for (AffectedComponent component : components) { result.put(component.name, component); } return result; } } /** * A component definition in the 'services/service' path. */ public static class AffectedComponent { @XmlAttribute public String name; @XmlElementWrapper(name="changes") @XmlElement(name="definition") public List<ConfigUpgradeChangeDefinition> changes; } }