/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/ojb/AbstractOJBStore.java,v 1.2 2005/01/04 11:08:10 ozeigermann Exp $
 * $Revision: 1.2 $
 * $Date: 2005/01/04 11:08:10 $
 *
 * ====================================================================
 *
 * 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.ojb;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

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

import org.apache.commons.lang.SerializationUtils;
import org.apache.commons.transaction.util.xa.AbstractTransactionalResource;
import org.apache.commons.transaction.util.xa.TransactionalResource;
import org.apache.ojb.broker.PersistenceBroker;
import org.apache.ojb.broker.PersistenceBrokerException;
import org.apache.ojb.broker.PersistenceBrokerFactory;
import org.apache.ojb.broker.metadata.ConnectionRepository;
import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
import org.apache.ojb.broker.metadata.MetadataException;
import org.apache.ojb.broker.metadata.MetadataManager;
import org.apache.ojb.broker.Identity;
import org.apache.slide.common.AbstractXAServiceBase;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceConnectionFailedException;
import org.apache.slide.common.ServiceDisconnectionFailedException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.common.ServiceResetFailedException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.store.ContentStore;
import org.apache.slide.store.LockStore;
import org.apache.slide.store.NodeStore;
import org.apache.slide.store.RevisionDescriptorStore;
import org.apache.slide.store.RevisionDescriptorsStore;
import org.apache.slide.store.SecurityStore;
import org.apache.slide.store.SequenceStore;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.util.logger.Logger;

import org.apache.slide.store.ojb.peer.SequencePeer;

/**
 * AbstractOJBStore, implements base functionality for OJBStore.
 * 
 */
