/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/txfile/FileSequenceStore.java,v 1.4 2004/07/28 09:33:58 ib Exp $
 * $Revision: 1.4 $
 * $Date: 2004/07/28 09:33:58 $
 *
 * ====================================================================
 *
 * 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 java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

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

import org.apache.commons.transaction.file.FileSequence;
import org.apache.commons.transaction.file.ResourceManagerException;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceConnectionFailedException;
import org.apache.slide.common.ServiceDisconnectionFailedException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.common.ServiceResetFailedException;
import org.apache.slide.common.AbstractServiceBase;
import org.apache.slide.store.SequenceStore;

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

/**
 * Sequence store using the file system. 
 * 
 */
public class FileSequenceStore extends AbstractServiceBase implements SequenceStore {

    protected static final String LOG_CHANNEL = FileSequenceStore.class.getName();

    protected static final String STORE_DIR_PARAMETER = "rootpath";

    protected FileSequence fileSequence;
    protected String storeDir = null;
    protected Map sequenceHash = new HashMap();
    protected int sequenceIncrement = 100;
    protected long sequenceStartValue = 1;

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

        storeDir = (String) parameters.get(STORE_DIR_PARAMETER);
        if (storeDir == null) {
            throw new ServiceParameterMissingException(this, STORE_DIR_PARAMETER);
        }

        try {
            fileSequence = new FileSequence(storeDir, new TxLogger(getLogger(), LOG_CHANNEL));
            getLogger().log("File Sequence Store configured to " + storeDir, LOG_CHANNEL, Logger.INFO);
        } catch (ResourceManagerException e) {
            getLogger().log("Can not initialize File Sequence Store", e, LOG_CHANNEL, Logger.CRITICAL);
            throw new ServiceParameterErrorException(this, STORE_DIR_PARAMETER);
        }
    }

    public String toString() {
        return "FileSequenceStore at " + storeDir;
    }

    /**
     * @see org.apache.slide.common.AbstractServiceBase#connect()
     */
    public void connect() throws ServiceConnectionFailedException {
    }

    /**
     * @see org.apache.slide.common.AbstractServiceBase#disconnect()
     */
    public void disconnect() throws ServiceDisconnectionFailedException {
    }

    /**
     * @see org.apache.slide.common.AbstractServiceBase#reset()
     */
    public void reset() throws ServiceResetFailedException {
    }

    /**
     * @see org.apache.slide.common.AbstractServiceBase#isConnected()
     */
    public boolean isConnected() throws ServiceAccessException {
        return true;
    }

    /**
     * @see org.apache.slide.store.SequenceStore#isSequenceSupported()
     */
    public boolean isSequenceSupported() {
        return true;
    }

    /**
     * @see org.apache.slide.store.SequenceStore#sequenceExists(java.lang.String)
     */
    public boolean sequenceExists(String sequenceName) throws ServiceAccessException {
        SeqContext seq = (SeqContext) sequenceHash.get(sequenceName);
        if (seq != null) {
            return true;
        } else {
            return fileSequence.exists(sequenceName);
        }
    }

    /**
     * @see org.apache.slide.store.SequenceStore#createSequence(java.lang.String)
     */
    public synchronized boolean createSequence(String sequenceName) throws ServiceAccessException {
        try {
            if (fileSequence.create(sequenceName, sequenceStartValue)) {
                sequenceHash.put(sequenceName, new SeqContext(sequenceName, sequenceStartValue, sequenceStartValue));
                return true;
            } else {
                return false;
            }
        } catch (ResourceManagerException e) {
            throw new ServiceAccessException(this, e);
        }
    }

    /**
     * @see org.apache.slide.store.SequenceStore#nextSequenceValue(java.lang.String)
     */
    public synchronized long nextSequenceValue(String sequenceName) throws ServiceAccessException {
        // get chunks of values to save disk access
        SeqContext seq = (SeqContext) sequenceHash.get(sequenceName);
        if (seq != null && seq.pos < seq.end - 1) {
            seq.pos++;
            return seq.pos;
        } else {
            try {
                if (seq == null) {
                    seq = new SeqContext(sequenceName);
                    sequenceHash.put(sequenceName, seq);
                }
                long pos = fileSequence.nextSequenceValueBottom(sequenceName, sequenceIncrement);
                seq.pos = pos;
                seq.end = seq.pos + sequenceIncrement;
                return seq.pos;
            } catch (ResourceManagerException e) {
                throw new ServiceAccessException(this, e);
            }
        }
    }

    /**
     * @see javax.transaction.xa.XAResource#commit(javax.transaction.xa.Xid, boolean)
     */
    public void commit(Xid xid, boolean onePhase) throws XAException {
        // XXX we are not transactional, this is our trick
    }

    /**
     * @see javax.transaction.xa.XAResource#end(javax.transaction.xa.Xid, int)
     */
    public void end(Xid xid, int flags) throws XAException {
        // XXX we are not transactional, this is our trick
    }

    /**
     * @see javax.transaction.xa.XAResource#forget(javax.transaction.xa.Xid)
     */
    public void forget(Xid xid) throws XAException {
        // XXX we are not transactional, this is our trick
    }

    /**
     * @see javax.transaction.xa.XAResource#getTransactionTimeout()
     */
    public int getTransactionTimeout() throws XAException {
        // XXX we are not transactional, this is our trick
        return 0;
    }

    /**
     * @see javax.transaction.xa.XAResource#isSameRM(javax.transaction.xa.XAResource)
     */
    public boolean isSameRM(XAResource xaResource) throws XAException {
        return (xaResource == this);
    }

    /**
     * @see javax.transaction.xa.XAResource#prepare(javax.transaction.xa.Xid)
     */
    public int prepare(Xid xid) throws XAException {
        // tell TM we do not need to commit
        return XA_RDONLY;
    }

    /**
     * @see javax.transaction.xa.XAResource#recover(int)
     */
    public Xid[] recover(int flag) throws XAException {
        // XXX we are not transactional, this is our trick
        return null;
    }

    /**
     * @see javax.transaction.xa.XAResource#rollback(javax.transaction.xa.Xid)
     */
    public void rollback(Xid xid) throws XAException {
        // XXX we are not transactional, this is our trick
    }

    /**
     * @see javax.transaction.xa.XAResource#setTransactionTimeout(int)
     */
    public boolean setTransactionTimeout(int seconds) throws XAException {
        // XXX we are not transactional, this is our trick
        return true;
    }

    /**
     * @see javax.transaction.xa.XAResource#start(javax.transaction.xa.Xid, int)
     */
    public void start(Xid xid, int flags) throws XAException {
        // XXX we are not transactional, this is our trick

    }

    protected static class SeqContext {
        public String name;
        public long pos;
        public long end;

        public SeqContext(String name) {
            this(name, -1, -1);
        }
        public SeqContext(String name, long pos, long end) {
            this.name = name;
            this.pos = pos;
            this.end = end;
        }
    }
}
