Merge branch 'jenkins_with_weblogic' of http://192.168.240.178:18080/eapim/eapim-online.git into jenkins_with_weblogic

This commit is contained in:
cho
2026-01-08 11:12:35 +09:00
5 changed files with 809 additions and 5 deletions
+500
View File
@@ -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/**` 엔드포인트 매핑<br>- TSEAIHS04 테이블에서 어댑터 정보 조회<br>- HttpDynamicInAdapterManager로 어댑터 찾기<br>- ApiAdapterService.callApi() 호출 |
### 2. ApiAdapterService
| 항목 | 내용 |
|------|------|
| 위치 | `eapim-online/src/main/java/com/eactive/eai/adapter/service/` |
| 역할 | Inbound 서비스 레이어 |
| 상속 | `HttpAdapterServiceSupport` |
| 주요 기능 | - HTTP Method별 요청 파싱<br>- Inbound 암/복호화 (AES128, AES256 등)<br>- 부모 클래스 `service()` 호출 |
### 3. HttpAdapterServiceSupport
| 항목 | 내용 |
|------|------|
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/dynamic/` |
| 역할 | Inbound 어댑터 추상 클래스 |
| 주요 기능 | - `service()` 메소드: 핵심 디스패처<br>- Pre-Filter / Post-Filter 실행<br>- StandardMessage 생성<br>- RequestDispatcher.handle() 호출 |
### 4. RequestDispatcher
| 항목 | 내용 |
|------|------|
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/` |
| 역할 | 요청 분배자 |
| 주요 기능 | - Processor 인스턴스 생성/획득<br>- `processor.execute()` 호출<br>- AdapterGroup 상태 관리 (동시 처리 수 증감) |
### 5. RequestProcessorSupport / RequestProcessor
| 항목 | 내용 |
|------|------|
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/inbound/processor/` |
| 역할 | 요청 처리 프로세서 |
| 관계 | `RequestProcessor extends RequestProcessorSupport` |
| 주요 기능 | - `execute()``hookup()` 호출<br>- StandardMessage → EAIMessage 변환<br>- 유량제어 (RateLimiter)<br>- SYNC/ASYNC 분기 처리<br>- FlowRouter.process() 호출 |
### 6. IFRouter
| 항목 | 내용 |
|------|------|
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/common/routing/` |
| 역할 | FlowControl → Outbound 라우터 |
| 주요 기능 | - Outbound Process로 라우팅<br>- Local/Remote 호출 판단<br>- FailOver 처리<br>- TAS(시뮬레이터) 거래 처리<br>- ElinkESBProcessProxy 호출 |
### 7. DefaultProcess / RESTProcess
| 항목 | 내용 |
|------|------|
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/outbound/` |
| 역할 | Outbound 처리 프로세스 |
| 관계 | `RESTProcess extends HTTPProcess extends DefaultProcess` |
| 주요 기능 (DefaultProcess) | - `flow()`: 핵심 처리 흐름<br>- 송신 전/후 처리<br>- 메시지 변환 (Transform)<br>- 로그 처리 |
| 주요 기능 (RESTProcess) | - `outboundCtrlCallService()`: HttpSender 호출<br>- REST 특화 처리<br>- 표준/비표준 메시지 처리 |
### 8. HttpSender
| 항목 | 내용 |
|------|------|
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/control/` |
| 역할 | HTTP 송신 제어 |
| 주요 기능 | - ElinkAdapterFactory에서 HTTP Adapter 획득<br>- `adapter.callService()` 호출<br>- SocketTimeout, HttpStatusException 처리 |
### 9. HttpClient5AdapterServiceRest
| 항목 | 내용 |
|------|------|
| 위치 | `elink-online-common/src/main/java/com/eactive/eai/adapter/http/client/impl/` |
| 역할 | 실제 HTTP 클라이언트 구현체 |
| 주요 기능 | - Apache HttpClient 5 사용<br>- GET/POST/PUT/DELETE 메소드 처리<br>- OAuth2 토큰 관리 (발급/갱신)<br>- Outbound 암/복호화 (AES128, AES256, AES256GCM 등)<br>- Forward Proxy 지원<br>- 응답 헤더 릴레이 |
---
## 클래스 관계도
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 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
+300
View File
@@ -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<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>();
// 컨텍스트 정리 (필수)
public static void clear();
// 비동기 전달용
public static Map<String, Object> getAll(); // 복사본 반환
public static void restore(Map<String, Object> 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<String, Object> transactionContextTransfer;
public Map<String, Object> getTransactionContextTransfer();
public void setTransactionContextTransfer(Map<String, Object> 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<Map<String,Object>>│◄── 직접 저장소 (Lazy Init)
│ └───────────────────────────────┘ │
│ + clear() │
│ + getAll() → Map 복사본 │
│ + restore(Map) │
│ + setSeedSalt() / getSeedSalt() │
│ + setEncryptionKey() / ... │
└───────────────┬─────────────────────┘
│ 비동기 전달 시
│ getAll() → 복사
┌─────────────────────────────────────┐
│ elink-online-common │
├─────────────────────────────────────┤
│ EAIMessage │
│ - transactionContextTransfer │◄── JMS 전달 전용 (직접 사용 금지!)
│ : Map<String,Object> │ Serializable
│ │
│ [송신] FlowRouter에서 설정 │
│ [수신] ElinkESBProcessProxy에서 │
│ restore() 후 null 초기화 │
└─────────────────────────────────────┘
```
@@ -54,14 +54,16 @@ class OAuth2BearerAuthTest {
}
// Step 2: Bearer 토큰으로 인증 API 호출
String apiUrl = TestConfig.getBaseUrl("/api/auth/case009?transactionId=1234567890");
String apiUrl = TestConfig.getBaseUrl("/api/auth/case009?transactionId=1234567890&parameterTest1=parameterTestValue1&parameterTest2=parameterTestValue2");
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(apiUrl);
httpPost.setHeader("Authorization", "Bearer " + accessToken);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setEntity(new StringEntity("{}", ContentType.APPLICATION_JSON));
httpPost.setEntity(new StringEntity("{}", ContentType.APPLICATION_JSON));
String responseBody = httpClient.execute(httpPost, response -> {
String body = EntityUtils.toString(response.getEntity());
System.out.println("Response Body: " + body);
@@ -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;
@@ -87,7 +87,7 @@ public class StandardMessageCoordinatorKJB extends DefaultStandardMessageCoordin
//전문요청일자
standardMessage.setData("Common.BizProc_Info.MESG_DMAN_DT", DatetimeUtil.getCurrentDate());
//전문요청시간
standardMessage.setData("Common.BizProc_Info.MESG_DMAN_TKTM", DatetimeUtil.getTime8());
standardMessage.setData("Common.BizProc_Info.MESG_DMAN_TKTM", DatetimeUtil.getFormattedDate("HHmmssSSS"));
//2012-07-24 임의 로 영업일자에 system시간 설정