/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.kernel;

import jakarta.transaction.NotSupportedException;
import jakarta.transaction.Status;
import jakarta.transaction.Synchronization;
import jakarta.transaction.SystemException;
import jakarta.transaction.Transaction;
import jakarta.transaction.TransactionManager;
import javax.transaction.xa.XAResource;

import org.apache.openjpa.ee.AbstractManagedRuntime;
import org.apache.openjpa.ee.ManagedRuntime;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.InvalidStateException;
import org.apache.openjpa.util.StoreException;
import org.apache.openjpa.util.UserException;

/**
 * Uses a local implementation of the {@link TransactionManager} interface.
 * This manager is valid only for a single {@link Broker}.
 * It duplicates non-managed transaction control.
 *
 * @author Abe White
 */
class LocalManagedRuntime extends AbstractManagedRuntime
    implements ManagedRuntime, TransactionManager, Transaction {

    private static final Localizer _loc = Localizer.forPackage
        (LocalManagedRuntime.class);

    private Synchronization _broker = null;
    private Synchronization _factorySync = null;
    private boolean _active = false;
    private Throwable _rollbackOnly = null;

    /**
     * Constructor. Provide broker that will be requesting managed
     * transaction info.
     */
    public LocalManagedRuntime(Broker broker) {
        _broker = broker;
    }

    @Override
    public TransactionManager getTransactionManager() {
        return this;
    }

    @Override
    public synchronized void begin() {
        if (_active)
            throw new InvalidStateException(_loc.get("active"));
        _active = true;
    }

    @Override
    public synchronized void commit() {
        if (!_active)
            throw new InvalidStateException(_loc.get("not-active"));

        // try to invoke before completion in preparation for commit
        RuntimeException err = null;
        if (_rollbackOnly == null) {
            try {
                _broker.beforeCompletion();
                if (_factorySync != null)
                    _factorySync.beforeCompletion();
            } catch (RuntimeException re) {
                _rollbackOnly = re;
                err = re;
            }
        } else // previously marked rollback only
            err = new StoreException(_loc.get("marked-rollback")).
                setCause(_rollbackOnly).setFatal(true);

        if (_rollbackOnly == null) {
            try {
                _broker.afterCompletion(Status.STATUS_COMMITTED);
                notifyAfterCompletion(Status.STATUS_COMMITTED);
            } catch (RuntimeException re) {
                if (err == null)
                    err = re;
            }
        }

        // if we haven't managed to commit, rollback
        if (_active) {
            try {
                rollback();
            } catch (RuntimeException re) {
                if (err == null)
                    err = re;
            }
        }

        // throw the first exception we encountered, if any
        if (err != null)
            throw err;
    }

    @Override
    public synchronized void rollback() {
        if (!_active)
            throw new InvalidStateException(_loc.get("not-active"));

        // rollback broker
        RuntimeException err = null;
        try {
            _broker.afterCompletion(Status.STATUS_ROLLEDBACK);
        } catch (RuntimeException re) {
            err = re;
        }

        // rollback synch, even if broker throws exception
        try {
            notifyAfterCompletion(Status.STATUS_ROLLEDBACK);
        } catch (RuntimeException re) {
            if (err == null)
                err = re;
        }

        if (err != null)
            throw err;
    }

    /**
     * Notifies the factory sync that the transaction has ended with
     * the given status. Clears all transaction state regardless
     * of any exceptions during the callback.
     */
    private void notifyAfterCompletion(int status) {
        _active = false;

        try {
            if (_factorySync != null)
                _factorySync.afterCompletion(status);
        } finally {
            _rollbackOnly = null;
            _factorySync = null;
        }
    }

    @Override
    public synchronized void setRollbackOnly() {
        setRollbackOnly(new UserException());
    }

    @Override
    public void setRollbackOnly(Throwable cause) {
        _rollbackOnly = cause;
    }

    @Override
    public Throwable getRollbackCause() {
        return _rollbackOnly;
    }

    @Override
    public synchronized int getStatus() {
        if (_rollbackOnly != null)
            return Status.STATUS_MARKED_ROLLBACK;
        if (_active)
            return Status.STATUS_ACTIVE;
        return Status.STATUS_NO_TRANSACTION;
    }

    @Override
    public Transaction getTransaction() {
        return this;
    }

    @Override
    public void resume(Transaction tobj)
        throws SystemException {
        throw new SystemException(NotSupportedException.class.getName());
    }

    @Override
    public void setTransactionTimeout(int sec)
        throws SystemException {
        throw new SystemException(NotSupportedException.class.getName());
    }

    @Override
    public Transaction suspend()
        throws SystemException {
        throw new SystemException(NotSupportedException.class.getName());
    }

    @Override
    public boolean delistResource(XAResource xaRes, int flag)
        throws SystemException {
        throw new SystemException(NotSupportedException.class.getName());
    }

    @Override
    public boolean enlistResource(XAResource xaRes)
        throws SystemException {
        throw new SystemException(NotSupportedException.class.getName());
    }

    @Override
    public synchronized void registerSynchronization(Synchronization sync) {
        if (sync == _broker)
            return;
        if (_factorySync != null)
            throw new InternalException();
        _factorySync = sync;
    }
}

