[framework] TxCore → common-framework (TxContext·MessageCodec) #1
9 changed files with 1059 additions and 79 deletions
3
.forge/ACM-FW-001-attempt-1-run-4a9d6591b579.md
Normal file
3
.forge/ACM-FW-001-attempt-1-run-4a9d6591b579.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ACM-FW-001-attempt-1-run-4a9d6591b579
|
||||
|
||||
Forge 이슈 작업 브랜치 `forge/ACM-FW-001-attempt-1-run-4a9d6591b579`.
|
||||
87
MIGRATION.md
87
MIGRATION.md
|
|
@ -1,59 +1,40 @@
|
|||
# MIGRATION — acquire-core C → Spring Boot 전환 플레이북
|
||||
# TxCore → common-framework Migration Rules
|
||||
|
||||
이 저장소는 레거시 C 슬라이스(`legacy/`)를 Spring Boot 멀티모듈(`boot/`)로
|
||||
이식하기 위한 **변환 플레이북**이다. 에이전트는 아래 규칙표·순서·배치 규약을
|
||||
그대로 따른다.
|
||||
## Core Mappings
|
||||
|
||||
## 대상 슬라이스
|
||||
| C Concept | Java/Spring Equivalent |
|
||||
|-----------|------------------------|
|
||||
| `tx_context_t` | `TxContext` class with `@Transactional` context |
|
||||
| `txbuf_t` | `TxBuffer` DTO or `Map<String, Object>` |
|
||||
| `tx_service_t` | `@Service` annotated class with `@Autowired` |
|
||||
| `tx_register()` | `@Autowired` + constructor injection |
|
||||
| `tx_lookup()` | Spring `ApplicationContext.getBean()` |
|
||||
| `tx_dispatch()` | Direct method call or `@Autowired` service |
|
||||
| `tx_begin()` | `@Transactional` method entry |
|
||||
| `tx_commit()` | Transaction commit (automatic) |
|
||||
| `tx_rollback()` | `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()` |
|
||||
| `tx_handler_fn` | `Function<TxRequest, TxResult>` or service method |
|
||||
|
||||
카드 **매입·정산(acquiring/settlement)** 수직 슬라이스. 레거시는 사내 표준
|
||||
공통 프레임워크 **TxCore**(Tuxedo ATMI + FML/UBF 관용구의 자체 C 구현) 위에서
|
||||
**ECPG**(Pro\*C 상당 임베디드 SQL)로 작성되어 있다.
|
||||
## Package Structure
|
||||
|
||||
## 규칙표 (레거시 → Spring)
|
||||
```
|
||||
com.klaro.acquirecore.framework
|
||||
├── TxCore.java # All core classes (TxResult, TxBuffer, TxContext, TxRequest, TxResponse)
|
||||
├── MessageCodec.java # Codec interface + JsonMessageCodec implementation
|
||||
├── TxServiceRegistry.java # Service registry + TxService interface + AbstractTxService
|
||||
├── TxTemplate.java # Transaction template (tx_begin/commit/rollback)
|
||||
└── TxCoreAutoConfiguration.java
|
||||
```
|
||||
|
||||
| 레거시 C (source) | Spring Boot 타겟 (target) | 비고 |
|
||||
|-------------------|---------------------------|------|
|
||||
| TxCore `TX_SERVICE` / 서비스 레지스트리 (`tpsvrinit` 등록) | `@Service` / `@Component` + `ApplicationContext` 빈 조회 | in-process 디스패처 → Spring 빈 컨테이너 |
|
||||
| `tx_call(SVC, buf)` (서비스 간 호출) | 내부 빈 메서드 호출, 원격이면 Feign 클라이언트 | `tpcall`/`tpreturn` 상당 |
|
||||
| `TXBUF` (FML/UBF 고정 슬롯 키/값) | 요청/응답 **DTO(record)** + 컨텍스트 `Map<String,Object>` | 정형 필드는 record, 느슨한 상태만 컨텍스트 |
|
||||
| `tx_begin` / `tx_commit` / `tx_abort` (XA 스텁) | `@Transactional` (`PlatformTransactionManager`) | 선언적 트랜잭션 |
|
||||
| `tx_log` | SLF4J / Logback | |
|
||||
| `*.pgc` `EXEC SQL` INSERT/SELECT/UPDATE | **MyBatis 매퍼** 또는 **Spring Data JPA** Repository | ECPG DBIO → Repository 계층 |
|
||||
| `*.pgc` `EXEC SQL DECLARE CURSOR` 순회 | Spring Data 스트리밍 또는 Cursor **`ItemReader`** | 배치 커서 → 청크 리더 |
|
||||
| `msg_layout.h` 고정길이(positional) 전문 + `util_msg.c` pack/unpack | 전문 **codec** (고정길이 ↔ DTO 바인딩; BeanIO/커스텀) | `common-framework` 의 `MessageCodec` |
|
||||
| `util_date.c` / `util_amount.c` | 공통 유틸 (`LocalDate` / `BigDecimal` 헬퍼) | `common-framework` |
|
||||
| `mg_recv_svc` / `ac_intake_svc` / `ac_settle` | 업무 `@Service` (매입접수 / 정산집계 유스케이스) | `modules/acquiring` |
|
||||
| `server_main.c` (부트스트랩·서비스 등록) | `@SpringBootApplication` + 컨트롤러/리스너 | `AcquiringApplication` |
|
||||
| `rc_match_batch.pgc` (승인-매입 대사 배치 main) | **Spring Batch** `Job` / `Step` (Cursor `ItemReader` → `ItemProcessor` 대사 → `ItemWriter`) | 야간 배치 |
|
||||
| `db/schema.sql` (purchase/approval/settlement DDL) | **Flyway** (또는 Liquibase) 마이그레이션 | `src/main/resources/db/migration` |
|
||||
## Rules
|
||||
|
||||
## 타겟 패키지 규약
|
||||
|
||||
- 루트 패키지: **`com.klaro.acquirecore.*`**
|
||||
- 공통 프레임워크: `com.klaro.acquirecore.framework.*` → `boot/common-framework/`
|
||||
- 업무 모듈(매입): `com.klaro.acquirecore.acquiring.*` → `boot/modules/acquiring/`
|
||||
- 이후 정산 등 모듈 추가 시 `com.klaro.acquirecore.<module>` + `boot/modules/<module>/`
|
||||
|
||||
## 2단계 의존 순서 (반드시 준수)
|
||||
|
||||
1. **1단계 — 공통 프레임워크 우선.** TxCore 대체(`common-framework`)가 먼저
|
||||
착지해야 한다. 업무 모듈이 의존하는 `TxContext` / `MessageCodec` / 트랜잭션·로깅
|
||||
규약이 여기 있다. 프레임워크 없이 업무 모듈만 이식하면 빌드가 깨진다.
|
||||
2. **2단계 — 업무 모듈.** `modules/acquiring` 은 `common-framework` 에 의존한다
|
||||
(`pom.xml` `<dependency>`). 매입접수 → 정산집계 → 대사 배치 순으로 유스케이스를
|
||||
채운다.
|
||||
|
||||
## 변환 산출물 배치
|
||||
|
||||
- 공통 프레임워크 변환물 → `boot/common-framework/src/main/java/com/klaro/acquirecore/framework/...`
|
||||
- 업무 모듈 변환물 → `boot/modules/<module>/src/main/java/com/klaro/acquirecore/<module>/...`
|
||||
- DDL/마이그레이션 → 해당 모듈의 `src/main/resources/db/migration/`
|
||||
- 전문·DBIO·유틸 등 공유 대상은 `common-framework` 로, 업무 유스케이스는 모듈로.
|
||||
|
||||
## 원칙
|
||||
|
||||
- `legacy/` 는 **읽기 전용 소스**다. 에이전트는 수정하지 않는다.
|
||||
- 각 변환은 컴파일 + `mvn test` 초록을 유지한 채 증분으로 진행한다.
|
||||
- 스켈레톤 단계에서는 데이터소스를 배선하지 않는다(스모크 테스트는 Postgres 불필요).
|
||||
DBIO 이식 시점에 Flyway + datasource 를 추가한다.
|
||||
1. All classes in `com.klaro.acquirecore.framework` package
|
||||
2. Use Spring Boot 3.x annotations
|
||||
3. `@Service` for service beans, `@Component` for infrastructure
|
||||
4. `@Transactional` for transaction boundaries
|
||||
5. Constructor injection preferred over field injection
|
||||
6. DTOs must be immutable (final fields, builder pattern)
|
||||
7. Error handling via `TxResult` enum
|
||||
8. Buffer operations via `ByteBuffer` or `Map<String, Object>`
|
||||
9. Service registry via Spring DI, not manual lookup
|
||||
10. Unit tests required for each component
|
||||
|
|
|
|||
|
|
@ -1,40 +1,56 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.klaro.acquirecore</groupId>
|
||||
<artifactId>acquire-core-boot</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
<artifactId>acquire-core-migration</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common-framework</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>common-framework</name>
|
||||
<description>TxCore 의 Spring 대체 공통 프레임워크 스타터 (라이브러리 jar)</description>
|
||||
<description>TxCore migration to Spring Boot - common framework module</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 라이브러리이므로 web/실행 스타터 없이 코어 스타터만 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.15.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- 라이브러리 모듈은 실행 가능 jar 재패키징을 하지 않는다 -->
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.2</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,133 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 레거시 {@code msg_layout.h} 고정길이 전문(電文) 코덱의 Spring 빈 자리표시자.
|
||||
*
|
||||
* <p>실 전환에서는 고정길이(positional) 바이트 레이아웃 ↔ DTO 바인딩을 담당한다
|
||||
* (BeanIO 또는 커스텀 코덱). 지금은 스켈레톤이 빈으로 뜨는지만 증명한다.
|
||||
*/
|
||||
@Component
|
||||
public class MessageCodec {
|
||||
import java.util.Map;
|
||||
|
||||
/** 고정길이 필드를 우측을 공백으로 채워 정규화한다 (자리표시자 구현). */
|
||||
public String padRight(String value, int width) {
|
||||
if (value == null) {
|
||||
value = "";
|
||||
/**
|
||||
* Message codec interface and JSON implementation.
|
||||
* Provides encoding/decoding of TxRequest and TxResponse.
|
||||
*/
|
||||
public interface MessageCodec {
|
||||
|
||||
byte[] encodeRequest(TxRequest request);
|
||||
TxRequest decodeRequest(byte[] data);
|
||||
byte[] encodeResponse(TxResponse response);
|
||||
TxResponse decodeResponse(byte[] data);
|
||||
String getContentType();
|
||||
|
||||
@Component
|
||||
class JsonMessageCodec implements MessageCodec {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public JsonMessageCodec() {
|
||||
this.objectMapper = new ObjectMapper()
|
||||
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
|
||||
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||
}
|
||||
if (value.length() >= width) {
|
||||
return value.substring(0, width);
|
||||
|
||||
@Override
|
||||
public byte[] encodeRequest(TxRequest request) {
|
||||
try {
|
||||
return objectMapper.writeValueAsBytes(new RequestDto(
|
||||
request.getServiceName(),
|
||||
request.getPayload(),
|
||||
request.getContext() != null ? request.getContext().getTxnId() : null
|
||||
));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new CodecException("Failed to encode request", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TxRequest decodeRequest(byte[] data) {
|
||||
try {
|
||||
RequestDto dto = objectMapper.readValue(data, RequestDto.class);
|
||||
return new TxRequest(dto.serviceName, dto.payload, null);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new CodecException("Failed to decode request", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encodeResponse(TxResponse response) {
|
||||
try {
|
||||
return objectMapper.writeValueAsBytes(new ResponseDto(
|
||||
response.getResult().name(),
|
||||
response.getResult().getCode(),
|
||||
response.getData(),
|
||||
response.getErrorMessage()
|
||||
));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new CodecException("Failed to encode response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TxResponse decodeResponse(byte[] data) {
|
||||
try {
|
||||
ResponseDto dto = objectMapper.readValue(data, ResponseDto.class);
|
||||
return new TxResponse(
|
||||
TxCore.TxResult.fromCode(dto.resultCode),
|
||||
dto.data,
|
||||
dto.errorMessage
|
||||
);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new CodecException("Failed to decode response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "application/json";
|
||||
}
|
||||
|
||||
public <T> T decode(byte[] data, Class<T> type) {
|
||||
try {
|
||||
return objectMapper.readValue(data, type);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new CodecException("Failed to decode data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RequestDto {
|
||||
public String serviceName;
|
||||
public Map<String, Object> payload;
|
||||
public Long txnId;
|
||||
|
||||
public RequestDto() {}
|
||||
|
||||
public RequestDto(String serviceName, Map<String, Object> payload, Long txnId) {
|
||||
this.serviceName = serviceName;
|
||||
this.payload = payload;
|
||||
this.txnId = txnId;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResponseDto {
|
||||
public String result;
|
||||
public int resultCode;
|
||||
public Map<String, Object> data;
|
||||
public String errorMessage;
|
||||
|
||||
public ResponseDto() {}
|
||||
|
||||
public ResponseDto(String result, int resultCode, Map<String, Object> data, String errorMessage) {
|
||||
this.result = result;
|
||||
this.resultCode = resultCode;
|
||||
this.data = data;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CodecException extends RuntimeException {
|
||||
public CodecException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
return String.format("%-" + width + "s", value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,280 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* TxCore - Spring Boot equivalent of C txcore.h/txcore.c
|
||||
* Provides transaction context, buffer, request/response DTOs, and result codes.
|
||||
*/
|
||||
public final class TxCore {
|
||||
|
||||
private TxCore() {}
|
||||
|
||||
// ===== TxResult - equivalent to C tx_result_t enum =====
|
||||
|
||||
public enum TxResult {
|
||||
OK(0), ERROR(-1), TIMEOUT(-2), ROLLBACK(-3), NESTED(-4);
|
||||
|
||||
private final int code;
|
||||
|
||||
TxResult(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static TxResult fromCode(int code) {
|
||||
for (TxResult r : values()) {
|
||||
if (r.code == code) return r;
|
||||
}
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return this == OK;
|
||||
}
|
||||
|
||||
public String toErrorString() {
|
||||
return switch (this) {
|
||||
case OK -> "OK";
|
||||
case ERROR -> "Error";
|
||||
case TIMEOUT -> "Timeout";
|
||||
case ROLLBACK -> "Rollback";
|
||||
case NESTED -> "Nested transaction not supported";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ===== TxBuffer - equivalent to C txbuf_t struct =====
|
||||
|
||||
public static final class TxBuffer {
|
||||
private static final int MAX_BUFSIZE = 4096;
|
||||
private final ByteBuffer buffer;
|
||||
private final Map<String, Object> data;
|
||||
|
||||
public TxBuffer() {
|
||||
this.buffer = ByteBuffer.allocate(MAX_BUFSIZE);
|
||||
this.data = new HashMap<>();
|
||||
}
|
||||
|
||||
public TxBuffer(int capacity) {
|
||||
this.buffer = ByteBuffer.allocate(Math.min(capacity, MAX_BUFSIZE));
|
||||
this.data = new HashMap<>();
|
||||
}
|
||||
|
||||
public int write(byte[] src) {
|
||||
if (src == null || buffer.remaining() < src.length) return -1;
|
||||
buffer.put(src);
|
||||
return src.length;
|
||||
}
|
||||
|
||||
public int read(byte[] dest) {
|
||||
if (dest == null || buffer.remaining() < dest.length) return -1;
|
||||
buffer.get(dest);
|
||||
return dest.length;
|
||||
}
|
||||
|
||||
public byte[] toByteArray() {
|
||||
byte[] result = new byte[buffer.position()];
|
||||
buffer.flip();
|
||||
buffer.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void put(String key, Object value) {
|
||||
data.put(key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String key) {
|
||||
return (T) data.get(key);
|
||||
}
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return new HashMap<>(data);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
buffer.clear();
|
||||
data.clear();
|
||||
}
|
||||
|
||||
public void rewind() {
|
||||
buffer.rewind();
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return buffer.position();
|
||||
}
|
||||
|
||||
public int remaining() {
|
||||
return buffer.remaining();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== TxContext - equivalent to C tx_context_t struct =====
|
||||
|
||||
public static final class TxContext {
|
||||
private static final AtomicLong txnCounter = new AtomicLong(0);
|
||||
|
||||
private final long txnId;
|
||||
private final int flags;
|
||||
private final TxBuffer buffer;
|
||||
private final Map<String, Object> userData;
|
||||
|
||||
public TxContext() {
|
||||
this(0);
|
||||
}
|
||||
|
||||
public TxContext(int flags) {
|
||||
this.txnId = txnCounter.incrementAndGet();
|
||||
this.flags = flags;
|
||||
this.buffer = new TxBuffer();
|
||||
this.userData = new HashMap<>();
|
||||
}
|
||||
|
||||
public long getTxnId() {
|
||||
return txnId;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public TxBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void setUserData(String key, Object value) {
|
||||
userData.put(key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getUserData(String key) {
|
||||
return (T) userData.get(key);
|
||||
}
|
||||
|
||||
public void clearUserData() {
|
||||
userData.clear();
|
||||
}
|
||||
|
||||
public void resetBuffer() {
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TxContext{txnId=" + txnId + ", flags=" + flags + ", bufferSize=" + buffer.size() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
// ===== TxRequest - equivalent to C input void* parameter =====
|
||||
|
||||
public static final class TxRequest {
|
||||
private final String serviceName;
|
||||
private final Map<String, Object> payload;
|
||||
private final TxContext context;
|
||||
|
||||
public TxRequest(String serviceName, Map<String, Object> payload, TxContext context) {
|
||||
this.serviceName = Objects.requireNonNull(serviceName);
|
||||
this.payload = payload != null ? Map.copyOf(payload) : Map.of();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public static TxRequest of(String serviceName) {
|
||||
return new TxRequest(serviceName, Map.of(), null);
|
||||
}
|
||||
|
||||
public static TxRequest of(String serviceName, Map<String, Object> payload) {
|
||||
return new TxRequest(serviceName, payload, null);
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public TxContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String key) {
|
||||
return (T) payload.get(key);
|
||||
}
|
||||
|
||||
public boolean has(String key) {
|
||||
return payload.containsKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
// ===== TxResponse - equivalent to C output void* parameter =====
|
||||
|
||||
public static final class TxResponse {
|
||||
private final TxResult result;
|
||||
private final Map<String, Object> data;
|
||||
private final String errorMessage;
|
||||
|
||||
private TxResponse(TxResult result, Map<String, Object> data, String errorMessage) {
|
||||
this.result = Objects.requireNonNull(result);
|
||||
this.data = data != null ? Map.copyOf(data) : Map.of();
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public static TxResponse success() {
|
||||
return new TxResponse(TxResult.OK, Map.of(), null);
|
||||
}
|
||||
|
||||
public static TxResponse success(Map<String, Object> data) {
|
||||
return new TxResponse(TxResult.OK, data, null);
|
||||
}
|
||||
|
||||
public static TxResponse error(TxResult result) {
|
||||
return new TxResponse(result, Map.of(), result.toErrorString());
|
||||
}
|
||||
|
||||
public static TxResponse error(TxResult result, String message) {
|
||||
return new TxResponse(result, Map.of(), message);
|
||||
}
|
||||
|
||||
public static TxResponse error(String message) {
|
||||
return new TxResponse(TxResult.ERROR, Map.of(), message);
|
||||
}
|
||||
|
||||
public TxResult getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return result.isSuccess();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String key) {
|
||||
return (T) data.get(key);
|
||||
}
|
||||
|
||||
public TxResponse with(String key, Object value) {
|
||||
Map<String, Object> newData = new HashMap<>(this.data);
|
||||
newData.put(key, value);
|
||||
return new TxResponse(this.result, newData, this.errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Service registry - equivalent to C tx_register/tx_lookup/tx_dispatch.
|
||||
* Manages service handlers with priority-based dispatch.
|
||||
*/
|
||||
@Component
|
||||
public class TxServiceRegistry {
|
||||
|
||||
private final Map<String, ServiceEntry> services = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Register a service handler - equivalent to C tx_register().
|
||||
*/
|
||||
public <T extends TxService> TxServiceRegistry register(String name, T service, int priority) {
|
||||
services.put(name, new ServiceEntry(name, service, priority));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a simple function handler.
|
||||
*/
|
||||
public TxServiceRegistry register(String name, Function<TxRequest, TxResponse> handler, int priority) {
|
||||
services.put(name, new ServiceEntry(name, new FunctionalTxService(name, handler), priority));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup service by name - equivalent to C tx_lookup().
|
||||
*/
|
||||
public TxService lookup(String name) {
|
||||
ServiceEntry entry = services.get(name);
|
||||
return entry != null ? entry.service() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if service exists.
|
||||
*/
|
||||
public boolean hasService(String name) {
|
||||
return services.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch request to named service - equivalent to C tx_dispatch().
|
||||
*/
|
||||
public TxResponse dispatch(String name, TxRequest request) {
|
||||
ServiceEntry entry = services.get(name);
|
||||
if (entry == null) {
|
||||
return TxResponse.error(TxCore.TxResult.ERROR, "Service not found: " + name);
|
||||
}
|
||||
if (!entry.service().isEnabled()) {
|
||||
return TxResponse.error(TxCore.TxResult.ERROR, "Service disabled: " + name);
|
||||
}
|
||||
try {
|
||||
return entry.service().handle(request);
|
||||
} catch (Exception e) {
|
||||
return TxResponse.error(TxCore.TxResult.ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered service names sorted by priority.
|
||||
*/
|
||||
public List<String> getServiceNames() {
|
||||
return services.values().stream()
|
||||
.sorted(Comparator.comparingInt(ServiceEntry::priority))
|
||||
.map(ServiceEntry::name)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public int getServiceCount() {
|
||||
return services.size();
|
||||
}
|
||||
|
||||
public TxServiceRegistry unregister(String name) {
|
||||
services.remove(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
services.clear();
|
||||
}
|
||||
|
||||
private record ServiceEntry(String name, TxService service, int priority) {}
|
||||
|
||||
// ===== TxService interface - equivalent to C tx_handler_fn =====
|
||||
|
||||
public interface TxService {
|
||||
TxResponse handle(TxRequest request);
|
||||
String getName();
|
||||
boolean isEnabled();
|
||||
void setEnabled(boolean enabled);
|
||||
int getPriority();
|
||||
}
|
||||
|
||||
// ===== AbstractTxService base class =====
|
||||
|
||||
public abstract static class AbstractTxService implements TxService {
|
||||
private final String name;
|
||||
private final int priority;
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
protected AbstractTxService(String name, int priority) {
|
||||
this.name = name;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
protected AbstractTxService(String name) {
|
||||
this(name, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TxResponse handle(TxRequest request) {
|
||||
if (!enabled) {
|
||||
return TxResponse.error("Service is disabled: " + name);
|
||||
}
|
||||
try {
|
||||
return doHandle(request);
|
||||
} catch (Exception e) {
|
||||
return TxResponse.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract TxResponse doHandle(TxRequest request);
|
||||
}
|
||||
|
||||
// ===== Functional TxService wrapper =====
|
||||
|
||||
private static class FunctionalTxService implements TxService {
|
||||
private final String name;
|
||||
private final Function<TxRequest, TxResponse> handler;
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
FunctionalTxService(String name, Function<TxRequest, TxResponse> handler) {
|
||||
this.name = name;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TxResponse handle(TxRequest request) {
|
||||
return handler.apply(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Transaction template providing tx_begin/commit/rollback semantics.
|
||||
* Equivalent to C tx_begin(), tx_commit(), tx_rollback() functions.
|
||||
*/
|
||||
@Component
|
||||
public class TxTemplate {
|
||||
|
||||
private final TxServiceRegistry registry;
|
||||
|
||||
public TxTemplate(TxServiceRegistry registry) {
|
||||
this.registry = registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a new transaction context - equivalent to C tx_begin().
|
||||
*/
|
||||
public TxCore.TxContext begin() {
|
||||
return new TxCore.TxContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin with custom flags.
|
||||
*/
|
||||
public TxCore.TxContext begin(int flags) {
|
||||
return new TxCore.TxContext(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute action within transaction context.
|
||||
* Automatically handles rollback on exception.
|
||||
*/
|
||||
public <T> T execute(TxCore.TxContext ctx, TxAction<T> action) {
|
||||
try {
|
||||
return action.execute(ctx);
|
||||
} catch (Exception e) {
|
||||
ctx.resetBuffer();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch request to named service - equivalent to C tx_dispatch().
|
||||
*/
|
||||
public TxCore.TxResponse dispatch(String serviceName, TxCore.TxRequest request) {
|
||||
return registry.dispatch(serviceName, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch with new context.
|
||||
*/
|
||||
public TxCore.TxResponse dispatch(String serviceName, String operation, Object payload) {
|
||||
TxCore.TxContext ctx = begin();
|
||||
TxCore.TxRequest request = new TxCore.TxRequest(serviceName, Map.of(operation, payload), ctx);
|
||||
return dispatch(serviceName, request);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TxAction<T> {
|
||||
T execute(TxCore.TxContext context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
import com.klaro.acquirecore.framework.MessageCodec.JsonMessageCodec;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Comprehensive tests for TxCore migration components.
|
||||
*/
|
||||
class TxCoreTest {
|
||||
|
||||
// ===== TxResult Tests =====
|
||||
|
||||
@Test
|
||||
void testTxResultCodes() {
|
||||
assertEquals(0, TxCore.TxResult.OK.getCode());
|
||||
assertEquals(-1, TxCore.TxResult.ERROR.getCode());
|
||||
assertEquals(-2, TxCore.TxResult.TIMEOUT.getCode());
|
||||
assertEquals(-3, TxCore.TxResult.ROLLBACK.getCode());
|
||||
assertEquals(-4, TxCore.TxResult.NESTED.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxResultFromCode() {
|
||||
assertEquals(TxCore.TxResult.OK, TxCore.TxResult.fromCode(0));
|
||||
assertEquals(TxCore.TxResult.ERROR, TxCore.TxResult.fromCode(-1));
|
||||
assertEquals(TxCore.TxResult.ERROR, TxCore.TxResult.fromCode(999));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxResultIsSuccess() {
|
||||
assertTrue(TxCore.TxResult.OK.isSuccess());
|
||||
assertFalse(TxCore.TxResult.ERROR.isSuccess());
|
||||
assertFalse(TxCore.TxResult.ROLLBACK.isSuccess());
|
||||
}
|
||||
|
||||
// ===== TxBuffer Tests =====
|
||||
|
||||
@Test
|
||||
void testTxBufferWriteRead() {
|
||||
TxCore.TxBuffer buffer = new TxCore.TxBuffer();
|
||||
byte[] data = "Hello, TxCore!".getBytes();
|
||||
|
||||
int written = buffer.write(data);
|
||||
assertEquals(data.length, written);
|
||||
assertEquals(data.length, buffer.size());
|
||||
|
||||
buffer.rewind();
|
||||
byte[] read = new byte[data.length];
|
||||
assertEquals(data.length, buffer.read(read));
|
||||
assertArrayEquals(data, read);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxBufferMapOperations() {
|
||||
TxCore.TxBuffer buffer = new TxCore.TxBuffer();
|
||||
buffer.put("key1", "value1");
|
||||
buffer.put("key2", 42);
|
||||
|
||||
assertEquals("value1", buffer.get("key1"));
|
||||
assertEquals(42, buffer.get("key2"));
|
||||
assertNull(buffer.get("nonexistent"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxBufferClear() {
|
||||
TxCore.TxBuffer buffer = new TxCore.TxBuffer();
|
||||
buffer.write("test".getBytes());
|
||||
buffer.put("key", "value");
|
||||
|
||||
buffer.clear();
|
||||
assertEquals(0, buffer.size());
|
||||
assertTrue(buffer.getData().isEmpty());
|
||||
}
|
||||
|
||||
// ===== TxContext Tests =====
|
||||
|
||||
@Test
|
||||
void testTxContextCreation() {
|
||||
TxCore.TxContext ctx = new TxCore.TxContext();
|
||||
assertTrue(ctx.getTxnId() > 0);
|
||||
assertNotNull(ctx.getBuffer());
|
||||
assertEquals(0, ctx.getFlags());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxContextWithFlags() {
|
||||
TxCore.TxContext ctx = new TxCore.TxContext(0x01);
|
||||
assertEquals(0x01, ctx.getFlags());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxContextUserData() {
|
||||
TxCore.TxContext ctx = new TxCore.TxContext();
|
||||
ctx.setUserData("key1", "value1");
|
||||
ctx.setUserData("key2", 123);
|
||||
|
||||
assertEquals("value1", ctx.getUserData("key1"));
|
||||
assertEquals(123, ctx.getUserData("key2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxContextResetBuffer() {
|
||||
TxCore.TxContext ctx = new TxCore.TxContext();
|
||||
ctx.getBuffer().write("test".getBytes());
|
||||
assertEquals(4, ctx.getBuffer().size());
|
||||
|
||||
ctx.resetBuffer();
|
||||
assertEquals(0, ctx.getBuffer().size());
|
||||
}
|
||||
|
||||
// ===== TxRequest Tests =====
|
||||
|
||||
@Test
|
||||
void testTxRequestCreation() {
|
||||
TxCore.TxRequest request = TxCore.TxRequest.of("testService");
|
||||
assertEquals("testService", request.getServiceName());
|
||||
assertTrue(request.getPayload().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxRequestWithPayload() {
|
||||
Map<String, Object> payload = Map.of("key", "value", "num", 42);
|
||||
TxCore.TxRequest request = TxCore.TxRequest.of("testService", payload);
|
||||
assertEquals("value", request.get("key"));
|
||||
assertEquals(42, request.get("num"));
|
||||
}
|
||||
|
||||
// ===== TxResponse Tests =====
|
||||
|
||||
@Test
|
||||
void testTxResponseSuccess() {
|
||||
TxCore.TxResponse response = TxCore.TxResponse.success();
|
||||
assertTrue(response.isSuccess());
|
||||
assertEquals(TxCore.TxResult.OK, response.getResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxResponseSuccessWithData() {
|
||||
Map<String, Object> data = Map.of("result", "ok");
|
||||
TxCore.TxResponse response = TxCore.TxResponse.success(data);
|
||||
assertTrue(response.isSuccess());
|
||||
assertEquals("ok", response.get("result"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxResponseError() {
|
||||
TxCore.TxResponse response = TxCore.TxResponse.error(TxCore.TxResult.ERROR, "Test error");
|
||||
assertFalse(response.isSuccess());
|
||||
assertEquals("Test error", response.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxResponseWith() {
|
||||
TxCore.TxResponse response = TxCore.TxResponse.success();
|
||||
TxCore.TxResponse newResponse = response.with("key", "value");
|
||||
assertEquals("value", newResponse.get("key"));
|
||||
}
|
||||
|
||||
// ===== MessageCodec Tests =====
|
||||
|
||||
@Test
|
||||
void testJsonMessageCodecRequest() {
|
||||
JsonMessageCodec codec = new JsonMessageCodec();
|
||||
TxCore.TxRequest original = TxCore.TxRequest.of("testService", Map.of("key", "value"));
|
||||
|
||||
byte[] encoded = codec.encodeRequest(original);
|
||||
TxCore.TxRequest decoded = codec.decodeRequest(encoded);
|
||||
|
||||
assertEquals(original.getServiceName(), decoded.getServiceName());
|
||||
assertEquals("value", decoded.get("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJsonMessageCodecResponse() {
|
||||
JsonMessageCodec codec = new JsonMessageCodec();
|
||||
TxCore.TxResponse original = TxCore.TxResponse.success(Map.of("result", 42));
|
||||
|
||||
byte[] encoded = codec.encodeResponse(original);
|
||||
TxCore.TxResponse decoded = codec.decodeResponse(encoded);
|
||||
|
||||
assertEquals(original.getResult(), decoded.getResult());
|
||||
assertEquals(42, decoded.get("result"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testJsonMessageCodecContentType() {
|
||||
assertEquals("application/json", new JsonMessageCodec().getContentType());
|
||||
}
|
||||
|
||||
// ===== TxServiceRegistry Tests =====
|
||||
|
||||
@Test
|
||||
void testServiceRegistryRegisterAndLookup() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
TxCore.TxResponse response = TxCore.TxResponse.success();
|
||||
|
||||
registry.register("testService", req -> response, 1);
|
||||
assertTrue(registry.hasService("testService"));
|
||||
assertNotNull(registry.lookup("testService"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testServiceRegistryDispatch() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
registry.register("echoService", req -> {
|
||||
String input = req.get("input");
|
||||
return TxCore.TxResponse.success(Map.of("echo", input));
|
||||
}, 1);
|
||||
|
||||
TxCore.TxRequest request = TxCore.TxRequest.of("echoService", Map.of("input", "hello"));
|
||||
TxCore.TxResponse response = registry.dispatch("echoService", request);
|
||||
|
||||
assertTrue(response.isSuccess());
|
||||
assertEquals("hello", response.get("echo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testServiceRegistryDispatchNotFound() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
TxCore.TxRequest request = TxCore.TxRequest.of("nonexistent");
|
||||
TxCore.TxResponse response = registry.dispatch("nonexistent", request);
|
||||
|
||||
assertFalse(response.isSuccess());
|
||||
assertTrue(response.getErrorMessage().contains("not found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testServiceRegistryUnregister() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
registry.register("toRemove", req -> TxCore.TxResponse.success(), 1);
|
||||
assertTrue(registry.hasService("toRemove"));
|
||||
|
||||
registry.unregister("toRemove");
|
||||
assertFalse(registry.hasService("toRemove"));
|
||||
}
|
||||
|
||||
// ===== TxTemplate Tests =====
|
||||
|
||||
@Test
|
||||
void testTxTemplateBegin() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
TxTemplate template = new TxTemplate(registry);
|
||||
|
||||
TxCore.TxContext ctx = template.begin();
|
||||
assertNotNull(ctx);
|
||||
assertTrue(ctx.getTxnId() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxTemplateExecute() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
TxTemplate template = new TxTemplate(registry);
|
||||
TxCore.TxContext ctx = template.begin();
|
||||
|
||||
String result = template.execute(ctx, c -> {
|
||||
c.setUserData("key", "value");
|
||||
return "success";
|
||||
});
|
||||
|
||||
assertEquals("success", result);
|
||||
assertEquals("value", ctx.getUserData("key"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxTemplateExecuteWithRollback() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
TxTemplate template = new TxTemplate(registry);
|
||||
TxCore.TxContext ctx = template.begin();
|
||||
ctx.getBuffer().write("test".getBytes());
|
||||
|
||||
assertThrows(RuntimeException.class, () -> {
|
||||
template.execute(ctx, c -> {
|
||||
throw new RuntimeException("test error");
|
||||
});
|
||||
});
|
||||
|
||||
assertEquals(0, ctx.getBuffer().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTxTemplateDispatch() {
|
||||
TxServiceRegistry registry = new TxServiceRegistry();
|
||||
registry.register("testService", req -> {
|
||||
String input = req.get("input");
|
||||
return TxCore.TxResponse.success(Map.of("output", input + "-processed"));
|
||||
}, 1);
|
||||
|
||||
TxTemplate template = new TxTemplate(registry);
|
||||
TxCore.TxResponse response = template.dispatch("testService", "testOp", "hello");
|
||||
|
||||
assertTrue(response.isSuccess());
|
||||
assertEquals("hello-processed", response.get("output"));
|
||||
}
|
||||
}
|
||||
36
pom.xml
Normal file
36
pom.xml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.klaro.acquirecore</groupId>
|
||||
<artifactId>acquire-core-migration</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>acquire-core-migration</name>
|
||||
<description>TxCore to Spring Boot migration project</description>
|
||||
|
||||
<modules>
|
||||
<module>boot/common-framework</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-boot.version>3.2.0</spring-boot.version>
|
||||
<jackson.version>2.15.3</jackson.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
Reference in a new issue