content: /*
   * 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;
  
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.Map;
  
  import com.alibaba.csp.sentinel.log.RecordLog;
  import com.alibaba.csp.sentinel.context.Context;
  import com.alibaba.csp.sentinel.context.ContextUtil;
  import com.alibaba.csp.sentinel.context.NullContext;
  import com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper;
  import com.alibaba.csp.sentinel.slotchain.ProcessorSlot;
  import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
  import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
  import com.alibaba.csp.sentinel.slotchain.SlotChainProvider;
  import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper;
  import com.alibaba.csp.sentinel.slots.block.BlockException;
  import com.alibaba.csp.sentinel.slots.block.Rule;
  
  /**
   * {@inheritDoc}
   *
   * @author jialiang.linjl
   * @author leyou(lihao)
   * @author Eric Zhao
   * @see Sph
   */
  public class CtSph implements Sph {
  
      private static final Object[] OBJECTS0 = new Object[0];
  
      /**
       * Same resource({@link ResourceWrapper#equals(Object)}) will share the same
       * {@link ProcessorSlotChain}, no matter in which {@link Context}.
       */
      private static volatile Map<ResourceWrapper, ProcessorSlotChain> chainMap
          = new HashMap<ResourceWrapper, ProcessorSlotChain>();
  
      private static final Object LOCK = new Object();
  
      private AsyncEntry asyncEntryWithNoChain(ResourceWrapper resourceWrapper, Context context) {
          AsyncEntry entry = new AsyncEntry(resourceWrapper, null, context);
          entry.initAsyncContext();
          // The async entry will be removed from current context as soon as it has been created.
          entry.cleanCurrentEntryInLocal();
          return entry;
      }
  
      private AsyncEntry asyncEntryWithPriorityInternal(ResourceWrapper resourceWrapper, int count, boolean prioritized,
                                                        Object... args) throws BlockException {
          Context context = ContextUtil.getContext();
          if (context instanceof NullContext) {
              // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
              // so here init the entry only. No rule checking will be done.
              return asyncEntryWithNoChain(resourceWrapper, context);
          }
          if (context == null) {
              // Using default context.
              context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
          }
  
          // Global switch is turned off, so no rule checking will be done.
          if (!Constants.ON) {
              return asyncEntryWithNoChain(resourceWrapper, context);
          }
  
          ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
  
          // Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no rule checking will be done.
          if (chain == null) {
              return asyncEntryWithNoChain(resourceWrapper, context);
          }
  
          AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context, count, args);
          try {
              chain.entry(context, resourceWrapper, null, count, prioritized, args);
              // Initiate the async context only when the entry successfully passed the slot chain.
              asyncEntry.initAsyncContext();
              // The asynchronous call may take time in background, and current context should not be hanged on it.
              // So we need to remove current async entry from current context.
              asyncEntry.cleanCurrentEntryInLocal();
          } catch (BlockException e1) {
              // When blocked, the async entry will be exited on current context.
              // The async context will not be initialized.
              asyncEntry.exitForContext(context, count, args);
              throw e1;
          } catch (Throwable e1) {
              // This should not happen, unless there are errors existing in Sentinel internal.
              // When this happens, async context is not initialized.
              RecordLog.warn("Sentinel unexpected exception in asyncEntryInternal", e1);
  
              asyncEntry.cleanCurrentEntryInLocal();
          }
          return asyncEntry;
      }
  
      private AsyncEntry asyncEntryInternal(ResourceWrapper resourceWrapper, int count, Object... args)
          throws BlockException {
          return asyncEntryWithPriorityInternal(resourceWrapper, count, false, args);
      }
  
      private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args)
          throws BlockException {
          Context context = ContextUtil.getContext();
          if (context instanceof NullContext) {
              // The {@link NullContext} indicates that the amount of context has exceeded the threshold,
              // so here init the entry only. No rule checking will be done.
              return new CtEntry(resourceWrapper, null, context);
          }
  
          if (context == null) {
              // Using default context.
              context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME);
          }
  
          // Global switch is close, no rule checking will do.
          if (!Constants.ON) {
              return new CtEntry(resourceWrapper, null, context);
          }
  
          ProcessorSlot<Object> chain = lookProcessChain(resourceWrapper);
  
          /*
           * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE},
           * so no rule checking will be done.
           */
          if (chain == null) {
              return new CtEntry(resourceWrapper, null, context);
          }
  
          Entry e = new CtEntry(resourceWrapper, chain, context, count, args);
          try {
              chain.entry(context, resourceWrapper, null, count, prioritized, args);
          } catch (BlockException e1) {
              e.exit(count, args);
              throw e1;
          } catch (Throwable e1) {
              // This should not happen, unless there are errors existing in Sentinel internal.
              RecordLog.info("Sentinel unexpected exception", e1);
          }
          return e;
      }
  
      /**
       * Do all {@link Rule}s checking about the resource.
       *
       * <p>Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use
       * same {@link ProcessorSlot} globally. </p>
       *
       * <p>Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
       * otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking
       * or exception.</p>
       *
       * @param resourceWrapper resource name
       * @param count           tokens needed
       * @param args            arguments of user method call
       * @return {@link Entry} represents this call
       * @throws BlockException if any rule's threshold is exceeded
       */
      public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException {
          return entryWithPriority(resourceWrapper, count, false, args);
      }
  
      /**
       * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will
       * be created if the resource doesn't relate one.
       *
       * <p>Same resource({@link ResourceWrapper#equals(Object)}) will share the same
       * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.<p/>
       *
       * <p>
       * Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE},
       * otherwise null will return.
       * </p>
       *
       * @param resourceWrapper target resource
       * @return {@link ProcessorSlotChain} of the resource
       */
      ProcessorSlot<Object> lookProcessChain(ResourceWrapper resourceWrapper) {
          ProcessorSlotChain chain = chainMap.get(resourceWrapper);
          if (chain == null) {
              synchronized (LOCK) {
                  chain = chainMap.get(resourceWrapper);
                  if (chain == null) {
                      // Entry size limit.
                      if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) {
                          return null;
                      }
  
                      chain = SlotChainProvider.newSlotChain();
                      Map<ResourceWrapper, ProcessorSlotChain> newMap = new HashMap<ResourceWrapper, ProcessorSlotChain>(
                          chainMap.size() + 1);
                      newMap.putAll(chainMap);
                      newMap.put(resourceWrapper, chain);
                      chainMap = newMap;
                  }
              }
          }
          return chain;
      }
  
      /**
       * Get current size of created slot chains.
       *
       * @return size of created slot chains
       * @since 0.2.0
       */
      public static int entrySize() {
          return chainMap.size();
      }
  
      /**
       * Reset the slot chain map. Only for internal test.
       *
       * @since 0.2.0
       */
      static void resetChainMap() {
          chainMap.clear();
      }
  
      /**
       * Only for internal test.
       *
       * @since 0.2.0
       */
      static Map<ResourceWrapper, ProcessorSlotChain> getChainMap() {
          return chainMap;
      }
  
      /**
       * This class is used for skip context name checking.
       */
      private final static class InternalContextUtil extends ContextUtil {
          static Context internalEnter(String name) {
              return trueEnter(name, "");
          }
  
          static Context internalEnter(String name, String origin) {
              return trueEnter(name, origin);
          }
      }
  
      @Override
      public Entry entry(String name) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
          return entry(resource, 1, OBJECTS0);
      }
  
      @Override
      public Entry entry(Method method) throws BlockException {
          MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
          return entry(resource, 1, OBJECTS0);
      }
  
      @Override
      public Entry entry(Method method, EntryType type) throws BlockException {
          MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
          return entry(resource, 1, OBJECTS0);
      }
  
      @Override
      public Entry entry(String name, EntryType type) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, type);
          return entry(resource, 1, OBJECTS0);
      }
  
      @Override
      public Entry entry(Method method, EntryType type, int count) throws BlockException {
          MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
          return entry(resource, count, OBJECTS0);
      }
  
      @Override
      public Entry entry(String name, EntryType type, int count) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, type);
          return entry(resource, count, OBJECTS0);
      }
  
      @Override
      public Entry entry(Method method, int count) throws BlockException {
          MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT);
          return entry(resource, count, OBJECTS0);
      }
  
      @Override
      public Entry entry(String name, int count) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT);
          return entry(resource, count, OBJECTS0);
      }
  
      @Override
      public Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException {
          MethodResourceWrapper resource = new MethodResourceWrapper(method, type);
          return entry(resource, count, args);
      }
  
      @Override
      public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, type);
          return entry(resource, count, args);
      }
  
      @Override
      public AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, type);
          return asyncEntryInternal(resource, count, args);
      }
  
      @Override
      public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, type);
          return entryWithPriority(resource, count, prioritized);
      }
  
      @Override
      public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized, Object... args)
          throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, type);
          return entryWithPriority(resource, count, prioritized, args);
      }
  
      @Override
      public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args)
          throws BlockException {
          return entryWithType(name, resourceType, entryType, count, false, args);
      }
  
      @Override
      public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized,
                                 Object[] args) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
          return entryWithPriority(resource, count, prioritized, args);
      }
  
      @Override
      public AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType, int count,
                                           boolean prioritized, Object[] args) throws BlockException {
          StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType);
          return asyncEntryWithPriorityInternal(resource, count, prioritized, args);
      }
  }
  