content: package com.ctrip.framework.apollo.adminservice.aop;
  
  
  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.NamespaceLock;
  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.common.dto.ItemChangeSets;
  import com.ctrip.framework.apollo.common.dto.ItemDTO;
  import com.ctrip.framework.apollo.common.exception.BadRequestException;
  import com.ctrip.framework.apollo.common.exception.ServiceException;
  
  import org.aspectj.lang.annotation.Aspect;
  import org.aspectj.lang.annotation.Before;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.dao.DataIntegrityViolationException;
  import org.springframework.stereotype.Component;
  
  
  /**
   * 一个namespace在一次发布中只能允许一个人修改配置
   * 通过数据库lock表来实现
   */
  @Aspect
  @Component
  public class NamespaceAcquireLockAspect {
    private static final Logger logger = LoggerFactory.getLogger(NamespaceAcquireLockAspect.class);
  
  
    @Autowired
    private NamespaceLockService namespaceLockService;
    @Autowired
    private NamespaceService namespaceService;
    @Autowired
    private ItemService itemService;
    @Autowired
    private BizConfig bizConfig;
  
  
    //create item
    @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)")
    public void requireLockAdvice(String appId, String clusterName, String namespaceName,
                                  ItemDTO item) {
      acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
    }
  
    //update item
    @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, itemId, item, ..)")
    public void requireLockAdvice(String appId, String clusterName, String namespaceName, long itemId,
                                  ItemDTO item) {
      acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
    }
  
    //update by change set
    @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)")
    public void requireLockAdvice(String appId, String clusterName, String namespaceName,
                                  ItemChangeSets changeSet) {
      acquireLock(appId, clusterName, namespaceName, changeSet.getDataChangeLastModifiedBy());
    }
  
    //delete item
    @Before("@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.");
      }
      acquireLock(item.getNamespaceId(), operator);
    }
  
    void acquireLock(String appId, String clusterName, String namespaceName,
                             String currentUser) {
      if (bizConfig.isNamespaceLockSwitchOff()) {
        return;
      }
  
      Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
  
      acquireLock(namespace, currentUser);
    }
  
    void acquireLock(long namespaceId, String currentUser) {
      if (bizConfig.isNamespaceLockSwitchOff()) {
        return;
      }
  
      Namespace namespace = namespaceService.findOne(namespaceId);
  
      acquireLock(namespace, currentUser);
  
    }
  
    private void acquireLock(Namespace namespace, String currentUser) {
      if (namespace == null) {
        throw new BadRequestException("namespace not exist.");
      }
  
      long namespaceId = namespace.getId();
  
      NamespaceLock namespaceLock = namespaceLockService.findLock(namespaceId);
      if (namespaceLock == null) {
        try {
          tryLock(namespaceId, currentUser);
          //lock success
        } catch (DataIntegrityViolationException e) {
          //lock fail
          namespaceLock = namespaceLockService.findLock(namespaceId);
          checkLock(namespace, namespaceLock, currentUser);
        } catch (Exception e) {
          logger.error("try lock error", e);
          throw e;
        }
      } else {
        //check lock owner is current user
        checkLock(namespace, namespaceLock, currentUser);
      }
    }
  
    private void tryLock(long namespaceId, String user) {
      NamespaceLock lock = new NamespaceLock();
      lock.setNamespaceId(namespaceId);
      lock.setDataChangeCreatedBy(user);
      lock.setDataChangeLastModifiedBy(user);
      namespaceLockService.tryLock(lock);
    }
  
    private void checkLock(Namespace namespace, NamespaceLock namespaceLock,
                           String currentUser) {
      if (namespaceLock == null) {
        throw new ServiceException(
            String.format("Check lock for %s failed, please retry.", namespace.getNamespaceName()));
      }
  
      String lockOwner = namespaceLock.getDataChangeCreatedBy();
      if (!lockOwner.equals(currentUser)) {
        throw new BadRequestException(
            "namespace:" + namespace.getNamespaceName() + " is modified by " + lockOwner);
      }
    }
  
  
  }
  