KaumeiTxDefinition.java

/*
 * SPDX-FileCopyrightText: 2026 kaumei.io
 * SPDX-License-Identifier: Apache-2.0
 */
package io.kaumei.jdbc.tx;

import io.kaumei.jdbc.annotation.KaumeiTx;

import static java.util.Objects.requireNonNull;

public record KaumeiTxDefinition(
        Isolation isolation,
        ReadOnly readOnly,
        ExceptionMatcher rollbackOn,
        ExceptionMatcher dontRollbackOn) {

    public static final ExceptionMatcher NO_THROWABLES = exception -> false;
    public static final ExceptionMatcher ALL_THROWABLES = exception -> true;

    public static final KaumeiTxDefinition DEFAULT = new KaumeiTxDefinition(
            Isolation.DEFAULT,
            ReadOnly.DEFAULT,
            NO_THROWABLES,
            NO_THROWABLES);

    @FunctionalInterface
    public interface ExceptionMatcher {

        boolean matches(Throwable exception);
    }

    public KaumeiTxDefinition {
        requireNonNull(isolation, "isolation");
        requireNonNull(readOnly, "readOnly");
        requireNonNull(rollbackOn, "rollbackOn");
        requireNonNull(dontRollbackOn, "dontRollbackOn");
    }

    public enum Isolation {
        DEFAULT,
        READ_UNCOMMITTED,
        READ_COMMITTED,
        REPEATABLE_READ,
        SERIALIZABLE
    }

    public enum ReadOnly {
        DEFAULT,
        READ_ONLY,
        READ_WRITE
    }

    public static Builder builder() {
        return new Builder();
    }

    /**
     * Creates a transaction definition from the non-propagation attributes of an annotation.
     * @param annotation transaction annotation to convert
     * @return the corresponding transaction definition
     */
    public static KaumeiTxDefinition from(KaumeiTx annotation) {
        requireNonNull(annotation, "annotation");
        Class<? extends Throwable>[] rollbackOn = annotation.rollbackOn();
        Class<? extends Throwable>[] dontRollbackOn = annotation.dontRollbackOn();
        if (annotation.isolation() == KaumeiTx.Isolation.DEFAULT
                && annotation.readOnly() == KaumeiTx.ReadOnly.DEFAULT
                && rollbackOn.length == 0
                && dontRollbackOn.length == 0) {
            return DEFAULT;
        }

        return new KaumeiTxDefinition(
                isolation(annotation.isolation()),
                readOnly(annotation.readOnly()),
                matcher(rollbackOn),
                matcher(dontRollbackOn));
    }

    private static Isolation isolation(KaumeiTx.Isolation isolation) {
        return switch (isolation) {
            case DEFAULT -> Isolation.DEFAULT;
            case READ_UNCOMMITTED -> Isolation.READ_UNCOMMITTED;
            case READ_COMMITTED -> Isolation.READ_COMMITTED;
            case REPEATABLE_READ -> Isolation.REPEATABLE_READ;
            case SERIALIZABLE -> Isolation.SERIALIZABLE;
        };
    }

    private static ReadOnly readOnly(KaumeiTx.ReadOnly readOnly) {
        return switch (readOnly) {
            case DEFAULT -> ReadOnly.DEFAULT;
            case READ_ONLY -> ReadOnly.READ_ONLY;
            case READ_WRITE -> ReadOnly.READ_WRITE;
        };
    }

    private static ExceptionMatcher matcher(Class<? extends Throwable>[] exceptionTypes) {
        if (exceptionTypes.length == 0) {
            return NO_THROWABLES;
        }

        Class<? extends Throwable>[] types = exceptionTypes.clone();
        return exception -> {
            for (Class<? extends Throwable> type : types) {
                if (type.isInstance(exception)) {
                    return true;
                }
            }
            return false;
        };
    }

    public static final class Builder {

        private Isolation isolation = Isolation.DEFAULT;
        private ReadOnly readOnly = ReadOnly.DEFAULT;
        private ExceptionMatcher rollbackOn = NO_THROWABLES;
        private ExceptionMatcher dontRollbackOn = NO_THROWABLES;

        public Builder isolation(Isolation isolation) {
            this.isolation = requireNonNull(isolation, "isolation");
            return this;
        }

        public Builder readOnly(ReadOnly readOnly) {
            this.readOnly = requireNonNull(readOnly, "readOnly");
            return this;
        }

        public Builder rollbackOn(ExceptionMatcher rollbackOn) {
            this.rollbackOn = requireNonNull(rollbackOn, "rollbackOn");
            return this;
        }

        public Builder dontRollbackOn(ExceptionMatcher dontRollbackOn) {
            this.dontRollbackOn = requireNonNull(dontRollbackOn, "dontRollbackOn");
            return this;
        }

        public KaumeiTxDefinition build() {
            return new KaumeiTxDefinition(isolation, readOnly, rollbackOn, dontRollbackOn);
        }
    }

}