package com.ctrip.framework.apollo.adminservice.aop; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.ctrip.framework.apollo.biz.config.BizConfig; 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.service.ItemService; import com.ctrip.framework.apollo.biz.service.NamespaceLockService; import com.ctrip.framework.apollo.biz.service.NamespaceService; import com.ctrip.framework.apollo.biz.service.ReleaseService; import com.ctrip.framework.apollo.common.constants.GsonType; import com.ctrip.framework.apollo.common.dto.ItemChangeSets; import com.ctrip.framework.apollo.common.dto.ItemDTO; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.core.utils.StringUtils; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; /** * unlock namespace if is redo operation. * -------------------------------------------- * For example: If namespace has a item K1 = v1 * -------------------------------------------- * First operate: change k1 = v2 (lock namespace) * Second operate: change k1 = v1 (unlock namespace) */ @Aspect @Component public class NamespaceUnlockAspect { private Gson gson = new Gson(); @Autowired private NamespaceLockService namespaceLockService; @Autowired private NamespaceService namespaceService; @Autowired private ItemService itemService; @Autowired private ReleaseService releaseService; @Autowired private BizConfig bizConfig; //create item @After("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)") public void requireLockAdvice(String appId, String clusterName, String namespaceName, ItemDTO item) { tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName)); } //update item @After("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, itemId, item, ..)") public void requireLockAdvice(String appId, String clusterName, String namespaceName, long itemId, ItemDTO item) { tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName)); } //update by change set @After("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)") public void requireLockAdvice(String appId, String clusterName, String namespaceName, ItemChangeSets changeSet) { tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName)); } //delete item @After("@annotation(PreAcquireNamespaceLock) && args(itemId, operator, ..)") public void requireLockAdvice(long itemId, String operator) { Item item = itemService.findOne(itemId); if (item == null) { throw new BadRequestException("item not exist."); } tryUnlock(namespaceService.findOne(item.getNamespaceId())); } private void tryUnlock(Namespace namespace) { if (bizConfig.isNamespaceLockSwitchOff()) { return; } if (!isModified(namespace)) { namespaceLockService.unlock(namespace.getId()); } } boolean isModified(Namespace namespace) { Release release = releaseService.findLatestActiveRelease(namespace); List<Item> items = itemService.findItemsWithoutOrdered(namespace.getId()); if (release == null) { return hasNormalItems(items); } Map<String, String> releasedConfiguration = gson.fromJson(release.getConfigurations(), GsonType.CONFIG); Map<String, String> configurationFromItems = generateConfigurationFromItems(namespace, items); MapDifference<String, String> difference = Maps.difference(releasedConfiguration, configurationFromItems); return !difference.areEqual(); } private boolean hasNormalItems(List<Item> items) { for (Item item : items) { if (!StringUtils.isEmpty(item.getKey())) { return true; } } return false; } private Map<String, String> generateConfigurationFromItems(Namespace namespace, List<Item> namespaceItems) { Map<String, String> configurationFromItems = Maps.newHashMap(); Namespace parentNamespace = namespaceService.findParentNamespace(namespace); //parent namespace if (parentNamespace == null) { generateMapFromItems(namespaceItems, configurationFromItems); } else {//child namespace Release parentRelease = releaseService.findLatestActiveRelease(parentNamespace); if (parentRelease != null) { configurationFromItems = gson.fromJson(parentRelease.getConfigurations(), GsonType.CONFIG); } generateMapFromItems(namespaceItems, configurationFromItems); } return configurationFromItems; } private Map<String, String> generateMapFromItems(List<Item> items, Map<String, String> configurationFromItems) { for (Item item : items) { String key = item.getKey(); if (StringUtils.isBlank(key)) { continue; } configurationFromItems.put(key, item.getValue()); } return configurationFromItems; } }