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;
}
}