org.apache.slide.store.txfile.AbstractTxFileStoreService.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.slide.store.txfile.AbstractTxFileStoreService.java

Source

/*
 * $Header: /var/chroot/cvs/cvs/factsheetDesigner/extern/jakarta-slide-server-src-2.1-iPlus Edit/src/stores/org/apache/slide/store/txfile/AbstractTxFileStoreService.java,v 1.3 2006-04-19 15:06:55 peter-cvs Exp $
 * $Revision: 1.3 $
 * $Date: 2006-04-19 15:06:55 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation
 *
 * 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.apache.slide.store.txfile;

import org.apache.commons.transaction.file.FileResourceManager;
import org.apache.commons.transaction.file.ResourceManager;
import org.apache.commons.transaction.file.ResourceManagerException;
import org.apache.slide.common.*;
import org.apache.slide.macro.ConflictException;

import java.io.File;
import java.util.Hashtable;

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

import org.apache.slide.util.logger.TxLogger;
import org.apache.slide.util.logger.Logger;
import org.apache.slide.transaction.SlideXidWrapper;

/**
 * Abstract base for transactional file stores.
 *
 * Even though the <code>XAResource</code> interface is implemented,
 * this code does not provide conformance to the JTA/JTS or XA spec.
 *
 * It is only intended for use inside of Slide's transaction manager.
 *
 *
 */
public abstract class AbstractTxFileStoreService extends AbstractServiceBase implements Status {

    protected static final int DEBUG_LEVEL = Logger.DEBUG;

    protected static final String STORE_DIR_PARAMETER = "rootpath";
    protected static final String WORK_DIR_PARAMETER = "workpath";
    protected static final String TIMEOUT_PARAMETER = "timeout";
    protected static final String URLENCODE_PATH_PARAMETER = "path-encode-path";
    protected static final String DEBUG_MODE_PARAMETER = "debug";

    protected FileResourceManager rm;
    protected boolean started = false;
    protected String storeDir;
    protected String workDir;

    // there might be at least one active transaction branch per thread
    protected ThreadLocal activeTransactionBranch = new ThreadLocal();

    public void setParameters(Hashtable parameters)
            throws ServiceParameterErrorException, ServiceParameterMissingException {

        storeDir = (String) parameters.get(STORE_DIR_PARAMETER);
        workDir = (String) parameters.get(WORK_DIR_PARAMETER);

        if (storeDir == null) {
            throw new ServiceParameterMissingException(this, STORE_DIR_PARAMETER);
        }
        if (workDir == null) {
            throw new ServiceParameterMissingException(this, WORK_DIR_PARAMETER);
        }

        new File(storeDir).mkdirs();
        new File(workDir).mkdirs();

        boolean debug = false;
        String debugString = (String) parameters.get(DEBUG_MODE_PARAMETER);
        if (debugString != null) {
            debug = "true".equals(debugString);
        }

        boolean urlEncodePath = false;
        String urlEncodePathString = (String) parameters.get(URLENCODE_PATH_PARAMETER);
        if (urlEncodePathString != null) {
            urlEncodePath = "true".equals(urlEncodePathString);
        }

        rm = new FileResourceManager(storeDir, workDir, urlEncodePath,
                new TxLogger(getLogger(), FileResourceManager.class.getName()), debug);

        getLogger().log("File Store configured to " + storeDir + ", working directory " + workDir, getLogChannel(),
                Logger.INFO);

        String timeoutString = (String) parameters.get(TIMEOUT_PARAMETER);
        if (timeoutString != null) {
            try {
                int timeout = Integer.parseInt(timeoutString);
                rm.setDefaultTransactionTimeout(timeout * 1000);
                getLogger().log("Set timeout to " + timeoutString, getLogChannel(), Logger.INFO);
            } catch (NumberFormatException nfe) {
                getLogger().log("Can not set timeout, '" + timeoutString + "' must be an integer!", getLogChannel(),
                        Logger.WARNING);
            }
        }

    }

    public String toString() {
        return "TxFileStore at " + storeDir + "  working on " + workDir;
    }

    public void connect() throws ServiceConnectionFailedException {
        try {
            rm.start();
            started = true;
        } catch (ResourceManagerException e) {
            throw new ServiceConnectionFailedException(this, e);
        }
    }

    public void disconnect() throws ServiceDisconnectionFailedException {
        try {
            if (!rm.stop(ResourceManager.SHUTDOWN_MODE_NORMAL)) {
                throw new ServiceDisconnectionFailedException(this, "Shut down timed out");
            }
            started = false;
        } catch (ResourceManagerException e) {
            throw new ServiceDisconnectionFailedException(this, e);
        }
    }