public abstract class AbstractOJBStore extends AbstractXAServiceBase 
		implements ContentStore, NodeStore, SecurityStore, LockStore, SequenceStore,
				   RevisionDescriptorStore, RevisionDescriptorsStore {
    
    protected static final String LOG_CHANNEL = AbstractOJBStore.class.getName();

    protected static final int NO_CONNECTION_LOG_LEVEL = Logger.DEBUG;

    /**
     * Indicates whether the transaction manager will commit / rollback
     * the transaction or the store is in charge of it. Slide's internal
     * TM does not commit / rollback, but TMs more aligned to the spec (e.g. JBoss' TM) do. 
     */
    protected boolean tmCommits = false;

    // ==== Service Methods ================================

    public void setParameters(Hashtable parameters)
            throws ServiceParameterErrorException,
            ServiceParameterMissingException {
        
        LoggerBridge.setLogger(getLogger());
        
        String value = (String)parameters.get("platform");
        try { 
            MetadataManager mm = MetadataManager.getInstance();
            ConnectionRepository cr = mm.connectionRepository();
            JdbcConnectionDescriptor defJcd;
            if (value != null) {
                defJcd = getDefaultDescriptor(cr);
                JdbcConnectionDescriptor jcd = (JdbcConnectionDescriptor)SerializationUtils.clone(defJcd);
                jcd.setJcdAlias("default");
                jcd.setDefaultConnection(true);
                jcd.setDbms(value);
                value = (String) parameters.get("protocol");
                if ( value != null )
                    jcd.setProtocol(value);
                value = (String) parameters.get("subprotocol");
                if ( value != null )
                    jcd.setSubProtocol(value);
                value = (String) parameters.get("dbalias");
                if ( value != null )
                    jcd.setDbAlias(value);
                value = (String) parameters.get("driver");
                if ( value != null )
                    jcd.setDriver(value);
                value = (String) parameters.get("jdbc-level");
                if (value != null)
                    jcd.setJdbcLevel(value);
                value = (String) parameters.get("username");
                if ( value != null )
                    jcd.setUserName(value);
                value = (String) parameters.get("password");
                if ( value != null )
                    jcd.setPassWord(value);
                /*
                jcd.setSequenceDescriptor(defJcd.getSequenceDescriptor());
                jcd.setConnectionPoolDescriptor(defJcd.getConnectionPoolDescriptor());
                jcd.setObjectCacheDescriptor(defJcd.getObjectCacheDescriptor());
                */
                cr.removeDescriptor(defJcd);
                cr.addDescriptor(jcd);
            }
            // Try to set the default PBKey.
            // This only works if it was not set previously.
            // Thus, for the connection defined in Domain.xml to take effect,
            // repository_database.xml MUST NOT contain a descriptor with
            // default-connection=true. We set it as default here.
            defJcd = getDefaultDescriptor(cr);
            defJcd.setDefaultConnection(true);
            mm.setDefaultPBKey(defJcd.getPBKey());
        } catch (MetadataException me) {
            getLogger().log("Error setting default OJB connection.",
                    LOG_CHANNEL, Logger.EMERGENCY);
        }
    }

    /**
     * Retrieves or computes the default connection descriptor.
     * 
     * @param cr the ConnectionRepository
     * @return the default connection descriptor or any declared if there is not default
     * or a newly created one with default parameters if none found.
     */
    protected JdbcConnectionDescriptor getDefaultDescriptor(ConnectionRepository cr) {
        JdbcConnectionDescriptor jcd = null;
        for(Iterator i=cr.getAllDescriptor().iterator();i.hasNext();) {
            jcd = (JdbcConnectionDescriptor)i.next();
            if ( jcd.isDefaultConnection() ) {
                break;
            }
        }
        if ( jcd == null ) {
            // create a default one
            jcd = new JdbcConnectionDescriptor();
            jcd.setDbms("Hsqldb");
            jcd.setJcdAlias("default");
            jcd.setProtocol("jdbc");
            jcd.setSubProtocol("hsqldb");
            jcd.setUserName("sa");
            jcd.setPassWord("");
            jcd.setDbAlias("slide");
            jcd.setDriver("org.hsqldb.jdbcDriver");
            jcd.setJdbcLevel(2.0);
        }
        return jcd;
    }
    
    public void initialize(NamespaceAccessToken token) throws ServiceInitializationFailedException {
        LoggerBridge.setLogger(getLogger());
    }
    
    public void connect() throws ServiceConnectionFailedException {
    }

    public void reset() throws ServiceResetFailedException {
    }

    public void disconnect() throws ServiceDisconnectionFailedException {
    }

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


    // ==== ContentStore Methods ================================

    public abstract NodeRevisionContent retrieveRevisionContent(Uri uri,
            NodeRevisionDescriptor revisionDescriptor)
            throws ServiceAccessException, RevisionNotFoundException;


    // ==== NodeStore Methods ================================

    public ObjectNode retrieveObject(Uri uri)
            throws ServiceAccessException, ObjectNotFoundException {
        
        if (getCurrentlyActiveTransactionalResource() == null) {
            PersistenceBroker broker = null;
            try {
                getLogger().log(
                        "Outside of transaction:  retrieveObject " + uri,
                        LOG_CHANNEL, NO_CONNECTION_LOG_LEVEL);
                broker = getNewBroker();
                if (!tmCommits)
                    broker.beginTransaction();
                return retrieveObject(broker, uri);
            } catch (PersistenceBrokerException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (broker != null) {
                    try {
                        if (!tmCommits) {
                            broker.commitTransaction();
                        }
                    } catch (PersistenceBrokerException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            broker.close();
                        } catch (PersistenceBrokerException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return retrieveObject(getCurrentBroker(), uri);
        }

    }

    public abstract ObjectNode retrieveObject(PersistenceBroker broker, Uri uri)
    	throws ServiceAccessException, ObjectNotFoundException;

    // ==== RevisionDescriptorsStore Methods ================================

    public NodeRevisionDescriptors retrieveRevisionDescriptors(Uri uri)
            throws ServiceAccessException, RevisionDescriptorNotFoundException {
        
        if (getCurrentlyActiveTransactionalResource() == null) {
            PersistenceBroker broker = null;
            try {
                getLogger().log(
                        "Outside of transaction:  retrieveObject " + uri,
                        LOG_CHANNEL, NO_CONNECTION_LOG_LEVEL);
                broker = getNewBroker();
                if (!tmCommits)
                    broker.beginTransaction();
                return retrieveRevisionDescriptors(broker, uri);
            } catch (PersistenceBrokerException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (broker != null) {
                    try {
                        if (!tmCommits) {
                            broker.commitTransaction();
                        }
                    } catch (PersistenceBrokerException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            broker.close();
                        } catch (PersistenceBrokerException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return retrieveRevisionDescriptors(getCurrentBroker(), uri);
        }
    }

    public abstract NodeRevisionDescriptors retrieveRevisionDescriptors(
            PersistenceBroker broker, Uri uri) throws ServiceAccessException,
            RevisionDescriptorNotFoundException;

    // ==== RevisionDescriptorStore Methods ================================

    public NodeRevisionDescriptor retrieveRevisionDescriptor(Uri uri,
            NodeRevisionNumber revisionNumber) throws ServiceAccessException,
            RevisionDescriptorNotFoundException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            PersistenceBroker broker = null;
            try {
                getLogger().log(
                        "Outside of transaction:  retrieveObject " + uri,
                        LOG_CHANNEL, NO_CONNECTION_LOG_LEVEL);
                broker = getNewBroker();
                if (!tmCommits)
                    broker.beginTransaction();
                return retrieveRevisionDescriptor(broker, uri, revisionNumber);
            } catch (PersistenceBrokerException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (broker != null) {
                    try {
                        if (!tmCommits) {
                            broker.commitTransaction();
                        }
                    } catch (PersistenceBrokerException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            broker.close();
                        } catch (PersistenceBrokerException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return retrieveRevisionDescriptor(getCurrentBroker(), uri, revisionNumber);
        }
        
    }
    
    public abstract NodeRevisionDescriptor retrieveRevisionDescriptor(PersistenceBroker broker, Uri uri, NodeRevisionNumber revisionNumber) 
    	throws ServiceAccessException, RevisionDescriptorNotFoundException;

    // ===== SecurityStore Methods ==================
    
    public Enumeration enumeratePermissions(Uri uri) throws ServiceAccessException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            PersistenceBroker broker = null;
            try {
                getLogger().log(
                        "Outside of transaction:  retrieveObject " + uri,
                        LOG_CHANNEL, NO_CONNECTION_LOG_LEVEL);
                broker = getNewBroker();
                if (!tmCommits)
                    broker.beginTransaction();
                return enumeratePermissions(broker, uri);
            } catch (PersistenceBrokerException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (broker != null) {
                    try {
                        if (!tmCommits) {
                            broker.commitTransaction();
                        }
                    } catch (PersistenceBrokerException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            broker.close();
                        } catch (PersistenceBrokerException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return enumeratePermissions(getCurrentBroker(), uri);
        }
    }
    
    public abstract Enumeration enumeratePermissions(PersistenceBroker broker, Uri uri) throws ServiceAccessException;
    
    // ===== LockStore Methods ======================
    
    public Enumeration enumerateLocks(Uri uri) throws ServiceAccessException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            PersistenceBroker broker = null;
            try {
                getLogger().log(
                        "Outside of transaction:  retrieveObject " + uri,
                        LOG_CHANNEL, NO_CONNECTION_LOG_LEVEL);
                broker = getNewBroker();
                if (!tmCommits)
                    broker.beginTransaction();
                return enumerateLocks(broker, uri);
            } catch (PersistenceBrokerException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (broker != null) {
                    try {
                        if (!tmCommits) {
                            broker.commitTransaction();
                        }
                    } catch (PersistenceBrokerException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            broker.close();
                        } catch (PersistenceBrokerException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return enumerateLocks(getCurrentBroker(), uri);
        }
    }
    
    public abstract Enumeration enumerateLocks(PersistenceBroker broker, Uri uri) throws ServiceAccessException;
    
    // ===== SequenceStore Implementation ===========
    
    public boolean isSequenceSupported() {
        return true;
    }
    
    public boolean sequenceExists(String sequenceName) throws ServiceAccessException {
        PersistenceBroker broker = null;
        try {
            broker = getNewBroker();
            if ( !tmCommits )
                broker.beginTransaction();
            Identity oid = broker.serviceIdentity().buildIdentity(SequencePeer.class, sequenceName);
            SequencePeer sequencePeer = (SequencePeer) broker.getObjectByIdentity(oid);
            return ( sequencePeer != null ? true : false );
        } catch (PersistenceBrokerException e) {
            throw new ServiceAccessException(this, e);
        } finally {
            if (broker != null) {
                try {
                    if (!tmCommits) {
                        broker.commitTransaction();
                    }
                } catch (PersistenceBrokerException e) {
                    throw new ServiceAccessException(this, e);
                } finally {
                    try {
                        broker.close();
                    } catch (PersistenceBrokerException e) {
                        getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
            
        }
    }
    
    public boolean createSequence(String sequenceName) throws ServiceAccessException {
        PersistenceBroker broker = null;
        try {
            broker = getNewBroker();
            if ( !tmCommits )
                broker.beginTransaction();
            Identity oid = broker.serviceIdentity().buildIdentity(SequencePeer.class, sequenceName);
            SequencePeer sequencePeer = (SequencePeer) broker.getObjectByIdentity(oid);
            if ( sequencePeer != null )
                throw new ServiceAccessException(this, "Sequence already exists.");
            sequencePeer = new SequencePeer();
            sequencePeer.setName(sequenceName);
            sequencePeer.setNextValue(1);
            broker.store(sequencePeer);
        } catch (PersistenceBrokerException e) {
            throw new ServiceAccessException(this, e);
        } finally {
            if (broker != null) {
                try {
                    if (!tmCommits) {
                        broker.commitTransaction();
                    }
                } catch (PersistenceBrokerException e) {
                    throw new ServiceAccessException(this, e);
                } finally {
                    try {
                        broker.close();
                    } catch (PersistenceBrokerException e) {
                        getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
            
        }
        return true;
    }
    
    /**
     * @see org.apache.slide.store.SequenceStore#nextSequenceValue(java.lang.String)
     */
    public long nextSequenceValue(String sequenceName) throws ServiceAccessException {
        PersistenceBroker broker = null;
        try {
            broker = getNewBroker();
            if ( !tmCommits )
                broker.beginTransaction();
            Identity oid = broker.serviceIdentity().buildIdentity(SequencePeer.class, sequenceName);
            SequencePeer sequencePeer = (SequencePeer) broker.getObjectByIdentity(oid);
            if ( sequencePeer == null )
                throw new ServiceAccessException(this, "Sequence does not exist.");
            long nextValue = sequencePeer.getNextValue();
            sequencePeer.setNextValue(nextValue+1L);
            broker.store(sequencePeer);
            return nextValue;
        } catch (PersistenceBrokerException e) {
            throw new ServiceAccessException(this, e);
        } finally {
            if (broker != null) {
                try {
                    if (!tmCommits) {
                        broker.commitTransaction();
                    }
                } catch (PersistenceBrokerException e) {
                    throw new ServiceAccessException(this, e);
                } finally {
                    try {
                        broker.close();
                    } catch (PersistenceBrokerException e) {
                        getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
            
        }
    }
    
    // ===== XAResource Methods =====================
    
    public int getTransactionTimeout() throws XAException {
        return 0;
    }

    public boolean setTransactionTimeout(int seconds) throws XAException {
        return false;
    }

    public boolean isSameRM(XAResource xares) throws XAException {
    	return (xares == this);
    }

    public Xid[] recover(int flag) throws XAException {

        getLogger().log("recover() for thread: " + Thread.currentThread(),
                LOG_CHANNEL, Logger.DEBUG);
        TransactionalResource id = getCurrentlyActiveTransactionalResource();

        if (id != null && id.getStatus() == STATUS_PREPARED) {
            Xid[] xids = new Xid[1];
            xids[0] = id.getXid();
            return xids;
        } else
            return new Xid[0];
    }
    
    protected boolean includeBranchInXid() {
        return false;
    }
    
    /** 
     * Get the Connection object associated with the current transaction.
     */
    protected PersistenceBroker getCurrentBroker() throws ServiceAccessException {

        getLogger().log("Getting current connection for thread " + Thread.currentThread(), LOG_CHANNEL, Logger.DEBUG);
        TransactionId id = (TransactionId) getCurrentlyActiveTransactionalResource();
        if (id == null) {
            getLogger().log("No id for current thread - called outside transaction?", LOG_CHANNEL, Logger.DEBUG);
            return null;
        }
        return id.broker;
    }

    protected PersistenceBroker getNewBroker() throws PersistenceBrokerException {
       return PersistenceBrokerFactory.defaultPersistenceBroker(); 
    }

    protected TransactionalResource createTransactionResource(Xid xid) throws PersistenceBrokerException {
		return new TransactionId(xid);
    }
    
    private class TransactionId extends AbstractTransactionalResource {
        Xid xid;
        int status;
        PersistenceBroker broker;

        TransactionId(Xid xid) throws PersistenceBrokerException {
            super(xid);
            
            status = STATUS_ACTIVE;
            broker = getNewBroker();
        }
        
        public void begin() throws XAException {
			try {
				if (!tmCommits) {
					broker.beginTransaction();
				}
			} catch (PersistenceBrokerException e) {
				throw new XAException(XAException.XA_RBCOMMFAIL);
			}
        }

        public void commit() throws XAException {
			try {
				if (!tmCommits) {
					broker.commitTransaction();
				}
			} catch (PersistenceBrokerException e) {
				throw new XAException(XAException.XA_RBCOMMFAIL);
			} finally {
				try {
					broker.close();
				} catch (PersistenceBrokerException e) {
					getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
				}
			}
		}
        
        public void rollback() throws XAException {
			try {
				if (!tmCommits) {
					broker.abortTransaction();
				}
			} catch (PersistenceBrokerException e) {
				throw new XAException(XAException.XA_RBCOMMFAIL);
			} finally {
				try {
					broker.close();
				} catch (PersistenceBrokerException e) {
					getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
				}
			}
		}

        public int prepare() throws XAException {
        	// no check possible
        	return XA_OK;
        }

        public void suspend() throws XAException {
        }

        public void resume() throws XAException {
        }

    }

    protected void log(String msg) {
        if (getLogger() == null)
            System.out.println(this.getClass().getName() + ": " + msg);
        else
            getLogger().log(msg, this.getClass().getName(), Logger.DEBUG);
    }


}