diff --git a/docs/API_TRANSACTION_FLOW.md b/docs/API_TRANSACTION_FLOW.md new file mode 100644 index 0000000..6605510 --- /dev/null +++ b/docs/API_TRANSACTION_FLOW.md @@ -0,0 +1,500 @@ +# API 거래 플로우 정리 + +## 전체 플로우 다이어그램 + +``` +[Client Request] + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 1. ApiAdapterController (/api/**, /mapi/**) │ +│ - HTTP 요청 수신, 어댑터 정보 조회 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 2. ApiAdapterService │ +│ - 요청 파싱 (GET/POST/PUT/DELETE) │ +│ - 암/복호화 처리 (AES128, AES256 등) │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 3. HttpAdapterServiceSupport.service() │ +│ - Pre/Post Filter 실행 │ +│ - RequestDispatcher 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 4. RequestDispatcher.handle() │ +│ - Processor 인스턴스 생성/획득 │ +│ - Adapter 상태 관리 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 5. RequestProcessor.hookup() │ +│ - EAIMessage 생성 (StandardMessage → EAIMessage) │ +│ - 유량제어, 라우팅 판단 │ +│ - FlowRouter.process() 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 6. IFRouter.process() │ +│ - Outbound 라우팅 │ +│ - FailOver 처리 │ +│ - ElinkESBProcessProxy 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 7. RESTProcess (extends DefaultProcess) │ +│ - flow() 메소드 실행 │ +│ - 송신 변환, 로그, 응답 처리 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 8. HttpSender.callService() │ +│ - ElinkAdapter 획득 │ +│ - Adapter 호출 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────┐ +│ 9. HttpClient5AdapterServiceRest.execute() │ +│ - 실제 HTTP 통신 (Apache HttpClient 5) │ +│ - OAuth 토큰 관리 │ +│ - 암/복호화 처리 │ +└─────────────────────────────────────────────────────────────────┘ + ↓ +[Target System] +``` + +--- + +## 각 클래스 설명 + +### 1. ApiAdapterController + +| 항목 | 내용 | +|------|------| +| 위치 | `eapim-online/src/main/java/com/eactive/eai/adapter/controller/` | +| 역할 | REST API 진입점 (Spring Controller) | +| 주요 기능 | - `/api/**`, `/mapi/**` 엔드포인트 매핑
- TSEAIHS04 테이블에서 어댑터 정보 조회
- HttpDynamicInAdapterManager로 어댑터 찾기
- ApiAdapterService.callApi() 호출 | + +### 2. ApiAdapterService + +| 항목 | 내용 | +|------|------| +| 위치 | `eapim-online/src/main/java/com/eactive/eai/adapter/service/` | +| 역할 | Inbound 서비스 레이어 | +| 상속 | `HttpAdapterServiceSupport` | +| 주요 기능 | - HTTP Method별 요청 파싱
- Inbound 암/복호화 (AES128, AES256 등)
- 부모 클래스 `service()` 호출 | + +### 3. HttpAdapterServiceSupport + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/dynamic/` | +| 역할 | Inbound 어댑터 추상 클래스 | +| 주요 기능 | - `service()` 메소드: 핵심 디스패처
- Pre-Filter / Post-Filter 실행
- StandardMessage 생성
- RequestDispatcher.handle() 호출 | + +### 4. RequestDispatcher + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/` | +| 역할 | 요청 분배자 | +| 주요 기능 | - Processor 인스턴스 생성/획득
- `processor.execute()` 호출
- AdapterGroup 상태 관리 (동시 처리 수 증감) | + +### 5. RequestProcessorSupport / RequestProcessor + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/inbound/processor/` | +| 역할 | 요청 처리 프로세서 | +| 관계 | `RequestProcessor extends RequestProcessorSupport` | +| 주요 기능 | - `execute()` → `hookup()` 호출
- StandardMessage → EAIMessage 변환
- 유량제어 (RateLimiter)
- SYNC/ASYNC 분기 처리
- FlowRouter.process() 호출 | + +### 6. IFRouter + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/common/routing/` | +| 역할 | FlowControl → Outbound 라우터 | +| 주요 기능 | - Outbound Process로 라우팅
- Local/Remote 호출 판단
- FailOver 처리
- TAS(시뮬레이터) 거래 처리
- ElinkESBProcessProxy 호출 | + +### 7. DefaultProcess / RESTProcess + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/outbound/` | +| 역할 | Outbound 처리 프로세스 | +| 관계 | `RESTProcess extends HTTPProcess extends DefaultProcess` | +| 주요 기능 (DefaultProcess) | - `flow()`: 핵심 처리 흐름
- 송신 전/후 처리
- 메시지 변환 (Transform)
- 로그 처리 | +| 주요 기능 (RESTProcess) | - `outboundCtrlCallService()`: HttpSender 호출
- REST 특화 처리
- 표준/비표준 메시지 처리 | + +### 8. HttpSender + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/control/` | +| 역할 | HTTP 송신 제어 | +| 주요 기능 | - ElinkAdapterFactory에서 HTTP Adapter 획득
- `adapter.callService()` 호출
- SocketTimeout, HttpStatusException 처리 | + +### 9. HttpClient5AdapterServiceRest + +| 항목 | 내용 | +|------|------| +| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/client/impl/` | +| 역할 | 실제 HTTP 클라이언트 구현체 | +| 주요 기능 | - Apache HttpClient 5 사용
- GET/POST/PUT/DELETE 메소드 처리
- OAuth2 토큰 관리 (발급/갱신)
- Outbound 암/복호화 (AES128, AES256, AES256GCM 등)
- Forward Proxy 지원
- 응답 헤더 릴레이 | + +--- + +## 클래스 관계도 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ eapim-online (프로젝트) │ +├─────────────────────────────────────────────────────────────────────────┤ +│ ApiAdapterController ──uses──▶ ApiAdapterService │ +│ │ │ +│ │ extends │ +│ ▼ │ +└──────────────────────────────────────┼──────────────────────────────────┘ + │ +┌──────────────────────────────────────┼──────────────────────────────────┐ +│ elink-online-common (서브모듈) │ +├──────────────────────────────────────┼──────────────────────────────────┤ +│ ▼ │ +│ HttpAdapterServiceSupport │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ RequestDispatcher ──creates──▶ RequestProcessor │ +│ │ │ +│ │ extends │ +│ ▼ │ +│ RequestProcessorSupport │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ FlowRouter ────────────────▶ IFRouter │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ RESTProcess ◀──extends── HTTPProcess │ +│ │ │ │ +│ │ extends │ extends │ +│ ▼ ▼ │ +│ DefaultProcess ◀────────────┘ │ +│ │ │ +│ │ calls │ +│ ▼ │ +│ HttpSender │ +│ │ │ +│ │ uses │ +│ ▼ │ +│ ElinkAdapterFactory │ +│ │ │ +│ │ creates │ +│ ▼ │ +│ HttpClient5AdapterServiceRest │ +│ │ │ +│ │ uses │ +│ ▼ │ +│ Apache HttpClient 5 │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 메시지 변환 흐름 + +``` +[HTTP Request Body] + ↓ + StandardMessage (표준전문) + ↓ + EAIMessage (내부 전문) + ↓ + Transform (변환) + ↓ + [Target System Request] + ↓ + [Target System Response] + ↓ + Transform (역변환) + ↓ + EAIMessage + ↓ + StandardMessage + ↓ + [HTTP Response Body] +``` + +--- + +## 주요 처리 패턴 + +| 패턴 | 설명 | 처리 위치 | +|------|------|-----------| +| **SS (Sync-Sync)** | 동기 요청 → 동기 응답 | RequestProcessor, RESTProcess | +| **SA (Sync-Async)** | 동기 요청 → 비동기 응답 (Topic 대기) | RESTProcess.getTopicProperty() | +| **AS (Async-Sync)** | 비동기 요청 → 동기 응답 | DefaultProcess.isAsyncSyncPattern() | +| **AA (Async-Async)** | 비동기 요청 → 비동기 응답 | RequestProcessor | + +--- + +## 비동기(Async) 처리 플로우 + +### 비동기 전체 흐름 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ [송신 스레드 - 요청 수신] │ +│ │ +│ RequestProcessor.hookup() │ +│ ├─ ROUTING_TYPE = "QUEUE" 판단 │ +│ │ │ +│ └─ FlowRouter.process() │ +│ └─ JMSSender.sendToQueue(eaiMessage, ...) │ +│ └─ EAIMessage 직렬화 → JMS Queue 전송 │ +│ │ │ +│ ← 즉시 응답 반환 (202 Accepted 등) │ +└─────────────────────────┼───────────────────────────────────────────────┘ + │ + [JMS Queue] + (ActiveMQ / WebLogic JMS / WebSphere MQ) + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ [수신 스레드 - QueueConsumer] │ +│ │ +│ DefaultQueueConsumer.run() │ +│ ├─ consumer.receive() - 메시지 수신 대기 │ +│ │ │ +│ ├─ EAIMessage 역직렬화 │ +│ │ └─ transactionContext Map 복원 │ +│ │ │ +│ ├─ ElinkTransactionContext.initialize(...) │ +│ │ │ +│ └─ ElinkESBProcessProxy.callElinkESBProcess() │ +│ └─ DefaultProcess / RESTProcess 호출 │ +│ └─ 실제 대외기관 호출 │ +│ │ +│ (finally) ElinkTransactionContext.clear() │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Queue 송신 (FlowRouter → JMSSender) + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ FlowRouter.process() │ +│ │ │ +│ ├─ route_type == "QUEUE" 확인 │ +│ │ │ +│ ├─ RoutingVO에서 asyncRoutingPath(Queue 이름) 조회 │ +│ │ │ +│ ├─ Properties에 추가 정보 설정 │ +│ │ ├─ EAIBWKCLS (업무그룹코드) │ +│ │ ├─ EAISVCCD (서비스코드) │ +│ │ └─ OSIDINSTCD (대외기관코드) │ +│ │ │ +│ └─ JMSSender.sendToQueue(eaiMessage, factory, queue, prop) │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ JMSSender.sendToQueue() │ +│ │ │ +│ ├─ ElinkConfig.isUseInternalQueue() 확인 │ +│ │ │ +│ ├─ [내부 Queue] AmqQueueProducer.sendMessage() │ +│ │ └─ ActiveMQ 내장 브로커 사용 (vm://localhost) │ +│ │ │ +│ └─ [외부 Queue] sendToJmsQueue() │ +│ ├─ JmsServiceLocator에서 QueueConnectionFactory 획득 │ +│ ├─ QueueConnection → QueueSession → QueueSender 생성 │ +│ ├─ ObjectMessage 생성 │ +│ │ ├─ msg.setObject(eaiMessage) ← Serializable │ +│ │ └─ msg.setStringProperty(...) ← Properties 복사 │ +│ └─ qsender.send(msg) │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### Queue Consumer 수신 및 처리 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ QueueConsumerService (Spring Bean) │ +│ │ │ +│ ├─ afterPropertiesSet() → startConsumer() │ +│ │ │ +│ ├─ ActiveMQConnectionFactory 생성 │ +│ │ └─ setTrustAllPackages(true) ← 역직렬화 허용 │ +│ │ │ +│ ├─ ExecutorService (FixedThreadPool) 생성 │ +│ │ │ +│ └─ DefaultQueueConsumer[maxThread] 생성 및 submit │ +│ └─ 각 Consumer가 별도 스레드에서 실행 │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ Consumer.run() [Loop] (DefaultQueueConsumer / FCQueueReceiver) │ +│ │ │ +│ ├─ while(run) { │ +│ │ message = consumer.receive(2000) ← 2초 타임아웃 │ +│ │ │ +│ │ if(message == null) continue; │ +│ │ │ +│ │ // ObjectMessage 검증 및 EAIMessage 역직렬화 │ +│ │ omsg = (ObjectMessage) message; │ +│ │ eaiMessage = (EAIMessage) omsg.getObject(); │ +│ │ │ +│ │ // RoutingVO에서 Process 경로 조회 │ +│ │ routingVO = RoutingManager.getRoutingVO(flwCntlRtnNm); │ +│ │ serviceURI = routingVO.getSyncRoutingPath(); // BaseFCProcess │ +│ │ │ +│ │ // Process 호출 (ElinkESBProcessProxy 내부에서 컨텍스트 관리) │ +│ │ ElinkESBProcessProxy.callElinkESBProcess(serviceURI, ...) │ +│ │ } │ +└─────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ ElinkESBProcessProxy.callElinkESBProcess() │ +│ │ │ +│ ├─ [조건 충족: ASYNC && !outbound] → 컨텍스트 초기화 │ +│ │ ElinkTransactionContext.initialize(eaiMessage.getTransactionContext()) +│ │ │ +│ ├─ BaseFCProcess.callService() │ +│ │ └─ ElinkESBProcessProxy (RESTProcess) ← 조건 미충족, 초기화 안함 │ +│ │ └─ RESTProcess 실행 │ +│ │ │ +│ └─ finally { ElinkTransactionContext.clear() } ← 조건부 해제 │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 비동기 관련 클래스 + +| 클래스 | 위치 | 역할 | +|--------|------|------| +| `FlowRouter` | `elink-online-common/.../routing/` | ROUTING_TYPE에 따라 CALL/QUEUE/TOPIC 분기 | +| `JMSSender` | `elink-online-common/.../util/` | JMS Queue/Topic 메시지 송신 | +| `AmqQueueProducer` | `elink-online-common/.../activemq/` | ActiveMQ 내장 브로커용 Producer | + +#### JMS Provider별 Consumer 구현체 + +| Provider | Consumer | 비고 | +|----------|----------|------| +| **ActiveMQ** | `flowcontrol/activemq/DefaultQueueConsumer.java` | 내장 브로커 (vm://localhost) | +| **JEUS** | `flowcontrol/jeus/DefaultQueueConsumer.java` | JNDI Lookup | +| **WebSphere** | `flowcontrol/websphere/DefaultQueueConsumer.java` | JNDI Lookup | +| **WebLogic** | `flowcontrol/jms/FCQueueReceiver.java` | JNDI Lookup (weblogic.jndi.WLInitialContextFactory) | + +> `ElinkTransactionContext` 초기화/해제는 Consumer가 아닌 `ElinkESBProcessProxy`에서 조건부로 처리됩니다. + +### Queue 연결 방식 + +| 방식 | 설정 | 사용 환경 | +|------|------|-----------| +| **내장 브로커** | `vm://localhost` | 단일 인스턴스, 테스트 환경 | +| **TCP 연결** | `tcp://host:61617` | 분산 환경, 다중 인스턴스 | +| **외부 JMS** | JNDI Lookup | WebLogic, WebSphere MQ 등 | + +### 환경별 Consumer 설정 (Spring XML) + +``` +WebContent/WEB-INF/ +├─ applicationContext.xml ← 메인 설정 +│ ├─ import: mq/applicationContext-activemq.xml +│ └─ import: mq/applicationContext-websphere.xml +│ +└─ mq/ + ├─ applicationContext-activemq.xml ← ActiveMQ (현재 활성화) + │ └─ flowcontrol.activemq.QueueConsumerService + │ └─ uri: vm://localhost (내장 브로커) + │ + └─ applicationContext-websphere.xml ← WebSphere MQ (주석처리 = 비활성화) + └─ flowcontrol.websphere.QueueConsumerService + └─ 외부 WebSphere MQ 연결 +``` + +> **Note**: WebLogic/JEUS 환경에서 외부 JMS 사용 시 해당 XML 파일 추가 또는 수정 필요. +> 현재 프로젝트는 **ActiveMQ 내장 브로커**를 기본으로 사용. + +### TransactionContext 비동기 전달 + +``` +[송신 스레드] [수신 스레드] + │ │ + ├─ eaiMsg.transactionContext │ + │ └─ {"SEED_SALT": "abc123", ...} │ + │ │ + ▼ │ +┌──────────────┐ │ +│ Serialize │ ──── JMS Queue ────▶ │ +│ (EAIMessage) │ │ +└──────────────┘ │ + ▼ + ┌──────────────────┐ + │ Deserialize │ + │ (EAIMessage) │ + │ └─ context 복원 │ + └──────────────────┘ + │ + ▼ + ElinkESBProcessProxy + └─ 조건부 initialize() + │ + ElinkTransactionContext + .getSeedSalt() → "abc123" +``` + +> **Note**: `EAIMessage.transactionContext`는 `Serializable`이므로 JMS Queue를 통해 전달되어도 데이터가 유지됩니다. +> `ElinkTransactionContext.initialize()`는 `ElinkESBProcessProxy`에서 ASYNC 거래 && !outbound 조건일 때만 호출됩니다. + +--- + +## 암호화 지원 (HttpClient5AdapterServiceRest) + +| 알고리즘 | 용도 | Key 추출 방식 | +|----------|------|---------------| +| AES128-TOSS | 토스 연동 | clientSecret[22:38] | +| AES128-TOGETHER | 투게더 연동 | clientSecret[44:60] | +| AES256 | 일반 | clientSecret[10:42] | +| AES256-KAKAO | 카카오 연동 | IV/Key 별도 설정 | +| AES256-NICEON | 나이스지키미 연동 | Adapter Token 기반 | +| AES256GCM | GCM 모드 | 동적 Key 생성 | + +--- + +## 관련 파일 경로 + +### eapim-online (메인 프로젝트) +- `src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java` +- `src/main/java/com/eactive/eai/adapter/service/ApiAdapterService.java` + +### elink-online-common (서브모듈) + +#### 동기 처리 +- `src/main/java/com/eactive/eai/adapter/http/dynamic/HttpAdapterServiceSupport.java` +- `src/main/java/com/eactive/eai/adapter/RequestDispatcher.java` +- `src/main/java/com/eactive/eai/inbound/processor/RequestProcessorSupport.java` +- `src/main/java/com/eactive/eai/inbound/processor/RequestProcessor.java` +- `src/main/java/com/eactive/eai/common/routing/IFRouter.java` +- `src/main/java/com/eactive/eai/outbound/DefaultProcess.java` +- `src/main/java/com/eactive/eai/outbound/RESTProcess.java` +- `src/main/java/com/eactive/eai/control/HttpSender.java` +- `src/main/java/com/eactive/eai/adapter/http/client/impl/HttpClient5AdapterServiceRest.java` + +#### 비동기 처리 (Queue) +- `src/main/java/com/eactive/eai/common/routing/FlowRouter.java` - 라우팅 분기 (CALL/QUEUE/TOPIC) +- `src/main/java/com/eactive/eai/common/routing/ElinkESBProcessProxy.java` - Process 호출 (비동기 컨텍스트 관리) +- `src/main/java/com/eactive/eai/common/util/JMSSender.java` - Queue/Topic 송신 +- `src/main/java/com/eactive/eai/common/activemq/AmqQueueProducer.java` - ActiveMQ Producer +- `src/main/java/com/eactive/eai/flowcontrol/activemq/QueueConsumerService.java` - ActiveMQ Consumer 서비스 +- `src/main/java/com/eactive/eai/flowcontrol/activemq/DefaultQueueConsumer.java` - ActiveMQ Consumer +- `src/main/java/com/eactive/eai/flowcontrol/jeus/QueueConsumerService.java` - JEUS Consumer 서비스 +- `src/main/java/com/eactive/eai/flowcontrol/jeus/DefaultQueueConsumer.java` - JEUS Consumer +- `src/main/java/com/eactive/eai/flowcontrol/websphere/QueueConsumerService.java` - WebSphere Consumer 서비스 +- `src/main/java/com/eactive/eai/flowcontrol/websphere/DefaultQueueConsumer.java` - WebSphere Consumer +- `src/main/java/com/eactive/eai/flowcontrol/jms/FCQueueReceiver.java` - WebLogic JMS Consumer diff --git a/docs/ELINK_TRANSACTION_CONTEXT.md b/docs/ELINK_TRANSACTION_CONTEXT.md new file mode 100644 index 0000000..12bb0a7 --- /dev/null +++ b/docs/ELINK_TRANSACTION_CONTEXT.md @@ -0,0 +1,300 @@ +# ElinkTransactionContext 아키텍처 + +## 개요 + +`ElinkTransactionContext`는 트랜잭션(거래) 범위에서 컨텍스트 변수를 관리하기 위한 ThreadLocal 기반 유틸리티입니다. JEP 함수 등 EAIMessage나 Properties에 직접 접근할 수 없는 클래스에서 트랜잭션 범위 변수에 접근할 수 있도록 합니다. + +--- + +## 핵심 설계 원칙 + +1. **ThreadLocal이 직접 저장소**: EAIMessage의 Map을 참조하지 않고 ThreadLocal에 직접 저장 +2. **Lazy Initialization**: 최초 put 시 Map 자동 생성 (명시적 초기화 불필요) +3. **clear()는 필수**: 스레드 풀 환경에서 메모리 누수 방지 +4. **비동기 전달용 필드 분리**: `transactionContextTransfer`는 JMS 전달 전용 + +--- + +## 구성 요소 + +### 1. ElinkTransactionContext (저장소) + +```java +// ElinkTransactionContext.java (elink-online-core) +public class ElinkTransactionContext { + private static final ThreadLocal> THREAD_LOCAL = new ThreadLocal<>(); + + // 컨텍스트 정리 (필수) + public static void clear(); + + // 비동기 전달용 + public static Map getAll(); // 복사본 반환 + public static void restore(Map ctx); // 복원 + + // 명시적 접근자 (Lazy init - 최초 put 시 Map 생성) + public static void setSeedSalt(String seedSalt); + public static String getSeedSalt(); + public static void setEncryptionKey(String encryptionKey); + public static String getEncryptionKey(); +} +``` + +- **위치**: `elink-online-core` +- **역할**: ThreadLocal 기반 트랜잭션 변수 저장소 +- **특징**: + - 명시적 초기화 불필요 (Lazy init) + - `clear()`는 필수 (finally 블록에서 호출) + +### 2. EAIMessage.transactionContextTransfer (전달용) + +```java +// EAIMessage.java (elink-online-common) +/** + * [주의] 비동기 JMS 전달 전용 - 직접 사용 금지! + */ +private Map transactionContextTransfer; + +public Map getTransactionContextTransfer(); +public void setTransactionContextTransfer(Map ctx); +``` + +- **위치**: `elink-online-common` +- **역할**: 비동기 거래 시 JMS를 통한 컨텍스트 전달 +- **특징**: + - `Serializable` - ObjectMessage로 직렬화 + - **직접 데이터 저장 용도로 사용 금지** + - 복원 후 즉시 null로 초기화 + +--- + +## 처리 흐름 + +### 동기 (Sync) 처리 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ ApiAdapterController.callApi() │ +│ │ │ +│ ├─ try { │ +│ │ service.callApi() │ +│ │ └─ RequestProcessor.hookup() │ +│ │ │ │ +│ │ ├─ [컨텍스트 사용 가능 - Lazy Init] ───────────────── │ +│ │ │ ElinkTransactionContext.setSeedSalt("value") │ +│ │ │ └─ 최초 호출 시 Map 자동 생성 │ +│ │ │ │ +│ │ ├─ FlowRouter.process() │ +│ │ │ └─ ElinkESBProcessProxy → Process 호출 │ +│ │ │ │ +│ │ ├─ [반환 후에도 컨텍스트 사용 가능] ✓ │ +│ │ │ │ +│ │ └─ 응답 반환 │ +│ │ } │ +│ └─ finally { │ +│ ElinkTransactionContext.clear() ← 최상위 진입점에서 해제 │ +│ } │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +### 비동기 (Async) 처리 - Queue 사용 + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ [송신 스레드] │ +│ │ +│ ApiAdapterController.callApi() │ +│ │ │ +│ ├─ try { │ +│ │ RequestProcessor.hookup() │ +│ │ ├─ ElinkTransactionContext.setSeedSalt("value") │ +│ │ │ └─ ThreadLocal에 직접 저장 │ +│ │ │ │ +│ │ └─ FlowRouter.process() │ +│ │ ├─ [컨텍스트 복사] ───────────────────────────────────── │ +│ │ │ eaiMessage.setTransactionContextTransfer( │ +│ │ │ ElinkTransactionContext.getAll() │ +│ │ │ ) │ +│ │ └─ JMSSender.sendToQueue(eaiMessage, ...) │ +│ │ └─ EAIMessage 직렬화 (transactionContextTransfer 포함)│ +│ │ } │ +│ └─ finally { ElinkTransactionContext.clear() } │ +│ │ │ +└──────────────────────────┼──────────────────────────────────────────────┘ + │ + [JMS Queue] + │ + ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ [수신 스레드 - Consumer] │ +│ │ +│ FCQueueReceiver / DefaultQueueConsumer │ +│ │ │ +│ ├─ EAIMessage 역직렬화 (transactionContextTransfer 복원) │ +│ │ │ +│ └─ ElinkESBProcessProxy.callElinkESBProcess(BaseFCProcess, ...) │ +│ │ │ +│ ├─ [조건 충족: ASYNC && !outbound] │ +│ │ ├─ ElinkTransactionContext.restore( │ +│ │ │ eaiMessage.getTransactionContextTransfer() │ +│ │ │ ) │ +│ │ └─ eaiMessage.setTransactionContextTransfer(null) ← 즉시 초기화 +│ │ │ +│ ├─ BaseFCProcess.callService() │ +│ │ └─ ElinkESBProcessProxy(..., RESTProcess, ...) │ +│ │ └─ [조건 미충족: outbound] → 복원 안함 │ +│ │ └─ RESTProcess 호출 │ +│ │ └─ ElinkTransactionContext.getSeedSalt() │ +│ │ └─ 송신 스레드에서 설정한 값 조회 ✓ │ +│ │ │ +│ └─ finally { ElinkTransactionContext.clear() } │ +└─────────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 초기화/해제 위치 + +| 위치 | 역할 | 비고 | +|------|------|------| +| `ApiAdapterController.callApi()` | 해제 (finally) | 동기 - 최상위 진입점 | +| `RequestProcessorSupport.execute()` | 해제 (finally) | 동기 - 안전망 (이중 해제) | +| `FlowRouter.process()` | 컨텍스트 복사 | 비동기 송신 전 | +| `ElinkESBProcessProxy.callElinkESBProcess()` | 복원/해제 (조건부) | 비동기 수신 시 | + +### ElinkESBProcessProxy 복원 조건 + +```java +boolean shouldManageContext = eaiMessage != null + && EAIMessageKeys.ASYNC_SVC.equals(eaiMessage.getSvcTsmtUsgTp()) // ASYNC 거래 + && (serviceURI == null || !serviceURI.startsWith("com.eactive.eai.outbound")); // outbound가 아닐 때 + +if (shouldManageContext) { + ElinkTransactionContext.restore(eaiMessage.getTransactionContextTransfer()); + eaiMessage.setTransactionContextTransfer(null); // 복원 후 즉시 초기화 +} +``` + +- **ASYNC 거래**: 동기 거래는 Lazy init으로 자동 처리 +- **outbound가 아닐 때**: BaseFCProcess 호출 시에만 복원, RESTProcess 호출 시에는 이미 복원된 상태 + +> **Note**: `clear()`는 멱등(idempotent)하므로 여러 번 호출되어도 안전합니다. + +--- + +## 사용 예시 + +### 값 설정 (RequestProcessor, DefaultProcess 등) + +```java +// SeedSalt 설정 (최초 호출 시 Map 자동 생성) +ElinkTransactionContext.setSeedSalt("o123y498172"); + +// EncryptionKey 설정 +ElinkTransactionContext.setEncryptionKey("myKey123"); +``` + +### 값 조회 (JEP 함수, Transform 등) + +```java +// SeedSalt 조회 +String seedSalt = ElinkTransactionContext.getSeedSalt(); + +// EncryptionKey 조회 +String encryptionKey = ElinkTransactionContext.getEncryptionKey(); +``` + +--- + +## 변수 추가 방법 + +새로운 트랜잭션 변수가 필요한 경우 `ElinkTransactionContext`에 접근자 메소드 추가: + +```java +// ElinkTransactionContext.java + +// 1. Key 상수 추가 +private static final String KEY_NEW_VARIABLE = "NEW_VARIABLE"; + +// 2. Setter 추가 (private put 헬퍼 사용) +public static void setNewVariable(String value) { + put(KEY_NEW_VARIABLE, value); +} + +// 3. Getter 추가 (private get 헬퍼 사용) +public static String getNewVariable() { + return get(KEY_NEW_VARIABLE); +} +``` + +--- + +## 주의사항 + +### Thread Pool 환경 + +- `ApiAdapterController.callApi()` finally에서 `clear()` 호출 +- `ElinkESBProcessProxy.callElinkESBProcess()` finally에서 조건부 `clear()` 호출 +- Thread 재사용 시 이전 트랜잭션 데이터 누수 방지 + +### 비동기 Queue 처리 + +- `EAIMessage.transactionContextTransfer`는 **전달 전용** (직접 접근 금지) +- `FlowRouter`에서 송신 전 `getAll()`로 복사 +- `ElinkESBProcessProxy`에서 `restore()` 후 즉시 null 초기화 + +### transactionContextTransfer 직접 사용 금지 + +```java +// ❌ 잘못된 사용 +eaiMessage.getTransactionContextTransfer().put("key", "value"); + +// ✓ 올바른 사용 +ElinkTransactionContext.setSeedSalt("value"); +``` + +--- + +## 관련 파일 + +| 파일 | 위치 | 역할 | +|------|------|------| +| `ElinkTransactionContext.java` | `elink-online-core` | ThreadLocal 컨텍스트 저장소 | +| `EAIMessage.java` | `elink-online-common` | transactionContextTransfer (전달용) | +| `ApiAdapterController.java` | `eapim-online` | 해제 (동기 최상위 진입점) | +| `FlowRouter.java` | `elink-online-common` | 비동기 송신 전 컨텍스트 복사 | +| `ElinkESBProcessProxy.java` | `elink-online-common` | 복원/해제 (비동기 - 조건부) | +| `RequestProcessorSupport.java` | `elink-online-common` | 해제 (동기 안전망) | + +--- + +## 클래스 다이어그램 + +``` +┌─────────────────────────────────────┐ +│ elink-online-core │ +├─────────────────────────────────────┤ +│ ElinkTransactionContext │ +│ ┌───────────────────────────────┐ │ +│ │ ThreadLocal>│◄── 직접 저장소 (Lazy Init) +│ └───────────────────────────────┘ │ +│ + clear() │ +│ + getAll() → Map 복사본 │ +│ + restore(Map) │ +│ + setSeedSalt() / getSeedSalt() │ +│ + setEncryptionKey() / ... │ +└───────────────┬─────────────────────┘ + │ 비동기 전달 시 + │ getAll() → 복사 + ▼ +┌─────────────────────────────────────┐ +│ elink-online-common │ +├─────────────────────────────────────┤ +│ EAIMessage │ +│ - transactionContextTransfer │◄── JMS 전달 전용 (직접 사용 금지!) +│ : Map │ Serializable +│ │ +│ [송신] FlowRouter에서 설정 │ +│ [수신] ElinkESBProcessProxy에서 │ +│ restore() 후 null 초기화 │ +└─────────────────────────────────────┘ +``` diff --git a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java index 11feef8..d118539 100644 --- a/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java +++ b/src/main/java/com/eactive/eai/adapter/controller/ApiAdapterController.java @@ -60,8 +60,10 @@ public class ApiAdapterController implements HttpAdapterServiceKey { String apiUri = servletRequest.getRequestURI(); // /api/v1/public/getUserInfo.svc apiUri = StringUtils.removeStart(apiUri, servletRequest.getContextPath()); - apiUri = StringUtils.removeEnd(apiUri, "/"); - + apiUri = StringUtils.removeEnd(apiUri, "/"); +// TODO: 샘플 주석 작업 완료 후 제거 요망 +// String salt = servletRequest.getHeader("salt"); +// ElinkTransactionContext.setSeedSalt(salt); //** jwhong TSEAIHS04의 api full path와 비교하여 adapter를 가져온다 String methodAndUri = servletRequest.getMethod() + "|" + apiUri; HttpDynamicInAdapterUri adptUri = null;