    public boolean isConnected() throws ServiceAccessException {
        return started;
    }

    public void reset() {
        rm.reset();
    }

    public int getTransactionTimeout() throws XAException {
        try {
            long msecs = rm.getTransactionTimeout(getActiveTxId());
            return Math.round(msecs / (float) 1000);
        } catch (ResourceManagerException e) {
            throw createXAException(e);
        }
    }

    public boolean setTransactionTimeout(int seconds) throws XAException {
        try {
            rm.setTransactionTimeout(getActiveTxId(), seconds * 1000);
            return true;
        } catch (ResourceManagerException e) {
            throw createXAException(e);
        }
    }

    public boolean isSameRM(XAResource xares) throws XAException {
        return (xares instanceof AbstractTxFileStoreService
                && ((AbstractTxFileStoreService) xares).rm.equals(this.rm));
    }

    public synchronized Xid[] recover(int flag) throws XAException {
        // do not care as this never is called by Slide
        return null;
    }

    public synchronized void forget(Xid xid) throws XAException {
        // there is nothing we remember, so there is nothing to forget
    }

    public synchronized int prepare(Xid xid) throws XAException {
        Object txId = wrap(xid);
        Thread currentThread = Thread.currentThread();
        getLogger().log("Thread " + currentThread + " prepares transaction branch " + txId, getLogChannel(),
                DEBUG_LEVEL);
        try {
            int status = rm.prepareTransaction(txId);
            switch (status) {
            case ResourceManager.PREPARE_SUCCESS_READONLY:
                return XA_RDONLY;
            case ResourceManager.PREPARE_SUCCESS:
                return XA_OK;
            default:
                throw new XAException(XAException.XA_RBROLLBACK);
            }
        } catch (ResourceManagerException e) {
            getLogger().log("Thread " + currentThread + " failed to prepare transaction branch " + txId, e,
                    getLogChannel(), Logger.CRITICAL);
            throw createXAException(e);
        }
    }

    public synchronized void rollback(Xid xid) throws XAException {
        Object txId = wrap(xid);
        Thread currentThread = Thread.currentThread();
        getLogger().log("Thread " + currentThread + " rolls back transaction branch " + txId, getLogChannel(),
                DEBUG_LEVEL);

        try {
            rm.rollbackTransaction(txId);
            activeTransactionBranch.set(null);
        } catch (ResourceManagerException e) {
            getLogger().log("Thread " + currentThread + " failed to roll back transaction branch " + txId, e,
                    getLogChannel(), Logger.CRITICAL);
            throw createXAException(e);
        }
    }

    public synchronized void commit(Xid xid, boolean onePhase) throws XAException {
        Object txId = wrap(xid);
        Thread currentThread = Thread.currentThread();
        getLogger().log("Thread " + currentThread + " commits transaction branch " + txId, getLogChannel(),
                DEBUG_LEVEL);

        try {
            if (!onePhase && rm.getTransactionState(txId) != STATUS_PREPARED) {
                throw new XAException(XAException.XAER_INVAL);
            }

            rm.commitTransaction(txId);
            activeTransactionBranch.set(null);
        } catch (ResourceManagerException e) {
            getLogger().log("Thread " + currentThread + " failed to commit transaction branch " + txId, e,
                    getLogChannel(), Logger.CRITICAL);
            throw createXAException(e);
        }
    }

    public synchronized void end(Xid xid, int flags) throws XAException {
        Object txId = wrap(xid);
        Thread currentThread = Thread.currentThread();
        getLogger().log("Thread " + currentThread
                + (flags == TMSUSPEND ? " suspends" : flags == TMFAIL ? " fails" : " ends")
                + " work on behalf of transaction branch " + txId, getLogChannel(), DEBUG_LEVEL);

        switch (flags) {
        case TMSUSPEND:
            activeTransactionBranch.set(null);
            break;
        case TMFAIL:
            try {
                rm.markTransactionForRollback(wrap(xid));
            } catch (ResourceManagerException e) {
                throw createXAException(e);
            }
            break;
        case TMSUCCESS:
            // not ineresting for us
            break;
        }
    }

