diff --git a/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java b/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java
deleted file mode 100644
index becbfeb..0000000
--- a/src/main/java/com/eactive/eai/agent/inflow/ReloadInflowClientControlCommand.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.eactive.eai.agent.inflow;
-
-import com.eactive.eai.agent.command.Command;
-import com.eactive.eai.agent.command.CommandException;
-import com.eactive.eai.common.inflow.AbstractInflowControlManager;
-import com.eactive.eai.common.inflow.InflowControlUtil;
-import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
-import com.eactive.eai.common.util.Logger;
-
-public class ReloadInflowClientControlCommand extends Command {
-
- private static final long serialVersionUID = 1L;
-
- public ReloadInflowClientControlCommand() {
- super("ReloadInflowClientControlCommand");
- }
-
- public Object execute() throws CommandException {
- Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
- if (logger.isInfo())
- logger.info(this.name + " is executed");
-
- if (!(args instanceof String)) {
- String rspErrorCode = "RECEAIMCM001";
- String msg = makeException(rspErrorCode, null);
- if (logger.isError())
- logger.error(msg);
- throw new CommandException(msg);
- }
-
- String keyName = (String) args;
- try {
- AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
- if (!(base instanceof ClientDualInflowControlManager)) {
- throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
- }
- ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
-
- if (keyName != null) {
- if ("ALL".equals(keyName)) {
- manager.reloadClient();
- if (logger.isWarn())
- logger.warn(this.name + "] all rule Reload.");
- } else {
- manager.reloadClient(keyName);
- if (logger.isWarn())
- logger.warn(this.name + "] " + keyName + " Reload.");
- }
- }
-
- } catch (Exception e) {
- String rspErrorCode = "RECEAIMCM002";
- String msg = makeException(rspErrorCode, e);
- if (logger.isError())
- logger.error(msg, e);
- throw new CommandException(msg);
- }
- return "success";
- }
-}
diff --git a/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java b/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java
deleted file mode 100644
index 97fe72c..0000000
--- a/src/main/java/com/eactive/eai/agent/inflow/RemoveInflowClientControlCommand.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.eactive.eai.agent.inflow;
-
-import com.eactive.eai.agent.command.Command;
-import com.eactive.eai.agent.command.CommandException;
-import com.eactive.eai.common.inflow.AbstractInflowControlManager;
-import com.eactive.eai.common.inflow.InflowControlUtil;
-import com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager;
-import com.eactive.eai.common.util.Logger;
-
-public class RemoveInflowClientControlCommand extends Command {
-
- private static final long serialVersionUID = 1L;
-
- public RemoveInflowClientControlCommand() {
- super("RemoveInflowClientControlCommand");
- }
-
- public Object execute() throws CommandException {
- Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
- if (logger.isInfo())
- logger.info(this.name + " is executed");
-
- if (!(args instanceof String)) {
- String rspErrorCode = "RECEAIMCM001";
- String msg = makeException(rspErrorCode, null);
- if (logger.isError())
- logger.error(msg);
- throw new CommandException(msg);
- }
-
- String key = (String) args;
- try {
- AbstractInflowControlManager base = InflowControlUtil.getInflowControlManager();
- if (!(base instanceof ClientDualInflowControlManager)) {
- throw new IllegalStateException("ClientDualInflowControlManager 가 활성화되지 않았습니다.");
- }
- ClientDualInflowControlManager manager = (ClientDualInflowControlManager) base;
- manager.removeClient(key);
- if (logger.isWarn())
- logger.warn(this.name + "] " + key + " removed.");
- } catch (Exception e) {
- String rspErrorCode = "RECEAIMCM002";
- String msg = makeException(rspErrorCode, e);
- if (logger.isError())
- logger.error(msg, e);
- throw new CommandException(msg);
- }
- return "success";
- }
-}
diff --git a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java
deleted file mode 100644
index da8d686..0000000
--- a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualBucket.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.eactive.eai.common.inflow.dual;
-
-import com.eactive.eai.common.inflow.InflowTargetVO;
-
-/**
- * DualBucket에 클라이언트 유량제어 메서드를 추가한 인터페이스.
- * {@link ClientDualInflowControlManager}가 구현한다.
- */
-public interface ClientDualBucket extends DualBucket {
-
- /**
- * 클라이언트 유량제어 체크 — 차단 원인 포함.
- * @return null이면 통과, "PER_SECOND" 또는 "THRESHOLD"이면 해당 버킷에서 차단
- */
- String isClientPassDetail(String clientId);
-
- /**
- * 클라이언트 유량제어 설정 조회 — 에러 메시지 생성용.
- * @return 등록된 설정이 없으면 null
- */
- InflowTargetVO getClientInflowThreshold(String clientId);
-}
diff --git a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java b/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java
deleted file mode 100644
index 606d1e7..0000000
--- a/src/main/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManager.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.eactive.eai.common.inflow.dual;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.springframework.stereotype.Component;
-
-import com.eactive.eai.common.inflow.Bucket;
-import com.eactive.eai.common.inflow.InflowTargetVO;
-import com.eactive.eai.common.inflow.InflowType;
-import com.eactive.eai.common.lifecycle.LifecycleException;
-import com.eactive.eai.common.util.ApplicationContextProvider;
-import com.eactive.eai.common.util.Logger;
-
-/**
- * 어댑터/인터페이스 이중 버킷(DualInflowControlManager)에 클라이언트 유량제어를 추가한 관리자.
- *
- *
적용 방법: INFLOW.properties에 아래 한 줄 추가.
- *
inflow.control.bucket.className=com.eactive.eai.common.inflow.dual.ClientDualInflowControlManager
- */
-@Component
-public class ClientDualInflowControlManager extends DualInflowControlManager implements ClientDualBucket {
-
- private static final Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
-
- public static synchronized Bucket getBucket() {
- return ApplicationContextProvider.getContext().getBean(ClientDualInflowControlManager.class);
- }
-
- private volatile Map clientBucketMap = new ConcurrentHashMap<>();
- private final ConcurrentHashMap clientMetricsMap = new ConcurrentHashMap<>();
-
- // -------------------------------------------------------------------------
- // Lifecycle
- // -------------------------------------------------------------------------
-
- @Override
- public void start() throws LifecycleException {
- super.start();
- try {
- initClients();
- } catch (Exception e) {
- throw new LifecycleException("ClientDualInflowControlManager init failed: " + e.getMessage());
- }
- }
-
- // -------------------------------------------------------------------------
- // 초기화 / 리로드
- // -------------------------------------------------------------------------
-
- private void initClients() throws Exception {
- this.clientBucketMap = loadBucketMap(InflowType.CLIENT);
- if (logger.isInfo()) logger.info("ClientDualInflowControlManager] initClients: " + clientBucketMap.size() + " buckets");
- }
-
- public synchronized void reloadClient() throws Exception {
- initClients();
- clientMetricsMap.clear();
- }
-
- public synchronized void reloadClient(String clientId) throws Exception {
- reloadBucketMap(clientBucketMap, InflowType.CLIENT, clientId);
- TargetMetrics m = clientMetricsMap.get(clientId);
- if (m != null) m.reset();
- }
-
- public synchronized void removeClient(String clientId) {
- clientBucketMap.remove(clientId);
- clientMetricsMap.remove(clientId);
- }
-
- // -------------------------------------------------------------------------
- // 클라이언트 메트릭 접근자
- // -------------------------------------------------------------------------
-
- public TargetMetrics getClientMetrics(String clientId) { return clientMetricsMap.get(clientId); }
- public Map getAllClientMetrics() { return Collections.unmodifiableMap(clientMetricsMap); }
- public void resetClientMetrics(String clientId) { TargetMetrics m = clientMetricsMap.get(clientId); if (m != null) m.reset(); }
- public void resetAllClientMetrics() { clientMetricsMap.values().forEach(TargetMetrics::reset); }
-
- // -------------------------------------------------------------------------
- // ClientDualBucket — 클라이언트 유량제어
- // -------------------------------------------------------------------------
-
- @Override
- public String isClientPassDetail(String clientId) {
- DualCustomBucket b = clientBucketMap.get(clientId);
- if (b == null) return DualCustomBucket.RESULT_PASS;
- String result = b.tryConsume();
- recordMetrics(clientMetricsMap, clientId, result);
- return result;
- }
-
- @Override
- public InflowTargetVO getClientInflowThreshold(String clientId) {
- DualCustomBucket b = clientBucketMap.get(clientId);
- return (b == null) ? null : b.getInflowTargetVo();
- }
-
- // -------------------------------------------------------------------------
- // 모니터링용 접근자
- // -------------------------------------------------------------------------
-
- public DualCustomBucket getClientBucket(String clientId) {
- return clientBucketMap.get(clientId);
- }
-
- public Map getClientBucketMap() {
- return clientBucketMap;
- }
-}
diff --git a/src/test/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManagerMetricsTest.java b/src/test/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManagerMetricsTest.java
deleted file mode 100644
index cb991d2..0000000
--- a/src/test/java/com/eactive/eai/common/inflow/dual/ClientDualInflowControlManagerMetricsTest.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package com.eactive.eai.common.inflow.dual;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.time.Duration;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.test.util.ReflectionTestUtils;
-
-import com.eactive.eai.common.inflow.InflowTargetVO;
-
-import io.github.bucket4j.Bandwidth;
-import io.github.bucket4j.Bucket4j;
-import io.github.bucket4j.local.LocalBucket;
-
-/**
- * ClientDualInflowControlManager 클라이언트 메트릭 단위 테스트.
- */
-class ClientDualInflowControlManagerMetricsTest {
-
- private ClientDualInflowControlManager manager;
-
- @BeforeEach
- void setUp() {
- manager = new ClientDualInflowControlManager();
- }
-
- // -------------------------------------------------------------------------
- // Helpers
- // -------------------------------------------------------------------------
-
- private InflowTargetVO makeTargetVO(String name) {
- InflowTargetVO vo = new InflowTargetVO();
- vo.setName(name);
- vo.setThresholdPerSecond(100);
- vo.setThreshold(10000);
- vo.setThresholdTimeUnit("DAY");
- vo.setActivate(true);
- return vo;
- }
-
- private LocalBucket stableBucket(long capacity) {
- return Bucket4j.builder()
- .addLimit(Bandwidth.simple(capacity, Duration.ofHours(1)))
- .build();
- }
-
- private LocalBucket exhaustedBucket(long capacity) {
- LocalBucket b = stableBucket(capacity);
- b.tryConsume(capacity);
- return b;
- }
-
- private DualCustomBucket passDualBucket(String name) {
- return new DualCustomBucket(stableBucket(100), null, makeTargetVO(name));
- }
-
- private DualCustomBucket blockedPerSecondDualBucket(String name) {
- return new DualCustomBucket(exhaustedBucket(1), null, makeTargetVO(name));
- }
-
- private DualCustomBucket blockedThresholdDualBucket(String name) {
- return new DualCustomBucket(stableBucket(100), exhaustedBucket(1), makeTargetVO(name));
- }
-
- private void injectClientMap(Map map) {
- ReflectionTestUtils.setField(manager, "clientBucketMap", map);
- }
-
- // =========================================================================
- // isClientPassDetail() — 카운터 증가
- // =========================================================================
-
- @Test
- void isClientPassDetail_bucketNotRegistered_noMetricsCreated() {
- manager.isClientPassDetail("UNKNOWN");
-
- assertNull(manager.getClientMetrics("UNKNOWN"));
- }
-
- @Test
- void isClientPassDetail_pass_incrementsAllowed() {
- Map map = new ConcurrentHashMap<>();
- map.put("C1", passDualBucket("C1"));
- injectClientMap(map);
-
- manager.isClientPassDetail("C1");
-
- assertEquals(1, manager.getClientMetrics("C1").allowed.get());
- }
-
- @Test
- void isClientPassDetail_blockedThreshold_incrementsRejectedThreshold() {
- Map map = new ConcurrentHashMap<>();
- map.put("C1", blockedThresholdDualBucket("C1"));
- injectClientMap(map);
-
- manager.isClientPassDetail("C1");
-
- assertEquals(1, manager.getClientMetrics("C1").rejectedThreshold.get());
- }
-
- @Test
- void isClientPassDetail_blockedPerSecond_incrementsRejectedPerSecond() {
- Map map = new ConcurrentHashMap<>();
- map.put("C1", blockedPerSecondDualBucket("C1"));
- injectClientMap(map);
-
- manager.isClientPassDetail("C1");
-
- assertEquals(1, manager.getClientMetrics("C1").rejectedPerSecond.get());
- }
-
- // =========================================================================
- // getAllClientMetrics — 조회 및 불변성
- // =========================================================================
-
- @Test
- void getAllClientMetrics_empty_returnsEmptyMap() {
- assertTrue(manager.getAllClientMetrics().isEmpty());
- }
-
- @Test
- void getAllClientMetrics_afterCalls_containsEntries() {
- Map map = new ConcurrentHashMap<>();
- map.put("C1", passDualBucket("C1"));
- map.put("C2", passDualBucket("C2"));
- injectClientMap(map);
-
- manager.isClientPassDetail("C1");
- manager.isClientPassDetail("C2");
-
- Map all = manager.getAllClientMetrics();
- assertEquals(2, all.size());
- assertTrue(all.containsKey("C1"));
- assertTrue(all.containsKey("C2"));
- }
-
- // =========================================================================
- // resetClientMetrics / resetAllClientMetrics
- // =========================================================================
-
- @Test
- void resetClientMetrics_resetsTargetClient() {
- Map map = new ConcurrentHashMap<>();
- map.put("C1", passDualBucket("C1"));
- injectClientMap(map);
-
- manager.isClientPassDetail("C1");
- manager.resetClientMetrics("C1");
-
- assertEquals(0, manager.getClientMetrics("C1").allowed.get());
- }
-
- @Test
- void resetAllClientMetrics_resetsAll() {
- Map map = new ConcurrentHashMap<>();
- map.put("C1", passDualBucket("C1"));
- map.put("C2", passDualBucket("C2"));
- injectClientMap(map);
-
- manager.isClientPassDetail("C1");
- manager.isClientPassDetail("C2");
- manager.resetAllClientMetrics();
-
- assertEquals(0, manager.getClientMetrics("C1").allowed.get());
- assertEquals(0, manager.getClientMetrics("C2").allowed.get());
- }
-
- // =========================================================================
- // removeClient — 메트릭 자동 삭제
- // =========================================================================
-
- @Test
- void removeClient_cleansUpMetrics() {
- Map map = new ConcurrentHashMap<>();
- map.put("C1", passDualBucket("C1"));
- injectClientMap(map);
-
- manager.isClientPassDetail("C1");
- assertNotNull(manager.getClientMetrics("C1"));
-
- manager.removeClient("C1");
-
- assertNull(manager.getClientMetrics("C1"));
- }
-}