package com.ctrip.framework.apollo.biz.service; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.ctrip.framework.apollo.biz.entity.Audit; import com.ctrip.framework.apollo.biz.entity.Cluster; import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.repository.NamespaceRepository; import com.ctrip.framework.apollo.common.constants.GsonType; import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus; import com.ctrip.framework.apollo.common.entity.AppNamespace; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.ServiceException; import com.ctrip.framework.apollo.common.utils.BeanUtils; import com.ctrip.framework.apollo.core.ConfigConsts; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @Service public class NamespaceService { private Gson gson = new Gson(); @Autowired private NamespaceRepository namespaceRepository; @Autowired private AuditService auditService; @Autowired private AppNamespaceService appNamespaceService; @Autowired private ItemService itemService; @Autowired private CommitService commitService; @Autowired private ReleaseService releaseService; @Autowired private ClusterService clusterService; @Autowired private NamespaceBranchService namespaceBranchService; @Autowired private ReleaseHistoryService releaseHistoryService; @Autowired private NamespaceLockService namespaceLockService; @Autowired private InstanceService instanceService; public Namespace findOne(Long namespaceId) { return namespaceRepository.findOne(namespaceId); } public Namespace findOne(String appId, String clusterName, String namespaceName) { return namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, clusterName, namespaceName); } public Namespace findPublicNamespaceForAssociatedNamespace(String clusterName, String namespaceName) { AppNamespace appNamespace = appNamespaceService.findPublicNamespaceByName(namespaceName); if (appNamespace == null) { throw new BadRequestException("namespace not exist"); } String appId = appNamespace.getAppId(); Namespace namespace = findOne(appId, clusterName, namespaceName); //default cluster's namespace if (Objects.equals(clusterName, ConfigConsts.CLUSTER_NAME_DEFAULT)) { return namespace; } //custom cluster's namespace not exist. //return default cluster's namespace if (namespace == null) { return findOne(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespaceName); } //custom cluster's namespace exist and has published. //return custom cluster's namespace Release latestActiveRelease = releaseService.findLatestActiveRelease(namespace); if (latestActiveRelease != null) { return namespace; } Namespace defaultNamespace = findOne(appId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespaceName); //custom cluster's namespace exist but never published. //and default cluster's namespace not exist. //return custom cluster's namespace if (defaultNamespace == null) { return namespace; } //custom cluster's namespace exist but never published. //and default cluster's namespace exist and has published. //return default cluster's namespace Release defaultNamespaceLatestActiveRelease = releaseService.findLatestActiveRelease(defaultNamespace); if (defaultNamespaceLatestActiveRelease != null) { return defaultNamespace; } //custom cluster's namespace exist but never published. //and default cluster's namespace exist but never published. //return custom cluster's namespace return namespace; } public List<Namespace> findPublicAppNamespaceAllNamespaces(String namespaceName, Pageable page) { AppNamespace publicAppNamespace = appNamespaceService.findPublicNamespaceByName(namespaceName); if (publicAppNamespace == null) { throw new BadRequestException( String.format("Public appNamespace not exists. NamespaceName = %s", namespaceName)); } List<Namespace> namespaces = namespaceRepository.findByNamespaceName(namespaceName, page); return filterChildNamespace(namespaces); } private List<Namespace> filterChildNamespace(List<Namespace> namespaces) { List<Namespace> result = new LinkedList<>(); if (CollectionUtils.isEmpty(namespaces)) { return result; } for (Namespace namespace : namespaces) { if (!isChildNamespace(namespace)) { result.add(namespace); } } return result; } public int countPublicAppNamespaceAssociatedNamespaces(String publicNamespaceName) { AppNamespace publicAppNamespace = appNamespaceService.findPublicNamespaceByName(publicNamespaceName); if (publicAppNamespace == null) { throw new BadRequestException( String.format("Public appNamespace not exists. NamespaceName = %s", publicNamespaceName)); } return namespaceRepository.countByNamespaceNameAndAppIdNot(publicNamespaceName, publicAppNamespace.getAppId()); } public List<Namespace> findNamespaces(String appId, String clusterName) { List<Namespace> namespaces = namespaceRepository.findByAppIdAndClusterNameOrderByIdAsc(appId, clusterName); if (namespaces == null) { return Collections.emptyList(); } return namespaces; } public List<Namespace> findByAppIdAndNamespaceName(String appId, String namespaceName) { return namespaceRepository.findByAppIdAndNamespaceName(appId, namespaceName); } public Namespace findChildNamespace(String appId, String parentClusterName, String namespaceName) { List<Namespace> namespaces = findByAppIdAndNamespaceName(appId, namespaceName); if (CollectionUtils.isEmpty(namespaces) || namespaces.size() == 1) { return null; } List<Cluster> childClusters = clusterService.findChildClusters(appId, parentClusterName); if (CollectionUtils.isEmpty(childClusters)) { return null; } Set<String> childClusterNames = childClusters.stream().map(Cluster::getName).collect(Collectors.toSet()); //the child namespace is the intersection of the child clusters and child namespaces for (Namespace namespace : namespaces) { if (childClusterNames.contains(namespace.getClusterName())) { return namespace; } } return null; } public Namespace findChildNamespace(Namespace parentNamespace) { String appId = parentNamespace.getAppId(); String parentClusterName = parentNamespace.getClusterName(); String namespaceName = parentNamespace.getNamespaceName(); return findChildNamespace(appId, parentClusterName, namespaceName); } public Namespace findParentNamespace(String appId, String clusterName, String namespaceName) { return findParentNamespace(new Namespace(appId, clusterName, namespaceName)); } public Namespace findParentNamespace(Namespace namespace) { String appId = namespace.getAppId(); String namespaceName = namespace.getNamespaceName(); Cluster cluster = clusterService.findOne(appId, namespace.getClusterName()); if (cluster != null && cluster.getParentClusterId() > 0) { Cluster parentCluster = clusterService.findOne(cluster.getParentClusterId()); return findOne(appId, parentCluster.getName(), namespaceName); } return null; } public boolean isChildNamespace(String appId, String clusterName, String namespaceName) { return isChildNamespace(new Namespace(appId, clusterName, namespaceName)); } public boolean isChildNamespace(Namespace namespace) { return findParentNamespace(namespace) != null; } public boolean isNamespaceUnique(String appId, String cluster, String namespace) { Objects.requireNonNull(appId, "AppId must not be null"); Objects.requireNonNull(cluster, "Cluster must not be null"); Objects.requireNonNull(namespace, "Namespace must not be null"); return Objects.isNull( namespaceRepository.findByAppIdAndClusterNameAndNamespaceName(appId, cluster, namespace)); } @Transactional public void deleteByAppIdAndClusterName(String appId, String clusterName, String operator) { List<Namespace> toDeleteNamespaces = findNamespaces(appId, clusterName); for (Namespace namespace : toDeleteNamespaces) { deleteNamespace(namespace, operator); } } @Transactional public Namespace deleteNamespace(Namespace namespace, String operator) { String appId = namespace.getAppId(); String clusterName = namespace.getClusterName(); String namespaceName = namespace.getNamespaceName(); itemService.batchDelete(namespace.getId(), operator); commitService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator); if (!isChildNamespace(namespace)) { releaseService.batchDelete(appId, clusterName, namespace.getNamespaceName(), operator); } //delete child namespace Namespace childNamespace = findChildNamespace(namespace); if (childNamespace != null) { namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, childNamespace.getClusterName(), NamespaceBranchStatus.DELETED, operator); //delete child namespace's releases. Notice: delete child namespace will not delete child namespace's releases releaseService.batchDelete(appId, childNamespace.getClusterName(), namespaceName, operator); } releaseHistoryService.batchDelete(appId, clusterName, namespaceName, operator); instanceService.batchDeleteInstanceConfig(appId, clusterName, namespaceName); namespaceLockService.unlock(namespace.getId()); namespace.setDeleted(true); namespace.setDataChangeLastModifiedBy(operator); auditService.audit(Namespace.class.getSimpleName(), namespace.getId(), Audit.OP.DELETE, operator); return namespaceRepository.save(namespace); } @Transactional public Namespace save(Namespace entity) { if (!isNamespaceUnique(entity.getAppId(), entity.getClusterName(), entity.getNamespaceName())) { throw new ServiceException("namespace not unique"); } entity.setId(0);//protection Namespace namespace = namespaceRepository.save(entity); auditService.audit(Namespace.class.getSimpleName(), namespace.getId(), Audit.OP.INSERT, namespace.getDataChangeCreatedBy()); return namespace; } @Transactional public Namespace update(Namespace namespace) { Namespace managedNamespace = namespaceRepository.findByAppIdAndClusterNameAndNamespaceName( namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName()); BeanUtils.copyEntityProperties(namespace, managedNamespace); managedNamespace = namespaceRepository.save(managedNamespace); auditService.audit(Namespace.class.getSimpleName(), managedNamespace.getId(), Audit.OP.UPDATE, managedNamespace.getDataChangeLastModifiedBy()); return managedNamespace; } @Transactional public void instanceOfAppNamespaces(String appId, String clusterName, String createBy) { List<AppNamespace> appNamespaces = appNamespaceService.findByAppId(appId); for (AppNamespace appNamespace : appNamespaces) { Namespace ns = new Namespace(); ns.setAppId(appId); ns.setClusterName(clusterName); ns.setNamespaceName(appNamespace.getName()); ns.setDataChangeCreatedBy(createBy); ns.setDataChangeLastModifiedBy(createBy); namespaceRepository.save(ns); auditService.audit(Namespace.class.getSimpleName(), ns.getId(), Audit.OP.INSERT, createBy); } } public Map<String, Boolean> namespacePublishInfo(String appId) { List<Cluster> clusters = clusterService.findParentClusters(appId); if (CollectionUtils.isEmpty(clusters)) { throw new BadRequestException("app not exist"); } Map<String, Boolean> clusterHasNotPublishedItems = Maps.newHashMap(); for (Cluster cluster : clusters) { String clusterName = cluster.getName(); List<Namespace> namespaces = findNamespaces(appId, clusterName); for (Namespace namespace : namespaces) { boolean isNamespaceNotPublished = isNamespaceNotPublished(namespace); if (isNamespaceNotPublished) { clusterHasNotPublishedItems.put(clusterName, true); break; } } clusterHasNotPublishedItems.putIfAbsent(clusterName, false); } return clusterHasNotPublishedItems; } private boolean isNamespaceNotPublished(Namespace namespace) { Release latestRelease = releaseService.findLatestActiveRelease(namespace); long namespaceId = namespace.getId(); if (latestRelease == null) { Item lastItem = itemService.findLastOne(namespaceId); return lastItem != null; } Date lastPublishTime = latestRelease.getDataChangeLastModifiedTime(); List<Item> itemsModifiedAfterLastPublish = itemService.findItemsModifiedAfterDate(namespaceId, lastPublishTime); if (CollectionUtils.isEmpty(itemsModifiedAfterLastPublish)) { return false; } Map<String, String> publishedConfiguration = gson.fromJson(latestRelease.getConfigurations(), GsonType.CONFIG); for (Item item : itemsModifiedAfterLastPublish) { if (!Objects.equals(item.getValue(), publishedConfiguration.get(item.getKey()))) { return true; } } return false; } }