/*
 * Copyright 2004-2014 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.jta.xa;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

/**
 * {@link XAResource}の抽象クラスです。
 *
 * @author higa
 *
 */
public abstract class AbstractXAResource implements XAResource {

    /** Xid */
    private Xid currentXid;
    /** XAResourceStatus */
    private XAResourceStatus status = XAResourceStatus.RS_NONE;
    /** timeout */
    private int timeout = 0;

    /**
     * @see javax.transaction.xa.XAResource#start(javax.transaction.xa.Xid, int)
     */
    @Override
    public void start(final Xid xid, final int flags) throws XAException {
        switch (flags) {
            case TMNOFLAGS:
                begin(xid);
                break;
            case TMRESUME:
                resume(xid);
                break;
            default:
                throw new XAException(String.valueOf(flags));
        }
    }

    /**
     * begin
     * @param xid Xid
     * @throws XAException XAException
     */
    private void begin(final Xid xid) throws XAException {
        assertCurrentXidNull();
        doBegin(xid);
        this.currentXid = xid;
        this.status = XAResourceStatus.RS_ACTIVE;
    }

    /**
     * assertCurrentXidNull
     * @throws XAException XAException
     */
    private void assertCurrentXidNull() throws XAException {
        if (this.currentXid != null) {
            throw new XAException("currentXid is null.");
        }
    }

    /**
     * トランザクションを開始します。
     *
     * @param xid トランザクション識別子
     * @throws XAException XA例外が発生した場合
     */
    protected abstract void doBegin(Xid xid) throws XAException;

    /**
     * resume
     * @param xid Xid
     * @throws XAException XAException
     */
    private void resume(final Xid xid) throws XAException {
        assertCurrentXidSame(xid);
        assertStatusSuspended();
        doResume(xid);
        this.status = XAResourceStatus.RS_ACTIVE;
    }

    /**
     * assertCurrentXidSame
     * @param xid Xid
     * @throws XAException XAException
     */
    private void assertCurrentXidSame(final Xid xid) throws XAException {
        if (this.currentXid != xid) {
            throw new XAException(
                    String.format("currentXid is different.(%s:%s)", xid, this.currentXid));
        }
    }

    /**
     * assertStatusSuspended
     * @throws XAException XAException
     */
    private void assertStatusSuspended() throws XAException {
        if (!XAResourceStatus.RS_SUSPENDED.equals(this.status)) {
            throw new XAException("status is not SUSPENDED. :" + this.status);
        }
    }

    /**
     * レジュームします。
     *
     * @param xid トランザクション識別子
     * @throws XAException XA例外が発生した場合
     */
    protected abstract void doResume(Xid xid) throws XAException;

    /**
     * @see javax.transaction.xa.XAResource#end(javax.transaction.xa.Xid, int)
     */
    @Override
    public void end(final Xid xid, final int flags) throws XAException {
        assertCurrentXidSame(xid);
        assertStatusActive();
        switch (flags) {
            case TMSUSPEND:
                suspend(xid);
                break;
            case TMFAIL:
                fail(xid);
                break;
            case TMSUCCESS:
                success(xid);
                break;
            default:
                throw new XAException(String.valueOf(flags));
        }
    }

    /**
     * assertStatusActive
     * @throws XAException XAException
     */
    private void assertStatusActive() throws XAException {
        if (!XAResourceStatus.RS_ACTIVE.equals(this.status)) {
            throw new XAException("status is not ACTIVE. :" + this.status);
        }
    }

    /**
     * suspend
     * @param xid Xid
     * @throws XAException XAException
     */
    private void suspend(final Xid xid) throws XAException {
        doSuspend(xid);
        this.status = XAResourceStatus.RS_SUSPENDED;
    }

    /**
     * サスペンドします。
     *
     * @param xid トランザクション識別子
     * @throws XAException XA例外が発生した場合
     */
    protected abstract void doSuspend(Xid xid) throws XAException;

    /**
     * fail
     * @param xid Xid
     * @throws XAException XAException
     */
    private void fail(final Xid xid) throws XAException {
        doFail(xid);
        this.status = XAResourceStatus.RS_FAIL;
    }

    /**
     * 失敗させます。
     *
     * @param xid トランザクション識別子
     * @throws XAException XAException
     */
    protected abstract void doFail(Xid xid) throws XAException;

    /**
     * success
     * @param xid Xid
     * @throws XAException XAException
     */
    private void success(final Xid xid) throws XAException {
        doSuccess(xid);
        this.status = XAResourceStatus.RS_SUCCESS;
    }

    /**
     * 成功時の処理を行ないます。
     *
     * @param xid トランザクション識別子
     * @throws XAException XA例外が発生した場合
     */
    protected abstract void doSuccess(Xid xid) throws XAException;

