From 4098297e51832b00ac839fe28e199f296a48868b Mon Sep 17 00:00:00 2001
From: Daydreamer-ia <2296032269@qq.com>
Date: Sun, 28 Aug 2022 17:38:08 +0800
Subject: [PATCH] Add the base implements for ability control.
---
.../AbstractAbilityControlManager.java | 401 ++++++++++++++++++
.../ability/DefaultAbilityControlManager.java | 343 +++++++++++++++
.../ability/discover/AbilityHandleLoader.java | 49 +++
.../discover/NacosAbilityManagerHolder.java | 92 ++++
4 files changed, 885 insertions(+)
create mode 100644 common/src/main/java/com/alibaba/nacos/common/ability/AbstractAbilityControlManager.java
create mode 100644 common/src/main/java/com/alibaba/nacos/common/ability/DefaultAbilityControlManager.java
create mode 100644 common/src/main/java/com/alibaba/nacos/common/ability/discover/AbilityHandleLoader.java
create mode 100644 common/src/main/java/com/alibaba/nacos/common/ability/discover/NacosAbilityManagerHolder.java
diff --git a/common/src/main/java/com/alibaba/nacos/common/ability/AbstractAbilityControlManager.java b/common/src/main/java/com/alibaba/nacos/common/ability/AbstractAbilityControlManager.java
new file mode 100644
index 000000000..f78fd0a20
--- /dev/null
+++ b/common/src/main/java/com/alibaba/nacos/common/ability/AbstractAbilityControlManager.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 1999-2022 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.nacos.common.ability;
+
+import com.alibaba.nacos.api.ability.constant.AbilityKey;
+import com.alibaba.nacos.api.ability.constant.AbilityStatus;
+import com.alibaba.nacos.api.ability.entity.AbilityTable;
+import com.alibaba.nacos.common.ability.handler.AbilityHandlePreProcessor;
+import com.alibaba.nacos.common.ability.inter.TraceableAbilityControlManager;
+import com.alibaba.nacos.common.notify.Event;
+import com.alibaba.nacos.common.notify.NotifyCenter;
+import com.alibaba.nacos.common.utils.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**.
+ * @author Daydreamer
+ * @description Base class for ability control. It can only be used internally by Nacos.It showld be sington.
+ * @date 2022/7/12 19:18
+ **/
+@SuppressWarnings("all")
+public abstract class AbstractAbilityControlManager implements TraceableAbilityControlManager {
+
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAbilityControlManager.class);
+
+ /**
+ * Abilities current supporting
+ *
+ * key: ability key from {@link AbilityKey}
+ * value: whether to turn on
+ */
+ protected final Map currentRunningAbility = new ConcurrentHashMap<>();
+
+ /**
+ * Ability table collections
+ *
+ * key: connectionId
+ * value: AbilityTable
+ */
+ protected final Map nodeAbilityTable = new ConcurrentHashMap<>();
+
+ /**.
+ * These handlers will be invoke before combine the ability table
+ */
+ private final List abilityHandlePreProcessors = new ArrayList<>();
+
+ /**
+ * This map is used to trace the status of ability table.
+ * Its status should be update after {@link #addNewTable(AbilityTable)} and {@link #removeTable(String)}
+ */
+ protected final Map> abilityStatus = new ConcurrentHashMap<>();
+
+ private final ReentrantLock lockForProcessors = new ReentrantLock();
+
+ private final ReentrantLock lockForAbilityTable = new ReentrantLock();
+
+ protected AbstractAbilityControlManager() {
+ // register events
+ registerAbilityEvent();
+ // put abilities
+ currentRunningAbility.putAll(AbilityKey.getCurrentNodeSupportAbility());
+ // initialize
+ init();
+ }
+
+
+ private void registerAbilityEvent(){
+ // register events
+ NotifyCenter.registerToPublisher(AbilityComeEvent.class, 16384);
+ NotifyCenter.registerToPublisher(AbilityExpiredEvent.class, 16384);
+ }
+
+ /**
+ * Whether the ability current node supporting is running. Return false if current node doesn't support.
+ *
+ * @param abilityKey ability key
+ * @return is running
+ */
+ @Override
+ public boolean isCurrentNodeAbilityRunning(String abilityKey) {
+ return currentRunningAbility.getOrDefault(abilityKey, false);
+ }
+
+ /**
+ * Register a new ability table.
+ *
+ * @param table the ability table.
+ */
+ @Override
+ public final void addNewTable(AbilityTable table) {
+ // id should not be null
+ String connectionId = table.getConnectionId();
+ // if exists
+ if (contains(connectionId) || connectionId == null) {
+ return;
+ }
+ lockForAbilityTable.lock();
+ try {
+ // check
+ if (contains(connectionId)) {
+ return;
+ }
+ // update status
+ abilityStatus.put(connectionId, new AtomicReference<>(AbilityStatus.INITIALIZING));
+ // handle ability table before joining current node
+ AbilityTable processed = process(table);
+ // hook method
+ add(processed);
+ // add to node
+ nodeAbilityTable.put(connectionId, table);
+ } finally {
+ lockForAbilityTable.unlock();
+ }
+ // update status
+ AtomicReference abilityStatusAtomicReference = abilityStatus.get(table.getConnectionId());
+ if (abilityStatusAtomicReference != null) {
+ // try one time
+ // do nothing if AbilityStatus == Expired
+ // if ready
+ if(abilityStatusAtomicReference.compareAndSet(AbilityStatus.INITIALIZING, AbilityStatus.READY)) {
+ // publish event to subscriber
+ AbilityComeEvent updateEvent = new AbilityComeEvent();
+ updateEvent.setConnectionId(table.getConnectionId());
+ updateEvent.setTable(table);
+ NotifyCenter.publishEvent(updateEvent);
+ }
+ } else {
+ LOGGER.warn("[AbiityControlManager] Cannot get connection status after processing ability table, possible reason is that the network is unstable");
+ }
+ }
+
+ /**
+ * Remove a ability table
+ *
+ * @param connectionId the ability table which is removing.
+ */
+ @Override
+ public final void removeTable(String connectionId) {
+ // if not exists
+ if(connectionId == null || !nodeAbilityTable.containsKey(connectionId)){
+ return;
+ }
+ AbilityTable removingTable = null;
+ lockForAbilityTable.lock();
+ try {
+ // check
+ if (!nodeAbilityTable.containsKey(connectionId)) {
+ return;
+ }
+ nodeAbilityTable.get(connectionId);
+ // update status
+ abilityStatus.computeIfPresent(connectionId, (k, v) -> {
+ v.set(AbilityStatus.EXPIRED);
+ return v;
+ });
+ // hook method
+ remove(connectionId);
+ // remove
+ nodeAbilityTable.remove(connectionId);
+ } finally {
+ lockForAbilityTable.unlock();
+ }
+ // remove status
+ abilityStatus.remove(connectionId);
+ // publish event
+ if (removingTable != null) {
+ AbilityExpiredEvent expiredEvent = new AbilityExpiredEvent();
+ expiredEvent.setTable(removingTable);
+ expiredEvent.setConnectionId(connectionId);
+ NotifyCenter.publishEvent(expiredEvent);
+ }
+ }
+
+
+ /**
+ * Register a new ability table. This is a ThreadSafe method for {@link AbstractAbilityControlManager#remove(String)}.
+ *
+ * @param table the ability table.
+ */
+ protected abstract void add(AbilityTable table);
+
+
+ /**
+ * Remove a ability table. This is a ThreadSafe method for {@link AbstractAbilityControlManager#add(AbilityTable)}.
+ *
+ * @param connectionId the ability table which is removing.
+ */
+ protected abstract void remove(String connectionId);
+
+
+ /**
+ * wthether contains this ability table
+ *
+ * @return
+ */
+ @Override
+ public boolean contains(String connectionId) {
+ return nodeAbilityTable.containsKey(connectionId);
+ }
+
+
+ /**
+ * Get the status of the ability table.
+ *
+ * @param connectionId connection id
+ * @return status of ability table {@link AbilityStatus}
+ */
+ @Override
+ public AbilityStatus trace(String connectionId) {
+ if (connectionId == null) {
+ return AbilityStatus.NOT_EXIST;
+ }
+ return abilityStatus.getOrDefault(connectionId, new AtomicReference<>(AbilityStatus.NOT_EXIST)).get();
+ }
+
+ /**
+ * Trace the status of connection if {@link AbilityStatus#INITIALIZING}
, wake up if {@link AbilityStatus#READY}
+ * It will return if status is {@link AbilityStatus#EXPIRED}
or {@link AbilityStatus#NOT_EXIST}
+ *
+ * @param connectionId connection id
+ * @param source source status
+ * @param target target status
+ * @return if success
+ */
+ @Override
+ public boolean traceReadySyn(String connectionId) {
+ AbilityStatus source = AbilityStatus.INITIALIZING;
+ AbilityStatus target = AbilityStatus.READY;
+ AtomicReference atomicReference = abilityStatus.get(connectionId);
+ // return if null
+ if (atomicReference == null || atomicReference.get().equals(AbilityStatus.EXPIRED)) {
+ return false;
+ } else if (target == atomicReference.get()) {
+ return true;
+ }
+ // try if status legal
+ while (!atomicReference.get().equals(target) && atomicReference.get().equals(source)) {
+ LockSupport.parkNanos(100L);
+ // if expired
+ if (atomicReference.get().equals(AbilityStatus.EXPIRED)) {
+ return false;
+ }
+ }
+ return atomicReference.get().equals(target);
+ }
+
+ /**.
+ * Invoking {@link AbilityHandlePreProcessor}
+ *
+ * @param source source ability table
+ * @return result
+ */
+ protected AbilityTable process(AbilityTable source) {
+ // do nothing if no processor
+ if (CollectionUtils.isEmpty(abilityHandlePreProcessors)) {
+ return source;
+ }
+ // copy to advoid error process
+ AbilityTable abilityTable = source;
+ AbilityTable copy = new AbilityTable(source.getConnectionId(), new HashMap<>(source.getAbility()), source.isServer(), source.getVersion());
+ for (AbilityHandlePreProcessor handler : abilityHandlePreProcessors) {
+ try {
+ abilityTable = handler.handle(abilityTable);
+ } catch (Throwable t) {
+ LOGGER.warn("[AbilityHandlePostProcessor] Failed to invoke {} :{}",
+ handler.getClass().getSimpleName(), t.getLocalizedMessage());
+ // ensure normal operation
+ abilityTable = copy;
+ }
+ }
+ return abilityTable;
+ }
+
+ /**.
+ * They will be invoked before updating ability table, but the order in which
+ * they are called cannot be guaranteed
+ *
+ * @param postProcessor PostProcessor instance
+ */
+ @Override
+ public void addPostProcessor(AbilityHandlePreProcessor postProcessor) {
+ lockForProcessors.lock();
+ try {
+ abilityHandlePreProcessors.add(postProcessor);
+ } finally {
+ lockForProcessors.unlock();
+ LOGGER.info("[AbilityHandlePostProcessor] registry handler: {}",
+ postProcessor.getClass().getSimpleName());
+ }
+ }
+
+ /**
+ * Initialize the manager
+ */
+ @Override
+ public void init() {
+ // default init
+ // nothing to do
+ }
+
+ /**
+ * It should be invoked before destroy
+ */
+ @Override
+ public void destroy() {
+ // default destroy
+ // nothing to do
+ }
+
+ /**
+ * Return ability table of current node
+ *
+ * @return ability table
+ */
+ @Override
+ public Map getCurrentRunningAbility() {
+ return new HashMap<>(this.currentRunningAbility);
+ }
+
+ /**
+ * base class for ability
+ */
+ public abstract class AbilityEvent extends Event {
+
+ private static final long serialVersionUID = -123241121302761L;
+
+ protected AbilityEvent(){}
+
+ /**
+ * connection id.
+ */
+ private String connectionId;
+
+ /**
+ * ability table
+ */
+ private AbilityTable table;
+
+
+ public String getConnectionId() {
+ return connectionId;
+ }
+
+ public void setConnectionId(String connectionId) {
+ this.connectionId = connectionId;
+ }
+
+ public AbilityTable getTable() {
+ return table;
+ }
+
+ public void setTable(AbilityTable table) {
+ this.table = table;
+ }
+ }
+
+ /**
+ * when a connection connected.
+ */
+ public class AbilityComeEvent extends AbilityEvent {
+
+ private static final long serialVersionUID = -123241121302761L;
+
+ private AbilityComeEvent(){}
+ }
+
+ /**
+ * when a connection disconnected.
+ */
+ public class AbilityExpiredEvent extends AbilityEvent {
+
+ private static final long serialVersionUID = -123241121212127619L;
+
+ private AbilityExpiredEvent(){}
+
+ }
+}
diff --git a/common/src/main/java/com/alibaba/nacos/common/ability/DefaultAbilityControlManager.java b/common/src/main/java/com/alibaba/nacos/common/ability/DefaultAbilityControlManager.java
new file mode 100644
index 000000000..b3012392e
--- /dev/null
+++ b/common/src/main/java/com/alibaba/nacos/common/ability/DefaultAbilityControlManager.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 1999-2022 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.nacos.common.ability;
+
+import com.alibaba.nacos.api.ability.constant.AbilityKey;
+import com.alibaba.nacos.api.ability.entity.AbilityTable;
+import com.alibaba.nacos.common.JustForTest;
+import com.alibaba.nacos.common.ability.handler.HandlerMapping;
+import com.alibaba.nacos.common.ability.inter.AbilityHandlerRegistry;
+import com.alibaba.nacos.common.executor.ExecutorFactory;
+import com.alibaba.nacos.common.notify.NotifyCenter;
+import com.alibaba.nacos.common.utils.CollectionUtils;
+import com.alibaba.nacos.common.utils.MapUtil;
+import com.alibaba.nacos.common.utils.ThreadUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**.
+ * @author Daydreamer
+ * @description It is a relatively complete capability control center implementation.
+ * @date 2022/7/12 19:18
+ **/
+public abstract class DefaultAbilityControlManager extends AbstractAbilityControlManager
+ implements AbilityHandlerRegistry {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAbilityControlManager.class);
+
+ /**
+ * . These handlers will be invoked when the flag of ability change key: ability key from {@link AbilityKey} value:
+ * componments who want to be invoked if its interested ability turn on/off
+ */
+ private final Map> handlerMappings = new ConcurrentHashMap<>();
+
+ /**.
+ * run for HandlerMapping
+ */
+ private final Executor simpleThreadPool = ExecutorFactory.newSingleExecutorService();
+
+ private final ReentrantLock lockForHandlerMappings = new ReentrantLock();
+
+ protected DefaultAbilityControlManager() {
+ ThreadUtils.addShutdownHook(this::destroy);
+ NotifyCenter.registerToPublisher(AbilityUpdateEvent.class, 16384);
+ }
+
+ /**
+ * . Turn on the ability whose key is abilityKey
+ *
+ * @param abilityKey ability key
+ * @return if turn success
+ */
+ @Override
+ public boolean enableCurrentNodeAbility(String abilityKey) {
+ return doTurn(true, abilityKey);
+ }
+
+ /**
+ * . Turn off the ability whose key is abilityKey
+ *
+ * @param abilityKey ability key
+ * @return if turn success
+ */
+ @Override
+ public boolean disableCurrentNodeAbility(String abilityKey) {
+ return doTurn(false, abilityKey);
+ }
+
+ /**
+ * . Turn on/off the ability of current node
+ *
+ * @param isOn is on
+ * @param abilityKey ability key from {@link AbilityKey}
+ * @return if turn success
+ */
+ private boolean doTurn(boolean isOn, String abilityKey) {
+ Boolean isEnabled = currentRunningAbility.get(abilityKey);
+ // if not supporting this key
+ if (isEnabled == null) {
+ LOGGER.warn("[AbilityControlManager] Attempt to turn on/off a not existed ability!");
+ return false;
+ } else if (isOn == isEnabled) {
+ // if already turn on/off
+ return true;
+ }
+ // turn on/off
+ currentRunningAbility.put(abilityKey, isOn);
+ // handler mappings
+ triggerHandlerMappingAsyn(abilityKey, isOn, this.handlerMappings);
+ // notify event
+ AbilityUpdateEvent abilityUpdateEvent = new AbilityUpdateEvent();
+ abilityUpdateEvent.setTable(new AbilityTable().setAbility(Collections.unmodifiableMap(currentRunningAbility)));
+ abilityUpdateEvent.isOn = isOn;
+ abilityUpdateEvent.abilityKey = abilityKey;
+ NotifyCenter.publishEvent(abilityUpdateEvent);
+ return true;
+ }
+
+ /**
+ * Register the component which is managed by {@link AbstractAbilityControlManager}. if you are hoping that a
+ * component will be invoked when turn on/off the ability whose key is abilityKey
.
+ *
+ * @param abilityKey component key.
+ * @param priority the higher the priority is, the faster it will be called.
+ * @param handlerMapping component instance.
+ */
+ @Override
+ public void registerComponent(String abilityKey, HandlerMapping handlerMapping, int priority) {
+ doRegisterComponent(abilityKey, handlerMapping, this.handlerMappings, lockForHandlerMappings, priority, currentRunningAbility);
+ }
+
+ @Override
+ public int removeComponent(String abilityKey, Class extends HandlerMapping> handlerMappingClazz) {
+ return doRemove(abilityKey, handlerMappingClazz, lockForHandlerMappings, handlerMappings);
+ }
+
+ @Override
+ public final void destroy() {
+ LOGGER.warn("[DefaultAbilityControlManager] - Start destroying...");
+ ((ThreadPoolExecutor) simpleThreadPool).shutdown();
+ if (MapUtil.isNotEmpty(handlerMappings)) {
+ handlerMappings.keySet().forEach(key -> doTriggerSyn(key, false, handlerMappings));
+ }
+ // hook
+ doDestroy();
+ LOGGER.warn("[DefaultAbilityControlManager] - Destruction of the end");
+ }
+
+ /**.
+ * hook for subclass
+ */
+ protected void doDestroy() {
+ // for server ability manager
+ }
+
+ /**
+ * Remove the component instance of handlerMappingClazz
.
+ *
+ * @param abilityKey ability key from {@link com.alibaba.nacos.api.ability.constant.AbilityKey}
+ * @param handlerMappingClazz implement of {@link HandlerMapping}
+ * @param lock lock for operation
+ * @param handlerMappingsMap handler collection map
+ * @return the count of components have removed
+ */
+ protected int doRemove(String abilityKey, Class extends HandlerMapping> handlerMappingClazz, Lock lock,
+ Map> handlerMappingsMap) {
+ List handlerMappings = handlerMappingsMap.get(abilityKey);
+ if (CollectionUtils.isEmpty(handlerMappings)) {
+ return 0;
+ }
+ lock.lock();
+ try {
+ AtomicInteger count = new AtomicInteger();
+ handlerMappings.removeIf(item -> {
+ if (item.handlerMapping.getClass().equals(handlerMappingClazz)) {
+ count.getAndIncrement();
+ return true;
+ }
+ return false;
+ });
+ return count.get();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public int removeAll(String abilityKey) {
+ List remove = this.handlerMappings.remove(abilityKey);
+ return Optional.ofNullable(remove).orElse(Collections.emptyList()).size();
+ }
+
+ /**.
+ * Register the component into handlerMappings locking by lockForHandlerMappings to ensure concurrency security.
+ *
+ * @param abilityKey ability key
+ * @param handlerMapping component instance.
+ * @param handlerMappings container
+ * @param lockForHandlerMappings lock to ensure concurrency
+ * @param abilityTable behavioral basis of handler
+ */
+ protected void doRegisterComponent(String abilityKey, HandlerMapping handlerMapping,
+ Map> handlerMappings, Lock lockForHandlerMappings,
+ int priority, Map abilityTable) {
+ if (!currentRunningAbility.containsKey(abilityKey)) {
+ LOGGER.warn("[AbilityHandlePostProcessor] Failed to register processor: {}, because illegal key!",
+ handlerMapping.getClass().getSimpleName());
+ }
+
+ // legal key
+ lockForHandlerMappings.lock();
+ try {
+ List handlers = handlerMappings.getOrDefault(abilityKey, new CopyOnWriteArrayList<>());
+ HandlerWithPriority handlerWithPriority = new HandlerWithPriority(handlerMapping, priority);
+ handlers.add(handlerWithPriority);
+ handlerMappings.put(abilityKey, handlers);
+ // choose behavior
+ // enable default
+ if (abilityTable.getOrDefault(abilityKey, false)) {
+ handlerMapping.enable();
+ } else {
+ handlerMapping.disable();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ LOGGER.error("[DefaultAbilityControlManager] Fail to register handler: {}", handlerMapping.getClass().getSimpleName());
+ } finally {
+ lockForHandlerMappings.unlock();
+ LOGGER.info("[DefaultAbilityControlManager] Successfully registered processor: {}",
+ handlerMapping.getClass().getSimpleName());
+ }
+ }
+
+ /**
+ * Invoke componments which linked to ability key asyn.
+ *
+ * @param key ability key from {@link AbilityKey}
+ * @param isEnabled turn on/off
+ * @param handlerMappingsMap handler collection
+ */
+ protected void triggerHandlerMappingAsyn(String key, boolean isEnabled,
+ Map> handlerMappingsMap) {
+ simpleThreadPool.execute(() -> doTriggerSyn(key, isEnabled, handlerMappingsMap));
+ }
+
+ /**
+ * Invoke componments which linked to ability key syn.
+ *
+ * @param key ability key from {@link AbilityKey}
+ * @param isEnabled turn on/off
+ * @param handlerMappingsMap handler collection
+ */
+ protected void doTriggerSyn(String key, boolean isEnabled,
+ Map> handlerMappingsMap) {
+ List handlerWithPriorities = handlerMappingsMap.get(key);
+ // return if empty
+ if (CollectionUtils.isEmpty(handlerWithPriorities)) {
+ return;
+ }
+ Collections.sort(handlerWithPriorities);
+ // invoked all
+ handlerWithPriorities.forEach(handlerMappingWithPriorities -> {
+ // any error from current handler does not affect other handler
+ HandlerMapping handlerMapping = handlerMappingWithPriorities.handlerMapping;
+ try {
+ if (isEnabled) {
+ handlerMapping.enable();
+ } else {
+ handlerMapping.disable();
+ }
+ } catch (Throwable t) {
+ LOGGER.warn("[HandlerMapping] Failed to invoke {} :{}", handlerMapping.getClass().getSimpleName(),
+ t.getLocalizedMessage());
+ }
+ });
+ }
+
+ @JustForTest
+ protected Map> handlerMapping() {
+ return this.handlerMappings;
+ }
+
+ /**
+ * Support priority handler.
+ */
+ protected class HandlerWithPriority implements Comparable {
+
+ /**.
+ * Decorated
+ */
+ public HandlerMapping handlerMapping;
+
+ /**.
+ * the higher the priority, the faster it will be called
+ */
+ public int priority;
+
+ public HandlerWithPriority(HandlerMapping handlerMapping, int priority) {
+ this.handlerMapping = handlerMapping;
+ this.priority = priority;
+ }
+
+ @Override
+ public int compareTo(HandlerWithPriority o) {
+ return o.priority - this.priority;
+ }
+ }
+
+ /**.
+ * notify when current node ability changing
+ */
+ public class AbilityUpdateEvent extends AbilityEvent {
+
+ private static final long serialVersionUID = -1232411212311111L;
+
+ private String abilityKey;
+
+ private boolean isOn;
+
+ private AbilityUpdateEvent(){}
+
+ public String getAbilityKey() {
+ return abilityKey;
+ }
+
+ public void setAbilityKey(String abilityKey) {
+ this.abilityKey = abilityKey;
+ }
+
+ public boolean isOn() {
+ return isOn;
+ }
+
+ public void setOn(boolean on) {
+ isOn = on;
+ }
+ }
+}
diff --git a/common/src/main/java/com/alibaba/nacos/common/ability/discover/AbilityHandleLoader.java b/common/src/main/java/com/alibaba/nacos/common/ability/discover/AbilityHandleLoader.java
new file mode 100644
index 000000000..feb07436a
--- /dev/null
+++ b/common/src/main/java/com/alibaba/nacos/common/ability/discover/AbilityHandleLoader.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 1999-2022 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.nacos.common.ability.discover;
+
+import com.alibaba.nacos.common.ability.handler.AbilityHandlePreProcessor;
+import com.alibaba.nacos.common.spi.NacosServiceLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+/**.
+ * @author Daydreamer
+ * @description It is spi loader to load {@link AbilityHandlePreProcessor}
+ * @date 2022/8/25 18:24
+ **/
+public class AbilityHandleLoader {
+
+ private final Collection initializers;
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbilityHandleLoader.class);
+
+ public AbilityHandleLoader() {
+ initializers = new HashSet<>();
+ for (AbilityHandlePreProcessor preProcessor : NacosServiceLoader.load(AbilityHandlePreProcessor.class)) {
+ initializers.add(preProcessor);
+ LOGGER.info("Load {} for AbilityHandlePreProcessor", preProcessor.getClass().getCanonicalName());
+ }
+ }
+
+ public Collection getInitializers() {
+ return initializers;
+ }
+}
diff --git a/common/src/main/java/com/alibaba/nacos/common/ability/discover/NacosAbilityManagerHolder.java b/common/src/main/java/com/alibaba/nacos/common/ability/discover/NacosAbilityManagerHolder.java
new file mode 100644
index 000000000..fe7a0c760
--- /dev/null
+++ b/common/src/main/java/com/alibaba/nacos/common/ability/discover/NacosAbilityManagerHolder.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 1999-2022 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.nacos.common.ability.discover;
+
+import com.alibaba.nacos.common.ability.AbstractAbilityControlManager;
+import com.alibaba.nacos.common.ability.DefaultAbilityControlManager;
+import com.alibaba.nacos.common.ability.inter.AbilityControlManager;
+import com.alibaba.nacos.common.spi.NacosServiceLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.ServiceConfigurationError;
+
+/**
+ * This class is used to discover {@link AbstractAbilityControlManager} implements. All the
+ * ability operation will be finish in this singleton.
+ *
+ * @author Daydreamer
+ * @date 2022/7/14 19:58
+ **/
+public class NacosAbilityManagerHolder {
+
+ /**.
+ * private constructor
+ */
+ private NacosAbilityManagerHolder() {
+ }
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(NacosAbilityManagerHolder.class);
+
+ /**.
+ * singleton
+ */
+ private static DefaultAbilityControlManager abstractAbilityControlManager;
+
+ static {
+ // spi discover implement
+ Collection load = null;
+ try {
+ // if server
+ load = NacosServiceLoader.load(DefaultAbilityControlManager.class);
+ } catch (ServiceConfigurationError e) {
+ // if client or not ability control manager
+ load = NacosServiceLoader.load(DefaultAbilityControlManager.class);
+ }
+ // the priority of the server is higher
+ if (load.size() > 0) {
+ load.forEach(clazz -> {
+ abstractAbilityControlManager = clazz;
+ });
+ LOGGER.info("[AbilityControlManager] Successfully initialize AbilityControlManager");
+ // init pre processor
+ AbilityHandleLoader loader = new AbilityHandleLoader();
+ loader.getInitializers().forEach(processor -> abstractAbilityControlManager.addPostProcessor(processor));
+ }
+ }
+
+ /**.
+ * get nacos ability control manager
+ *
+ * @return BaseAbilityControlManager
+ */
+ public static DefaultAbilityControlManager getInstance() {
+ return abstractAbilityControlManager;
+ }
+
+ /**.
+ * Return the target type of ability manager
+ *
+ * @param clazz clazz
+ * @param target type
+ * @return AbilityControlManager
+ */
+ public static T getInstance(Class clazz) {
+ return clazz.cast(abstractAbilityControlManager);
+ }
+}