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