    /**
     * @see javax.transaction.xa.XAResource#prepare(javax.transaction.xa.Xid)
     */
    @Override
    public int prepare(final Xid xid) throws XAException {
        assertCurrentXidSame(xid);
        assertStatusSuccess();
        final int ret = doPrepare(xid);
        if (ret == XA_OK) {
            this.status = XAResourceStatus.RS_PREPARED;
        } else {
            this.status = XAResourceStatus.RS_NONE;
        }
        return ret;
    }

    /**
     * assertStatus
     * @throws XAException XAException
     */
    private void assertStatusSuccess() throws XAException {
        if (!XAResourceStatus.RS_SUCCESS.equals(this.status)) {
            throw new XAException("status is not SUCCESS. :" + this.status);
        }
    }

    /**
     * コミットする準備を行ないます。
     *
     * @param xid トランザクション識別子
     * @return 投票の結果
     * @throws XAException XA例外が発生した場合
     */
    protected abstract int doPrepare(Xid xid) throws XAException;

    /**
     * @see javax.transaction.xa.XAResource#commit(javax.transaction.xa.Xid, boolean)
     */
    @Override
    public void commit(final Xid xid, final boolean onePhase) throws XAException {
        assertCurrentXidSame(xid);
        if (onePhase) {
            assertStatusSuccess();
        } else {
            assertStatusPrepared();
        }
        doCommit(xid, onePhase);
        init();
    }

    /**
     * assertStatus
     * @throws XAException XAException
     */
    private void assertStatusPrepared() throws XAException {
        if (!XAResourceStatus.RS_PREPARED.equals(this.status)) {
            throw new XAException("status is not PREPARED. :" + this.status);
        }
    }

    /**
     * コミットします。
     *
     * @param xid トランザクション識別子
     * @param onePhase 1フェーズかどうか
     * @throws XAException XA例外が発生した場合
     */
    protected abstract void doCommit(Xid xid, boolean onePhase) throws XAException;

    /**
     * init
     */
    private void init() {
        this.currentXid = null;
        this.status = XAResourceStatus.RS_NONE;
    }

    /**
     * @see javax.transaction.xa.XAResource#forget(javax.transaction.xa.Xid)
     */
    @Override
    public void forget(final Xid xid) throws XAException {
        assertCurrentXidSame(xid);
        doForget(xid);
        init();
    }

    /**
     * トランザクションを忘れます。
     *
     * @param xid トランザクション識別子
     * @throws XAException XA例外が発生した場合
     */
    protected abstract void doForget(Xid xid) throws XAException;

    /**
     * @see javax.transaction.xa.XAResource#recover(int)
     */
    @Override
    public Xid[] recover(final int flag) throws XAException {
        final Xid[] ret = null;
        return ret;
    }

    /**
     * @see javax.transaction.xa.XAResource#rollback(javax.transaction.xa.Xid)
     */
    @Override
    public void rollback(final Xid xid) throws XAException {
        assertCurrentXidSame(xid);
        assertStatusSuccessOrFailOrPrepared();
        doRollback(xid);
        init();
    }

    /**
     * assertStatus
     * @throws XAException XAException
     */
    private void assertStatusSuccessOrFailOrPrepared() throws XAException {
        switch (this.status) {
            case RS_SUCCESS:
            case RS_FAIL:
            case RS_PREPARED:
                break;
            default:
                throw new XAException(String.valueOf(this.status));
        }
    }

    /**
     * ロールバックします。
     *
     * @param xid トランザクション識別子
     * @throws XAException XA例外が発生した場合
     */
    protected abstract void doRollback(Xid xid) throws XAException;

    /**
     * @see javax.transaction.xa.XAResource#isSameRM(javax.transaction.xa.XAResource)
     */
    @Override
    public boolean isSameRM(final XAResource xar) throws XAException {
        return false;
    }

    /**
     * @see javax.transaction.xa.XAResource#getTransactionTimeout()
     */
    @Override
    public int getTransactionTimeout() throws XAException {
        return this.timeout;
    }

    /**
     * @see javax.transaction.xa.XAResource#setTransactionTimeout(int)
     */
    @Override
    public boolean setTransactionTimeout(final int seconds) throws XAException {
        this.timeout = seconds;
        return true;
    }

    /**
     * 現在のトランザクション識別子を返します。
     *
     * @return 現在のトランザクション識別子
     */
    public Xid getCurrentXid() {
        return this.currentXid;
    }

    /**
     * ステータスを返します。
     *
     * @return ステータス
     */
    public int getStatus() {
        return this.status.value();
    }
}
