001/* 002 * Copyright 2015-2022 Transmogrify LLC, 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.pyranid; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.Immutable; 023import java.util.Optional; 024 025import static java.util.Objects.requireNonNull; 026 027/** 028 * Immutable observation of one completed transaction boundary, after cleanup and post-transaction hooks. 029 * <p> 030 * The database result and the invocation failure describe different facts: a committed transaction can still fail 031 * during cleanup or a post-transaction hook. A normal rollback-only return has no failure. Nested independent 032 * transactions have separate completions even when the same failure escapes both boundaries. 033 * <p> 034 * The failure is the original throwable reference, not an immutable copy of that throwable. 035 * 036 * @since 4.8.0 037 */ 038@Immutable 039public final class TransactionCompletion { 040 @NonNull 041 private final Long transactionId; 042 @Nullable 043 private final Long enclosingTransactionId; 044 @NonNull 045 private final TransactionResult result; 046 @Nullable 047 private final Throwable failure; 048 049 TransactionCompletion(@NonNull Long transactionId, @Nullable Long enclosingTransactionId, 050 @NonNull TransactionResult result, @Nullable Throwable failure) { 051 this.transactionId = requireNonNull(transactionId); 052 this.enclosingTransactionId = enclosingTransactionId; 053 this.result = requireNonNull(result); 054 this.failure = failure; 055 } 056 057 /** 058 * Gets this boundary's identifier. IDs correlate transactions within the process, not durable business operations. 059 * 060 * @return transaction identifier 061 */ 062 @NonNull 063 public Long getTransactionId() { 064 return this.transactionId; 065 } 066 067 /** 068 * Gets the identifier of the ambient transaction when this boundary started, including a different database's 069 * transaction when an independent nested transaction was started explicitly. 070 * 071 * @return enclosing transaction identifier, if any 072 */ 073 @NonNull 074 public Optional<Long> getEnclosingTransactionId() { 075 return Optional.ofNullable(this.enclosingTransactionId); 076 } 077 078 /** 079 * Gets this boundary's database outcome. Scopes without physical JDBC work retain their logical commit or rollback 080 * result. This result does not describe the outcome of independently completed nested transactions. 081 * 082 * @return transaction result 083 */ 084 @NonNull 085 public TransactionResult getResult() { 086 return this.result; 087 } 088 089 /** 090 * Gets the exact throwable propagated by this invocation, including suppressed cleanup and hook failures. 091 * The throwable may subsequently gain suppressed failures as it crosses enclosing transaction boundaries. 092 * 093 * @return invocation failure, or empty for a normal return 094 */ 095 @NonNull 096 public Optional<Throwable> getFailure() { 097 return Optional.ofNullable(this.failure); 098 } 099}