diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketController.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketController.java new file mode 100644 index 0000000..1d26c14 --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketController.java @@ -0,0 +1,106 @@ +package com.eactive.eai.manage.inflow; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 어댑터/인터페이스/클라이언트 버킷 상태 모니터링 API. + * + * DualInflowControlManager 가 활성화된 환경에서만 정상 응답한다. + * 비활성화 환경에서는 success=false 또는 빈 목록을 반환한다. + * + * GET /manage/inflow/adapter/bucket-status → 전체 어댑터 버킷 상태 + * GET /manage/inflow/adapter/{adapterId}/bucket-status → 특정 어댑터 버킷 상태 + * GET /manage/inflow/interface/bucket-status → 전체 인터페이스 버킷 상태 + * GET /manage/inflow/interface/{interfaceId}/bucket-status → 특정 인터페이스 버킷 상태 + * GET /manage/inflow/client/bucket-status → 전체 클라이언트 버킷 상태 + * GET /manage/inflow/client/{clientId}/bucket-status → 특정 클라이언트 버킷 상태 + */ +@RestController +@RequestMapping("/manage/inflow") +public class InflowTargetBucketController { + + @Autowired + private InflowTargetBucketService bucketService; + + // ========================================================================= + // 어댑터 + // ========================================================================= + + @GetMapping("/adapter/bucket-status") + public ResponseEntity getAllAdapterBucketStatus() { + List list = bucketService.getAllAdapterBucketStatus(); + return ok(list); + } + + @GetMapping("/adapter/{adapterId}/bucket-status") + public ResponseEntity getAdapterBucketStatus(@PathVariable String adapterId) { + TargetBucketStatusDTO dto = bucketService.getAdapterBucketStatus(adapterId); + return single(dto, "어댑터를 찾을 수 없습니다: " + adapterId); + } + + // ========================================================================= + // 인터페이스 + // ========================================================================= + + @GetMapping("/interface/bucket-status") + public ResponseEntity getAllInterfaceBucketStatus() { + List list = bucketService.getAllInterfaceBucketStatus(); + return ok(list); + } + + @GetMapping("/interface/{interfaceId}/bucket-status") + public ResponseEntity getInterfaceBucketStatus(@PathVariable String interfaceId) { + TargetBucketStatusDTO dto = bucketService.getInterfaceBucketStatus(interfaceId); + return single(dto, "인터페이스를 찾을 수 없습니다: " + interfaceId); + } + + // ========================================================================= + // 클라이언트 + // ========================================================================= + + @GetMapping("/client/bucket-status") + public ResponseEntity getAllClientBucketStatus() { + List list = bucketService.getAllClientBucketStatus(); + return ok(list); + } + + @GetMapping("/client/{clientId}/bucket-status") + public ResponseEntity getClientBucketStatus(@PathVariable String clientId) { + TargetBucketStatusDTO dto = bucketService.getClientBucketStatus(clientId); + return single(dto, "클라이언트를 찾을 수 없습니다: " + clientId); + } + + // ========================================================================= + // Helpers + // ========================================================================= + + private ResponseEntity ok(List list) { + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", list); + result.put("count", list.size()); + return ResponseEntity.ok(result); + } + + private ResponseEntity single(TargetBucketStatusDTO dto, String notFoundMsg) { + if (dto == null) { + Map error = new HashMap<>(); + error.put("success", false); + error.put("message", notFoundMsg); + return ResponseEntity.ok(error); + } + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", dto); + return ResponseEntity.ok(result); + } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java new file mode 100644 index 0000000..1ec9a3a --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/InflowTargetBucketService.java @@ -0,0 +1,121 @@ +package com.eactive.eai.manage.inflow; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.eactive.eai.common.inflow.InflowControlManager; +import com.eactive.eai.common.inflow.InflowTargetVO; +import com.eactive.eai.common.inflow.dual.DualCustomBucket; +import com.eactive.eai.common.inflow.dual.DualInflowControlManager; + +/** + * 어댑터/인터페이스/클라이언트 버킷 상태 조회 서비스. + * + * DualInflowControlManager 가 활성화된 경우에만 동작한다. + * InflowControlManager 가 기본 구현체인 경우 빈 목록 또는 null 을 반환한다. + */ +@Service +public class InflowTargetBucketService { + + // ------------------------------------------------------------------------- + // 어댑터 + // ------------------------------------------------------------------------- + + public TargetBucketStatusDTO getAdapterBucketStatus(String adapterId) { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return null; + DualCustomBucket bucket = manager.getAdapterBucket(adapterId); + if (bucket == null) return null; + return toDto(adapterId, "ADAPTER", bucket); + } + + public List getAllAdapterBucketStatus() { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return new ArrayList<>(); + return toDtoList("ADAPTER", manager.getAdapterBucketMap()); + } + + // ------------------------------------------------------------------------- + // 인터페이스 + // ------------------------------------------------------------------------- + + public TargetBucketStatusDTO getInterfaceBucketStatus(String interfaceId) { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return null; + DualCustomBucket bucket = manager.getInterfaceBucket(interfaceId); + if (bucket == null) return null; + return toDto(interfaceId, "INTERFACE", bucket); + } + + public List getAllInterfaceBucketStatus() { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return new ArrayList<>(); + return toDtoList("INTERFACE", manager.getInterfaceBucketMap()); + } + + // ------------------------------------------------------------------------- + // 클라이언트 + // ------------------------------------------------------------------------- + + public TargetBucketStatusDTO getClientBucketStatus(String clientId) { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return null; + DualCustomBucket bucket = manager.getClientBucket(clientId); + if (bucket == null) return null; + return toDto(clientId, "CLIENT", bucket); + } + + public List getAllClientBucketStatus() { + DualInflowControlManager manager = getDualManager(); + if (manager == null) return new ArrayList<>(); + return toDtoList("CLIENT", manager.getClientBucketMap()); + } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------- + + private DualInflowControlManager getDualManager() { + InflowControlManager base = InflowControlManager.getInstance(); + return (base instanceof DualInflowControlManager) ? (DualInflowControlManager) base : null; + } + + private TargetBucketStatusDTO toDto(String targetId, String targetType, DualCustomBucket bucket) { + InflowTargetVO vo = bucket.getInflowTargetVo(); + List buckets = new ArrayList<>(); + + if (vo.getThresholdPerSecond() > 0) { + long capacity = vo.getThresholdPerSecond(); + long available = bucket.getPerSecondAvailableTokens(); + if (available < 0) available = capacity; + buckets.add(new GroupBucketStatusDTO.BucketInfo( + "perSecond", capacity, Math.min(available, capacity), "SEC")); + } + + if (vo.getThreshold() > 0) { + long capacity = vo.getThreshold(); + long available = bucket.getThresholdAvailableTokens(); + if (available < 0) available = capacity; + buckets.add(new GroupBucketStatusDTO.BucketInfo( + "threshold", capacity, Math.min(available, capacity), vo.getThresholdTimeUnit())); + } + + TargetBucketStatusDTO dto = new TargetBucketStatusDTO(); + dto.setTargetId(targetId); + dto.setTargetType(targetType); + dto.setActivate(vo.isActivate()); + dto.setBuckets(buckets); + return dto; + } + + private List toDtoList(String targetType, Map bucketMap) { + List result = new ArrayList<>(); + for (Map.Entry entry : bucketMap.entrySet()) { + result.add(toDto(entry.getKey(), targetType, entry.getValue())); + } + return result; + } +} diff --git a/src/main/java/com/eactive/eai/manage/inflow/TargetBucketStatusDTO.java b/src/main/java/com/eactive/eai/manage/inflow/TargetBucketStatusDTO.java new file mode 100644 index 0000000..1f7ed8e --- /dev/null +++ b/src/main/java/com/eactive/eai/manage/inflow/TargetBucketStatusDTO.java @@ -0,0 +1,27 @@ +package com.eactive.eai.manage.inflow; + +import java.util.List; + +/** + * 어댑터/인터페이스/클라이언트 버킷 상태 DTO. + * {@link GroupBucketStatusDTO}와 동일한 BucketInfo 구조를 공유한다. + */ +public class TargetBucketStatusDTO { + + private String targetId; // 어댑터명 | 인터페이스ID | 클라이언트ID + private String targetType; // "ADAPTER" | "INTERFACE" | "CLIENT" + private boolean activate; + private List buckets; + + public String getTargetId() { return targetId; } + public void setTargetId(String targetId) { this.targetId = targetId; } + + public String getTargetType() { return targetType; } + public void setTargetType(String targetType) { this.targetType = targetType; } + + public boolean isActivate() { return activate; } + public void setActivate(boolean activate) { this.activate = activate; } + + public List getBuckets() { return buckets; } + public void setBuckets(List buckets) { this.buckets = buckets; } +}