/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * Licensed 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.seasar.extension.tx.adapter;

import javax.transaction.TransactionRolledbackException;

import org.seasar.extension.tx.TransactionCallback;
import org.seasar.extension.tx.TransactionManagerAdapter;
import org.seasar.framework.exception.SIllegalStateException;
import org.seasar.framework.log.Logger;

import com.ibm.websphere.uow.UOWSynchronizationRegistry;
import com.ibm.wsspi.uow.UOWAction;
import com.ibm.wsspi.uow.UOWActionException;
import com.ibm.wsspi.uow.UOWException;
import com.ibm.wsspi.uow.UOWManager;

/**
 * WebSphere version 6 (6.0.2.19以降または6.1.0.9以降) が提供するUOW APIを使用してトランザクションを制御する、
 * {@link TransactionManagerAdapter}の実装です。
 * 
 * @author koichik
 * @since 2.4.18
 */
public class WAS6TransactionManagerAdapter implements TransactionManagerAdapter {

    /** グローバルトランザクションを示します */
    protected static final int GLOBAL_TX = UOWSynchronizationRegistry.UOW_TYPE_GLOBAL_TRANSACTION;

    /** ローカルトランザクションを示します */
    protected static final int LOCAL_TX = UOWSynchronizationRegistry.UOW_TYPE_LOCAL_TRANSACTION;

    /** 既存のトランザクションがあれば参加することを示します */
    protected static final boolean JOIN_TX = true;

    /** 新規のトランザクションを開始することを示します */
    protected static final boolean NEW_TX = false;

    private static final Logger logger = Logger
            .getLogger(WAS6TransactionManagerAdapter.class);

    /** UOW API の提供するトランザクションマネージャ */
    protected final UOWManager uowManager;

    /**
     * インスタンスを構築します。
     * 
     * @param uowManager
     *            UOW API の提供するトランザクションマネージャ
     */
    public WAS6TransactionManagerAdapter(final UOWManager uowManager) {
        this.uowManager = uowManager;
    }

    public Object required(final TransactionCallback callback) throws Throwable {
        return executeCallback(callback, GLOBAL_TX, JOIN_TX);
    }

    public Object requiresNew(final TransactionCallback callback)
            throws Throwable {
        return executeCallback(callback, GLOBAL_TX, NEW_TX);
    }

    public Object mandatory(final TransactionCallback callback)
            throws Throwable {
        if (!hasTransaction()) {
            throw new SIllegalStateException("ESSR0311", null);
        }
        return callback.execute(this);
    }

    public Object notSupported(final TransactionCallback callback)
            throws Throwable {
        return executeCallback(callback, LOCAL_TX, NEW_TX);
    }

    public Object never(final TransactionCallback callback) throws Throwable {
        if (hasTransaction()) {
            throw new SIllegalStateException("ESSR0317", null);
        }
        return callback.execute(this);
    }

    public void setRollbackOnly() {
        try {
            if (uowManager.getUOWStatus() == UOWSynchronizationRegistry.UOW_STATUS_ACTIVE) {
                uowManager.setRollbackOnly();
            }
        } catch (final Exception e) {
            logger.log("ESSR0017", new Object[] { e.getMessage() }, e);
        }
    }

    /**
     * トランザクション制御下でトランザクションコールバックを呼び出します。
     * 
     * @param callback
     *            トランザクションコールバック
     * @param transactionType
     *            {@link #GLOBAL_TX}または{@link #LOCAL_TX}
     * @param joinTransaction
     *            {@link #JOIN_TX}または{@link #NEW_TX}
     * @return トランザクションコールバックの戻り値
     * @throws Throwable
     *             トランザクションコールバックが例外をスローした場合
     */
    protected Object executeCallback(final TransactionCallback callback,
            final int transactionType, final boolean joinTransaction)
            throws Throwable {
        try {
            final UOWActionImpl action = new UOWActionImpl(callback);
            uowManager.runUnderUOW(transactionType, joinTransaction, action);
            return action.getResult();
        } catch (final UOWActionException e) {
            final Throwable cause = e.getCause();
            if (cause instanceof WrappedException) {
                throw cause.getCause();
            }
            throw cause;
        } catch (final UOWException e) {
            final TransactionRolledbackException ex = new TransactionRolledbackException(
                    e.getMessage());
            ex.detail = e;
            throw ex;
        }
    }

    /**
     * 現在のスレッド上でトランザクションが開始されている場合は<code>true</code>を、それ以外の場合は<code>false</code>
     * を返します。
     * 
     * @return 現在のスレッド上でトランザクションが開始されている場合は<code>true</code>
     */
    protected boolean hasTransaction() {
        return uowManager.getUOWStatus() != UOWSynchronizationRegistry.UOW_STATUS_NONE;
    }

    /**
     * <code>UOWManager</code>が制御するトランザクション中に実行するアクションの実装です。
     * 
     * @author koichik
     */
    public class UOWActionImpl implements UOWAction {

        /** トランザクションコールバック */
        protected TransactionCallback callback;

        /** トランザクションコールバックの戻り値 */
        protected Object result;

        /**
         * インスタンスを構築します。
         * 
         * @param callback
         *            トランザクションコールバック
         */
        public UOWActionImpl(final TransactionCallback callback) {
            this.callback = callback;
        }

        /**
         * <code>UOWManager</code>から呼び出されます。
         */
        public void run() throws Exception {
            try {
                result = callback.execute(WAS6TransactionManagerAdapter.this);
            } catch (final Throwable e) {
                throw new WrappedException(e);
            }
        }

        /**
         * トランザクションコールバックの戻り値を返します。
         * 
         * @return トランザクションコールバックの戻り値
         */
        public Object getResult() {
            return result;
        }

    }

    /**
     * トランザクションコールバックからスローされた例外をラップする例外です。
     * <p>
     * <code>UOWAction</code>からチェックされない例外をスローするとトランザクションがロールバックされてしまうので、
     * トランザクションコールバックで発生した例外は全てこの例外でラップします。 トランザクションの制御 (コミットするかロールバックするかの判定)
     * はトランザクションコールバックで完了しています。
     * </p>
     * 
     * @author koichik
     */
    public static class WrappedException extends Exception {

        private static final long serialVersionUID = 1L;

        /**
         * インスタンスを構築します。
         * 
         * @param cause
         *            原因となった例外
         */
        public WrappedException(final Throwable cause) {
            super(cause);
        }

    }

}