    public synchronized void start(Xid xid, int flags) throws XAException {
        Object txId = wrap(xid);
        Thread currentThread = Thread.currentThread();
        getLogger().log("Thread " + currentThread
                + (flags == TMNOFLAGS ? " starts" : flags == TMJOIN ? " joins" : " resumes")
                + " work on behalf of transaction branch " + txId, getLogChannel(), DEBUG_LEVEL);

        switch (flags) {
        // a new transaction
        case TMNOFLAGS:
            if (getActiveTxId() != null) {
                throw new XAException(XAException.XAER_INVAL);
            }
            try {
                rm.startTransaction(txId);
                activeTransactionBranch.set(txId);
            } catch (ResourceManagerException e) {
                throw createXAException(e);
            }
            break;
        case TMJOIN:
            if (getActiveTxId() != null) {
                throw new XAException(XAException.XAER_INVAL);
            }
            try {
                if (rm.getTransactionState(txId) == STATUS_NO_TRANSACTION) {
                    throw new XAException(XAException.XAER_INVAL);
                }
            } catch (ResourceManagerException e) {
                throw createXAException(e);
            }
            activeTransactionBranch.set(txId);
            break;
        case TMRESUME:
            activeTransactionBranch.set(txId);
            break;
        }
    }

    public synchronized void throwInternalError(String cause) throws ServiceAccessException {
        Object txId = getActiveTxId();

        getLogger().log("Thread " + Thread.currentThread() + " marked transaction branch " + txId
                + " for rollback. Cause: " + cause, getLogChannel(), Logger.WARNING);

        try {
            rm.markTransactionForRollback(txId);
        } catch (ResourceManagerException re) {
            throw new ServiceAccessException(this, re);
        }

        throw new ServiceAccessException(this, cause);

    }

    public synchronized void throwInternalError(Throwable cause) throws ServiceAccessException {
        Object txId = getActiveTxId();

        getLogger().log("Thread " + Thread.currentThread() + " marked transaction branch " + txId
                + " for rollback. Cause: " + cause, getLogChannel(), Logger.WARNING);

        try {
            rm.markTransactionForRollback(txId);
        } catch (ResourceManagerException re) {
            throw new ServiceAccessException(this, re);
        }

        throw new ServiceAccessException(this, cause);

    }

    // TODO if error is caused by lock that could not be acquired
    // we should try deadlock detection instead of simply rolling back
    // if no deadlock is detected, retrying for lock would be preferred method
    public synchronized void throwInternalError(Throwable cause, String uri) throws ServiceAccessException {
        Object txId = getActiveTxId();

        if ((cause instanceof ResourceManagerException)
                && ((ResourceManagerException) cause).getStatus() == ResourceManagerException.ERR_NO_LOCK) {

            // XXX strictly speaking, this is incorrect, as we actually did not chck for deadlock,
            // but silently assume a deadlock must have been the cause for the failed lock
            if (txId != null) {
                try {
                    rm.markTransactionForRollback(txId);
                } catch (ResourceManagerException re) {
                    throw new ServiceAccessException(this, re);
                }
            }
            getLogger().log("DEADLOCK VICTIM: Thread " + Thread.currentThread() + " marked transaction branch "
                    + txId + " for rollback", getLogChannel(), Logger.INFO);

            throw new ServiceAccessException(this, new ConflictException(uri));

        } else {

            getLogger().log(
                    "Could not process URI '" + uri + "'! Thread " + Thread.currentThread()
                            + " marking transaction branch " + txId + " for rollback",
                    cause, getLogChannel(), Logger.WARNING);

            if (txId != null) {
                try {
                    rm.markTransactionForRollback(txId);
                } catch (ResourceManagerException re) {
                    throw new ServiceAccessException(this, re);
                }
            }

            throw new ServiceAccessException(this, cause);

        }
    }

    protected Object getActiveTxId() {
        Object txId = activeTransactionBranch.get();
        return txId;
    }

    protected XAException createXAException(ResourceManagerException e) {
        if (e.getStatus() == ResourceManagerException.ERR_DUP_TX) {
            return new XAException(XAException.XAER_DUPID);
        } else if (e.getStatus() == ResourceManagerException.ERR_TXID_INVALID) {
            return new XAException(XAException.XAER_NOTA);
        } else {
            return new XAException(e.toString());
        }
    }

    protected String wrap(Xid xid) {
        String sxid = SlideXidWrapper.wrap(xid).toString();
        // XXX for tx file store replace :, ', \ and / which might be part of the identifier with chars not
        // offensive to any file system specific chars
        sxid = sxid.replace('\'', '.').replace('"', '.').replace(':', '.').replace('/', '.').replace('\\', '.');
        return sxid;
    }

    abstract protected String getLogChannel();
}