This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 91f4277740 feat: implement REFRESH event logic for node data 
synchronization and add unit tests (#6958)
91f4277740 is described below

commit 91f4277740fed6e9fe87eb34e17139c3cc766729
Author: Limbo <[email protected]>
AuthorDate: Sun Aug 23 16:37:24 2026 +0800

    feat: implement REFRESH event logic for node data synchronization and add 
unit tests (#6958)
    
    Co-authored-by: aias00 <[email protected]>
---
 .../listener/AbstractNodeDataChangedListener.java  |  14 +++
 .../AbstractNodeDataChangedListenerTest.java       | 128 +++++++++++++++++++++
 2 files changed, 142 insertions(+)

diff --git 
a/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
 
b/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
index 99462c6516..21e6b6d594 100644
--- 
a/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
+++ 
b/shenyu-admin-listener/shenyu-admin-listener-api/src/main/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListener.java
@@ -226,6 +226,9 @@ public abstract class AbstractNodeDataChangedListener 
implements DataChangedList
                     this.delChangedMapToList(nameToIdMap, configKeyPrefix);
                     break;
                 case REFRESH:
+                    changedList.forEach(changedData -> 
publishConfig(configKeyPrefix + mappingKey.apply(changedData) + 
DefaultNodeConstants.JOIN_POINT + mappingValue.apply(changedData), 
changedData));
+                    this.refreshChangedMapToList(nameToIdMap, configKeyPrefix);
+                    break;
                 case MYSELF:
                 default:
                     changedList.forEach(changedData -> 
publishConfig(configKeyPrefix + mappingKey.apply(changedData) + 
DefaultNodeConstants.JOIN_POINT + mappingValue.apply(changedData), 
changedData));
@@ -239,6 +242,17 @@ public abstract class AbstractNodeDataChangedListener 
implements DataChangedList
         }
     }
 
+    private void refreshChangedMapToList(final Map<String, List<String>> 
stringListMap, final String configKeyPrefix) {
+        stringListMap.forEach((key, listIds) -> {
+            final String listKey = configKeyPrefix + key + 
DefaultNodeConstants.POINT_LIST;
+            final String oldNodeListStr = 
Optional.ofNullable(getConfig(listKey)).orElse(DefaultNodeConstants.EMPTY_ARRAY_STR);
+            final List<String> oldNodeList = 
GsonUtils.getInstance().fromList(oldNodeListStr, String.class);
+            oldNodeList.removeAll(listIds);
+            oldNodeList.forEach(id -> delConfig(configKeyPrefix + key + 
DefaultNodeConstants.JOIN_POINT + id));
+            publishConfig(listKey, listIds);
+        });
+    }
+
     private void putChangedMapToList(final Map<String, List<String>> 
stringListMap, final String configKeyPrefix) {
         stringListMap.forEach((key, listIds) -> {
             final String oldNodeListStr = 
Optional.ofNullable(getConfig(configKeyPrefix + key + 
DefaultNodeConstants.POINT_LIST))
diff --git 
a/shenyu-admin-listener/shenyu-admin-listener-api/src/test/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListenerTest.java
 
b/shenyu-admin-listener/shenyu-admin-listener-api/src/test/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListenerTest.java
new file mode 100644
index 0000000000..f552617407
--- /dev/null
+++ 
b/shenyu-admin-listener/shenyu-admin-listener-api/src/test/java/org/apache/shenyu/admin/listener/AbstractNodeDataChangedListenerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.listener;
+
+import org.apache.shenyu.common.constant.DefaultNodeConstants;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public final class AbstractNodeDataChangedListenerTest {
+
+    private static final String NAMESPACE_ID = "namespace";
+
+    private static final String FIRST_PLUGIN = "firstPlugin";
+
+    private static final String SECOND_PLUGIN = "secondPlugin";
+
+    private static final String RETAINED_ID = "retained";
+
+    private static final String ADDED_ID = "added";
+
+    private static final String STALE_ID = "stale";
+
+    @Test
+    public void testOnSelectorChangedRefreshReplacesPluginSelectorIds() {
+        TestNodeDataChangedListener listener = new 
TestNodeDataChangedListener();
+        listener.putConfig(selectorListKey(FIRST_PLUGIN), 
Arrays.asList(RETAINED_ID, STALE_ID));
+        listener.putConfig(selectorListKey(SECOND_PLUGIN), 
Arrays.asList(RETAINED_ID, STALE_ID));
+        listener.putConfig(selectorDataKey(FIRST_PLUGIN, STALE_ID), new 
SelectorData());
+        listener.putConfig(selectorDataKey(SECOND_PLUGIN, STALE_ID), new 
SelectorData());
+
+        listener.onSelectorChanged(Arrays.asList(
+                selectorData(FIRST_PLUGIN, RETAINED_ID),
+                selectorData(SECOND_PLUGIN, RETAINED_ID),
+                selectorData(FIRST_PLUGIN, ADDED_ID)), 
DataEventTypeEnum.REFRESH);
+
+        assertEquals(Arrays.asList(RETAINED_ID, ADDED_ID), 
listener.config(selectorListKey(FIRST_PLUGIN)));
+        assertEquals(Arrays.asList(RETAINED_ID), 
listener.config(selectorListKey(SECOND_PLUGIN)));
+        assertNull(listener.config(selectorDataKey(FIRST_PLUGIN, STALE_ID)));
+        assertNull(listener.config(selectorDataKey(SECOND_PLUGIN, STALE_ID)));
+        assertTrue(listener.wasDeleted(selectorDataKey(FIRST_PLUGIN, 
STALE_ID)));
+        assertTrue(listener.wasDeleted(selectorDataKey(SECOND_PLUGIN, 
STALE_ID)));
+    }
+
+    private static SelectorData selectorData(final String pluginName, final 
String selectorId) {
+        return SelectorData.builder()
+                .namespaceId(NAMESPACE_ID)
+                .pluginName(pluginName)
+                .id(selectorId)
+                .build();
+    }
+
+    private static String selectorListKey(final String pluginName) {
+        return NAMESPACE_ID + ".selector." + pluginName + 
DefaultNodeConstants.POINT_LIST;
+    }
+
+    private static String selectorDataKey(final String pluginName, final 
String selectorId) {
+        return NAMESPACE_ID + ".selector." + pluginName + 
DefaultNodeConstants.JOIN_POINT + selectorId;
+    }
+
+    private static final class TestNodeDataChangedListener extends 
AbstractNodeDataChangedListener {
+
+        private final Map<String, Object> configs = new HashMap<>();
+
+        private final Set<String> deletedDataIds = new HashSet<>();
+
+        private TestNodeDataChangedListener() {
+            super(new ChangeData("plugin", "selector", "rule", "auth", "meta", 
"proxy.selector", "discovery"));
+        }
+
+        @Override
+        public void doPublishConfig(final String dataId, final Object data) {
+            configs.put(dataId, data);
+        }
+
+        @Override
+        protected void doDelConfig(final String dataId) {
+            configs.remove(dataId);
+            deletedDataIds.add(dataId);
+        }
+
+        @Override
+        public String getConfig(final String dataId) {
+            return Optional.ofNullable(configs.get(dataId))
+                    .map(GsonUtils.getInstance()::toJson)
+                    .orElse(null);
+        }
+
+        private void putConfig(final String dataId, final Object data) {
+            configs.put(dataId, data);
+        }
+
+        private Object config(final String dataId) {
+            return configs.get(dataId);
+        }
+
+        private boolean wasDeleted(final String dataId) {
+            return deletedDataIds.contains(dataId);
+        }
+    }
+}

Reply via email to