package com.ctrip.framework.apollo.portal.service; import com.ctrip.framework.apollo.portal.component.config.PortalConfig; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.ctrip.framework.apollo.common.constants.GsonType; import com.ctrip.framework.apollo.common.dto.ItemDTO; import com.ctrip.framework.apollo.common.dto.NamespaceDTO; import com.ctrip.framework.apollo.common.dto.ReleaseDTO; import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.core.enums.ConfigFileFormat; import com.ctrip.framework.apollo.core.enums.Env; import com.ctrip.framework.apollo.core.utils.StringUtils; import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; import com.ctrip.framework.apollo.portal.component.PortalSettings; import com.ctrip.framework.apollo.portal.constant.CatEventType; import com.ctrip.framework.apollo.portal.entity.bo.ItemBO; import com.ctrip.framework.apollo.portal.entity.bo.NamespaceBO; import com.ctrip.framework.apollo.portal.spi.UserInfoHolder; import com.ctrip.framework.apollo.tracer.Tracer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; @Service public class NamespaceService { private Logger logger = LoggerFactory.getLogger(NamespaceService.class); private Gson gson = new Gson(); @Autowired private PortalConfig portalConfig; @Autowired private PortalSettings portalSettings; @Autowired private UserInfoHolder userInfoHolder; @Autowired private AdminServiceAPI.NamespaceAPI namespaceAPI; @Autowired private ItemService itemService; @Autowired private ReleaseService releaseService; @Autowired private AppNamespaceService appNamespaceService; @Autowired private InstanceService instanceService; @Autowired private NamespaceBranchService branchService; public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) { if (StringUtils.isEmpty(namespace.getDataChangeCreatedBy())) { namespace.setDataChangeCreatedBy(userInfoHolder.getUser().getUserId()); } namespace.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId()); NamespaceDTO createdNamespace = namespaceAPI.createNamespace(env, namespace); Tracer.logEvent(CatEventType.CREATE_NAMESPACE, String.format("%s+%s+%s+%s", namespace.getAppId(), env, namespace.getClusterName(), namespace.getNamespaceName())); return createdNamespace; } @Transactional public void deleteNamespace(String appId, Env env, String clusterName, String namespaceName) { //1. check private namespace AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(appId, namespaceName); if (appNamespace != null && !appNamespace.isPublic()) { throw new BadRequestException("Private namespace can not be deleted"); } //2. check parent namespace has not instances if (namespaceHasInstances(appId, env, clusterName, namespaceName)) { throw new BadRequestException("Can not delete namespace because namespace has active instances"); } //3. check child namespace has not instances NamespaceDTO childNamespace = branchService.findBranchBaseInfo(appId, env, clusterName, namespaceName); if (childNamespace != null && namespaceHasInstances(appId, env, childNamespace.getClusterName(), namespaceName)) { throw new BadRequestException("Can not delete namespace because namespace's branch has active instances"); } //4. check public namespace has not associated namespace if (appNamespace != null && publicAppNamespaceHasAssociatedNamespace(namespaceName, env)) { throw new BadRequestException("Can not delete public namespace which has associated namespaces"); } String operator = userInfoHolder.getUser().getUserId(); namespaceAPI.deleteNamespace(env, appId, clusterName, namespaceName, operator); } public NamespaceDTO loadNamespaceBaseInfo(String appId, Env env, String clusterName, String namespaceName) { NamespaceDTO namespace = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName); if (namespace == null) { throw new BadRequestException("namespaces not exist"); } return namespace; } /** * load cluster all namespace info with items */ public List<NamespaceBO> findNamespaceBOs(String appId, Env env, String clusterName) { List<NamespaceDTO> namespaces = namespaceAPI.findNamespaceByCluster(appId, env, clusterName); if (namespaces == null || namespaces.size() == 0) { throw new BadRequestException("namespaces not exist"); } List<NamespaceBO> namespaceBOs = new LinkedList<>(); for (NamespaceDTO namespace : namespaces) { NamespaceBO namespaceBO; try { namespaceBO = transformNamespace2BO(env, namespace); namespaceBOs.add(namespaceBO); } catch (Exception e) { logger.error("parse namespace error. app id:{}, env:{}, clusterName:{}, namespace:{}", appId, env, clusterName, namespace.getNamespaceName(), e); throw e; } } return namespaceBOs; } public List<NamespaceDTO> getPublicAppNamespaceAllNamespaces(Env env, String publicNamespaceName, int page, int size) { return namespaceAPI.getPublicAppNamespaceAllNamespaces(env, publicNamespaceName, page, size); } public NamespaceBO loadNamespaceBO(String appId, Env env, String clusterName, String namespaceName) { NamespaceDTO namespace = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName); if (namespace == null) { throw new BadRequestException("namespaces not exist"); } return transformNamespace2BO(env, namespace); } public boolean namespaceHasInstances(String appId, Env env, String clusterName, String namespaceName) { return instanceService.getInstanceCountByNamepsace(appId, env, clusterName, namespaceName) > 0; } public boolean publicAppNamespaceHasAssociatedNamespace(String publicNamespaceName, Env env) { return namespaceAPI.countPublicAppNamespaceAssociatedNamespaces(env, publicNamespaceName) > 0; } public NamespaceBO findPublicNamespaceForAssociatedNamespace(Env env, String appId, String clusterName, String namespaceName) { NamespaceDTO namespace = namespaceAPI.findPublicNamespaceForAssociatedNamespace(env, appId, clusterName, namespaceName); return transformNamespace2BO(env, namespace); } public Map<String, Map<String, Boolean>> getNamespacesPublishInfo(String appId) { Map<String, Map<String, Boolean>> result = Maps.newHashMap(); Set<Env> envs = portalConfig.publishTipsSupportedEnvs(); for (Env env : envs) { if (portalSettings.isEnvActive(env)) { result.put(env.toString(), namespaceAPI.getNamespacePublishInfo(env, appId)); } } return result; } private NamespaceBO transformNamespace2BO(Env env, NamespaceDTO namespace) { NamespaceBO namespaceBO = new NamespaceBO(); namespaceBO.setBaseInfo(namespace); String appId = namespace.getAppId(); String clusterName = namespace.getClusterName(); String namespaceName = namespace.getNamespaceName(); fillAppNamespaceProperties(namespaceBO); List<ItemBO> itemBOs = new LinkedList<>(); namespaceBO.setItems(itemBOs); //latest Release ReleaseDTO latestRelease; Map<String, String> releaseItems = new HashMap<>(); latestRelease = releaseService.loadLatestRelease(appId, env, clusterName, namespaceName); if (latestRelease != null) { releaseItems = gson.fromJson(latestRelease.getConfigurations(), GsonType.CONFIG); } //not Release config items List<ItemDTO> items = itemService.findItems(appId, env, clusterName, namespaceName); int modifiedItemCnt = 0; for (ItemDTO itemDTO : items) { ItemBO itemBO = transformItem2BO(itemDTO, releaseItems); if (itemBO.isModified()) { modifiedItemCnt++; } itemBOs.add(itemBO); } //deleted items List<ItemBO> deletedItems = parseDeletedItems(items, releaseItems); itemBOs.addAll(deletedItems); modifiedItemCnt += deletedItems.size(); namespaceBO.setItemModifiedCnt(modifiedItemCnt); return namespaceBO; } private void fillAppNamespaceProperties(NamespaceBO namespace) { NamespaceDTO namespaceDTO = namespace.getBaseInfo(); //先从当前appId下面找,包含私有的和公共的 AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(namespaceDTO.getAppId(), namespaceDTO.getNamespaceName()); //再从公共的app namespace里面找 if (appNamespace == null) { appNamespace = appNamespaceService.findPublicAppNamespace(namespaceDTO.getNamespaceName()); } String format; boolean isPublic; if (appNamespace == null) { format = ConfigFileFormat.Properties.getValue(); isPublic = false; } else { format = appNamespace.getFormat(); isPublic = appNamespace.isPublic(); namespace.setParentAppId(appNamespace.getAppId()); namespace.setComment(appNamespace.getComment()); } namespace.setFormat(format); namespace.setPublic(isPublic); } private List<ItemBO> parseDeletedItems(List<ItemDTO> newItems, Map<String, String> releaseItems) { Map<String, ItemDTO> newItemMap = BeanUtils.mapByKey("key", newItems); List<ItemBO> deletedItems = new LinkedList<>(); for (Map.Entry<String, String> entry : releaseItems.entrySet()) { String key = entry.getKey(); if (newItemMap.get(key) == null) { ItemBO deletedItem = new ItemBO(); deletedItem.setDeleted(true); ItemDTO deletedItemDto = new ItemDTO(); deletedItemDto.setKey(key); String oldValue = entry.getValue(); deletedItem.setItem(deletedItemDto); deletedItemDto.setValue(oldValue); deletedItem.setModified(true); deletedItem.setOldValue(oldValue); deletedItem.setNewValue(""); deletedItems.add(deletedItem); } } return deletedItems; } private ItemBO transformItem2BO(ItemDTO itemDTO, Map<String, String> releaseItems) { String key = itemDTO.getKey(); ItemBO itemBO = new ItemBO(); itemBO.setItem(itemDTO); String newValue = itemDTO.getValue(); String oldValue = releaseItems.get(key); //new item or modified if (!StringUtils.isEmpty(key) && (oldValue == null || !newValue.equals(oldValue))) { itemBO.setModified(true); itemBO.setOldValue(oldValue == null ? "" : oldValue); itemBO.setNewValue(newValue); } return itemBO; } }