content: /*
   * Copyright 1999-2019 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
   *
   *      https://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.adapter.gateway.common.api;
  
  import java.util.HashMap;
  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.property.DynamicSentinelProperty;
  import com.alibaba.csp.sentinel.property.PropertyListener;
  import com.alibaba.csp.sentinel.property.SentinelProperty;
  import com.alibaba.csp.sentinel.util.AssertUtil;
  import com.alibaba.csp.sentinel.spi.SpiLoader;
  import com.alibaba.csp.sentinel.util.StringUtil;
  
  /**
   * Manager for gateway API definitions.
   *
   * @author Eric Zhao
   * @since 1.6.0
   */
  public final class GatewayApiDefinitionManager {
  
      private static final Map<String, ApiDefinition> API_MAP = new ConcurrentHashMap<>();
  
      private static final ApiDefinitionPropertyListener LISTENER = new ApiDefinitionPropertyListener();
      private static SentinelProperty<Set<ApiDefinition>> currentProperty = new DynamicSentinelProperty<>();
  
      /**
       * The map keeps all found ApiDefinitionChangeObserver (class name as key).
       */
      private static final Map<String, ApiDefinitionChangeObserver> API_CHANGE_OBSERVERS = new ConcurrentHashMap<>();
  
      static {
          try {
              currentProperty.addListener(LISTENER);
              initializeApiChangeObserverSpi();
          } catch (Throwable ex) {
              RecordLog.warn("[GatewayApiDefinitionManager] Failed to initialize", ex);
              ex.printStackTrace();
          }
      }
  
      private static void initializeApiChangeObserverSpi() {
          List<ApiDefinitionChangeObserver> listeners = SpiLoader.of(ApiDefinitionChangeObserver.class).loadInstanceList();
          for (ApiDefinitionChangeObserver e : listeners) {
              API_CHANGE_OBSERVERS.put(e.getClass().getCanonicalName(), e);
              RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {}"
                  , e.getClass().getCanonicalName());
          }
      }
  
      public static void register2Property(SentinelProperty<Set<ApiDefinition>> property) {
          AssertUtil.notNull(property, "property cannot be null");
          synchronized (LISTENER) {
              RecordLog.info("[GatewayApiDefinitionManager] Registering new property to gateway API definition manager");
              currentProperty.removeListener(LISTENER);
              property.addListener(LISTENER);
              currentProperty = property;
          }
      }
  
      /**
       * Load given gateway API definitions and apply to downstream observers.
       *
       * @param apiDefinitions set of gateway API definitions
       * @return true if updated, or else false
       */
      public static boolean loadApiDefinitions(Set<ApiDefinition> apiDefinitions) {
          return currentProperty.updateValue(apiDefinitions);
      }
  
      public static ApiDefinition getApiDefinition(final String apiName) {
          if (apiName == null) {
              return null;
          }
          return API_MAP.get(apiName);
      }
  
      public static Set<ApiDefinition> getApiDefinitions() {
          return new HashSet<>(API_MAP.values());
      }
  
      private static final class ApiDefinitionPropertyListener implements PropertyListener<Set<ApiDefinition>> {
  
          @Override
          public void configUpdate(Set<ApiDefinition> set) {
              applyApiUpdateInternal(set);
              RecordLog.info("[GatewayApiDefinitionManager] Api definition updated: {}", API_MAP);
          }
  
          @Override
          public void configLoad(Set<ApiDefinition> set) {
              applyApiUpdateInternal(set);
              RecordLog.info("[GatewayApiDefinitionManager] Api definition loaded: {}", API_MAP);
          }
  
          private static synchronized void applyApiUpdateInternal(Set<ApiDefinition> set) {
              if (set == null || set.isEmpty()) {
                  API_MAP.clear();
                  notifyDownstreamListeners(new HashSet<ApiDefinition>());
                  return;
              }
              Map<String, ApiDefinition> map = new HashMap<>(set.size());
              Set<ApiDefinition> validSet = new HashSet<>();
              for (ApiDefinition definition : set) {
                  if (isValidApi(definition)) {
                      map.put(definition.getApiName(), definition);
                      validSet.add(definition);
                  }
              }
  
              API_MAP.clear();
              API_MAP.putAll(map);
  
              // propagate to downstream.
              notifyDownstreamListeners(validSet);
          }
      }
  
      private static void notifyDownstreamListeners(/*@Valid*/ final Set<ApiDefinition> definitions) {
          try {
              for (Map.Entry<?, ApiDefinitionChangeObserver> entry : API_CHANGE_OBSERVERS.entrySet()) {
                  entry.getValue().onChange(definitions);
              }
          } catch (Exception ex) {
              RecordLog.warn("[GatewayApiDefinitionManager] WARN: failed to notify downstream api listeners", ex);
          }
      }
  
      public static boolean isValidApi(ApiDefinition apiDefinition) {
          return apiDefinition != null && StringUtil.isNotBlank(apiDefinition.getApiName())
              && apiDefinition.getPredicateItems() != null;
      }
  
      static void addApiChangeListener(ApiDefinitionChangeObserver listener) {
          AssertUtil.notNull(listener, "listener cannot be null");
          API_CHANGE_OBSERVERS.put(listener.getClass().getCanonicalName(), listener);
      }
  
      static void removeApiChangeListener(Class<?> clazz) {
          AssertUtil.notNull(clazz, "class cannot be null");
          API_CHANGE_OBSERVERS.remove(clazz.getCanonicalName());
      }
  }
  