/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.slots.block.authority;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.csp.sentinel.log.RecordLog;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.csp.sentinel.property.DynamicSentinelProperty;
import com.alibaba.csp.sentinel.property.PropertyListener;
import com.alibaba.csp.sentinel.property.SentinelProperty;

/**
 * Manager for authority rules.
 *
 * @author youji.zj
 * @author jialiang.linjl
 * @author Eric Zhao
 */
public final class AuthorityRuleManager {

    private static Map<String, Set<AuthorityRule>> authorityRules = new ConcurrentHashMap<>();

    private static final RulePropertyListener LISTENER = new RulePropertyListener();
    private static SentinelProperty<List<AuthorityRule>> currentProperty = new DynamicSentinelProperty<>();

    static {
        currentProperty.addListener(LISTENER);
    }

    public static void register2Property(SentinelProperty<List<AuthorityRule>> property) {
        AssertUtil.notNull(property, "property cannot be null");
        synchronized (LISTENER) {
            if (currentProperty != null) {
                currentProperty.removeListener(LISTENER);
            }
            property.addListener(LISTENER);
            currentProperty = property;
            RecordLog.info("[AuthorityRuleManager] Registering new property to authority rule manager");
        }
    }

    /**
     * Load the authority rules to memory.
     *
     * @param rules list of authority rules
     */
    public static void loadRules(List<AuthorityRule> rules) {
        currentProperty.updateValue(rules);
    }

    public static boolean hasConfig(String resource) {
        return authorityRules.containsKey(resource);
    }

    /**
     * Get a copy of the rules.
     *
     * @return a new copy of the rules.
     */
    public static List<AuthorityRule> getRules() {
        List<AuthorityRule> rules = new ArrayList<>();
        if (authorityRules == null) {
            return rules;
        }
        for (Map.Entry<String, Set<AuthorityRule>> entry : authorityRules.entrySet()) {
            rules.addAll(entry.getValue());
        }
        return rules;
    }

    private static class RulePropertyListener implements PropertyListener<List<AuthorityRule>> {

        @Override
        public void configUpdate(List<AuthorityRule> conf) {
            Map<String, Set<AuthorityRule>> rules = loadAuthorityConf(conf);

            authorityRules.clear();
            if (rules != null) {
                authorityRules.putAll(rules);
            }
            RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules);
        }

        private Map<String, Set<AuthorityRule>> loadAuthorityConf(List<AuthorityRule> list) {
            Map<String, Set<AuthorityRule>> newRuleMap = new ConcurrentHashMap<>();

            if (list == null || list.isEmpty()) {
                return newRuleMap;
            }

            for (AuthorityRule rule : list) {
                if (!isValidRule(rule)) {
                    RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule);
                    continue;
                }

                if (StringUtil.isBlank(rule.getLimitApp())) {
                    rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT);
                }

                String identity = rule.getResource();
                Set<AuthorityRule> ruleSet = newRuleMap.get(identity);
                // putIfAbsent
                if (ruleSet == null) {
                    ruleSet = new HashSet<>();
                    ruleSet.add(rule);
                    newRuleMap.put(identity, ruleSet);
                } else {
                    // One resource should only have at most one authority rule, so just ignore redundant rules.
                    RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString());
                }
            }

            return newRuleMap;
        }

        @Override
        public void configLoad(List<AuthorityRule> value) {
            Map<String, Set<AuthorityRule>> rules = loadAuthorityConf(value);

            authorityRules.clear();
            if (rules != null) {
                authorityRules.putAll(rules);
            }
            RecordLog.info("[AuthorityRuleManager] Load authority rules: {}", authorityRules);
        }
    }

    static Map<String, Set<AuthorityRule>> getAuthorityRules() {
        return authorityRules;
    }

    public static boolean isValidRule(AuthorityRule rule) {
        return rule != null && !StringUtil.isBlank(rule.getResource())
            && rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp());
    }
}

@@ -1387,32 +1387,41 @@
  private static 
+volatile 
 Map%3CString, Set%3C
@@ -3160,32 +3160,45 @@
 %0A        public 
+synchronized 
 void configUpdat
@@ -3196,14 +3196,12 @@
 nfig
-Update
+Load
 (Lis
@@ -3217,20 +3217,21 @@
 tyRule%3E 
-conf
+value
 ) %7B%0A    
@@ -3238,33 +3238,17 @@
         
-Map%3CString, Set%3CA
+a
 uthority
@@ -3243,39 +3243,32 @@
    authorityRule
-%3E%3E rule
 s = loadAuthorit
@@ -3273,20 +3273,21 @@
 ityConf(
-conf
+value
 );%0A%0A    
@@ -3286,32 +3286,100 @@
 );%0A%0A            
+RecordLog.info(%22%5BAuthorityRuleManager%5D Authority rules loaded: %7B%7D%22, 
 authorityRules.c
@@ -3368,39 +3368,32 @@
 , authorityRules
-.clear(
 );%0A            i
@@ -3391,37 +3391,103 @@
     
+%7D%0A%0A
     
-if (rules != null) %7B%0A    
+    @Override%0A        public synchronized void configUpdate(List%3CAuthorityRule%3E conf) %7B%0A
     
@@ -3504,37 +3504,49 @@
 thorityRules
-.putAll(rules
+ = loadAuthorityConf(conf
 );%0A         
@@ -3540,33 +3540,32 @@
 f);%0A            
-%7D
 %0A            Rec
@@ -5013,399 +5013,8 @@
  %7D%0A%0A
-        @Override%0A        public void configLoad(List%3CAuthorityRule%3E value) %7B%0A            Map%3CString, Set%3CAuthorityRule%3E%3E rules = loadAuthorityConf(value);%0A%0A            authorityRules.clear();%0A            if (rules != null) %7B%0A                authorityRules.putAll(rules);%0A            %7D%0A            RecordLog.info(%22%5BAuthorityRuleManager%5D Load authority rules: %7B%7D%22, authorityRules);%0A        %7D%0A
     

Improve AuthorityRuleManager: replace the rule map instead of clear-then-insert when updating rules (#2655)