diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxTemplate.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxTemplate.java new file mode 100644 index 0000000..1dc3d8d --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxTemplate.java @@ -0,0 +1,96 @@ +package com.klaro.acquirecore.framework; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.UUID; +import java.util.function.Supplier; + +/** + * Transaction template providing @Transactional equivalent functionality. + * Migrated from txcore.c tx_begin/tx_commit/tx_rollback functions. + */ +@Component +public class TxTemplate { + + /** Exception thrown when transaction execution fails. */ + public static class TxExecutionException extends RuntimeException { + private final String txId; + private final TxContext.TxState state = TxContext.TxState.FAILED; + + public TxExecutionException(String message) { super(message); this.txId = null; } + public TxExecutionException(String message, Throwable cause) { super(message, cause); this.txId = null; } + public TxExecutionException(String txId, String message) { super(message); this.txId = txId; } + public TxExecutionException(String txId, String message, Throwable cause) { super(message, cause); this.txId = txId; } + public String getTxId() { return txId; } + public TxContext.TxState getState() { return state; } + } + + @Autowired + private TxServiceRegistry registry; + + @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) + public TxResult execute(String serviceName, Supplier action) { + return execute(serviceName, new TxConfig(), action); + } + + @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) + public TxResult execute(String serviceName, TxConfig config, Supplier action) { + String txId = UUID.randomUUID().toString(); + TxContext context = new TxContext(txId, serviceName); + context.setStartTime(System.currentTimeMillis()); + context.setState(TxContext.TxState.ACTIVE); + try { + Object result = action.get(); + context.setEndTime(System.currentTimeMillis()); + context.setState(TxContext.TxState.COMMITTED); + TxResult txResult = TxResult.ok(txId); + txResult.setData(result); + txResult.setDurationMs(context.getEndTime() - context.getStartTime()); + return txResult; + } catch (Exception e) { + context.setEndTime(System.currentTimeMillis()); + context.setState(TxContext.TxState.ROLLED_BACK); + context.setErrorMessage(e.getMessage()); + TxResult txResult = TxResult.fail(txId, e.getMessage()); + txResult.setDurationMs(context.getEndTime() - context.getStartTime()); + throw new TxExecutionException(txId, "Transaction failed: " + e.getMessage(), e); + } + } + + public TxContext begin(String serviceName) { + String txId = UUID.randomUUID().toString(); + TxContext context = new TxContext(txId, serviceName); + context.setStartTime(System.currentTimeMillis()); + context.setState(TxContext.TxState.PENDING); + return context; + } + + public TxContext begin(String serviceName, TxConfig config) { + TxContext context = begin(serviceName); + context.setAttribute("config", config); + return context; + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public TxResult commit(TxContext context) { + context.setEndTime(System.currentTimeMillis()); + context.setState(TxContext.TxState.COMMITTED); + return TxResult.ok(context.getTxId()); + } + + public TxResult rollback(TxContext context, String reason) { + context.setEndTime(System.currentTimeMillis()); + context.setState(TxContext.TxState.ROLLED_BACK); + context.setErrorMessage(reason); + return TxResult.fail(context.getTxId(), reason); + } + + public TxResult executeViaRegistry(String serviceName, TxContext context) { + return registry.execute(serviceName, context); + } + + public TxServiceRegistry getRegistry() { return registry; } +}