/*
 * $Header: /esup-portail/injac/applications/SlideServer/Packages/esup-serveur-WebDav-3/update/slide/add-ons/stores/uPortalStore/src/stores/org/apache/slide/store/impl/rdbms/AbstractRDBMSStore.java,v 1.1 2005/02/08 09:23:45 tbellemb Exp $
 * $Revision: 1.1 $
 * $Date: 2005/02/08 09:23:45 $
 *
 * ====================================================================
 *
 * Copyright 1999-2003 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.impl.rdbms;

import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Enumeration;
import java.util.Hashtable;

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

import org.apache.commons.transaction.util.xa.AbstractTransactionalResource;
import org.apache.commons.transaction.util.xa.TransactionalResource;
import org.apache.slide.common.AbstractXAServiceBase;
import org.apache.slide.common.Service;
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.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.search.basic.BasicExpressionFactory;
import org.apache.slide.search.basic.IBasicExpressionFactory;
import org.apache.slide.search.basic.IBasicExpressionFactoryProvider;
import org.apache.slide.security.NodePermission;
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.store.impl.rdbms.expression.RDBMSExpressionFactory;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.util.logger.Logger;

/**
 * J2EE store implementation - implements the shared parts of
 * the two (content, descriptors) J2EE stores for the new Indexed DB Schema.
 *
 * @version $Revision: 1.1 $
 */
public abstract class AbstractRDBMSStore
    extends AbstractXAServiceBase
    implements
        LockStore,
        NodeStore,
        RevisionDescriptorsStore,
        RevisionDescriptorStore,
        SecurityStore,
        ContentStore,
        SequenceStore,
        IBasicExpressionFactoryProvider {

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

    protected RDBMSAdapter adapter;

    protected boolean alreadyInitialized = false;
    
    protected int isSequenceSupported = 0;
    
    protected boolean useRdbmsExpressionFactory;

    /**
     * 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;

    /**
     * Initializes the data source with a set of parameters.
     *
     * @param parameters Hashtable containing the parameters' name 
     *                   and associated value
     * @exception ServiceParameterErrorException   Incorrect service parameter
     * @exception ServiceParameterMissingException Service parameter missing
     */
    public void setParameters(Hashtable parameters)
        throws ServiceParameterErrorException, ServiceParameterMissingException {

        // Adapter class
        String value = (String) parameters.get("adapter");
        if (value == null) {
            adapter = new StandardRDBMSAdapter(this, getLogger());
        } else {
            try {
                Class adapterClass = Class.forName(value);
                Constructor ctor = adapterClass.getConstructor(new Class[] { Service.class, Logger.class });
                adapter = (RDBMSAdapter) ctor.newInstance(new Object[] { this, getLogger()});
            } catch (Exception e) {
                // reflection exception
                getLogger().log(
                    "Error instantiating Adapter '" + value + "' (" + e.getMessage() + ")",
                    LOG_CHANNEL,
                    Logger.ERROR);
            }
        }

        if (adapter != null) {
            adapter.setParameters(parameters);
        }

        // Use RDBMSExpressionFactory?
        value = (String) parameters.get("use-rdbms-expression-factory");
        this.useRdbmsExpressionFactory = Boolean.valueOf(value).booleanValue();
        
        // XXX need to initialize it here, as some security requests access the store before initialization
        try {
            initialize(null);
        } catch (ServiceInitializationFailedException e) {
            // XXX this is not very satisfactory...
            throw new ServiceParameterErrorException(this, e.getMessage());
        }
    }

    /**
     * Establishes the global connection to the data source.
     * 
     * @exception ServiceConnectionFailedException if the connection failed
     */
    public void connect() throws ServiceConnectionFailedException {
    }

    /**
     * Returns connection status.
     */
    public boolean isConnected() {
        return true;
    }

    /**
     * Closes the global connection to the data source.
     *
     * @exception ServiceDisconnectionFailedException if closing the connection
     *            failed
     */
    public void disconnect() throws ServiceDisconnectionFailedException {
    }

    /**
     * Does nothing.
     *
     * @exception ServiceResetFailedException Reset failed
     */
    public void reset() throws ServiceResetFailedException {

    }

    
    // ----------------------------------------------------- XAResource Methods

    /**
     * Get the transaction timeout value for this XAResource.
     * Just returns 0, we don't have a way of doing transaction timeouts
     * with the connection.
     */
    public int getTransactionTimeout() throws XAException {
        return 0;
    }

    /**
     * Set transaction timeout, not implemented (returns false).
     */
    public boolean setTransactionTimeout(int timeout) throws XAException {
        return false;
    }

    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];
    }

    public boolean isSameRM(XAResource xares) throws XAException {
    	return (xares == this);
    }
    
    // ----------------------------------------------- IBasicExpressionFactoryProvider Implementation
    
    public IBasicExpressionFactory getBasicExpressionFactory() {
        if (this.useRdbmsExpressionFactory) {
            return new RDBMSExpressionFactory(this);
        }
        else {
            return new BasicExpressionFactory();
        }
    }

    // ----------------------------------------------- SequenceStore Implementation

    /**
     * @see org.apache.slide.store.SequenceStore#createSequence(java.lang.String)
     */
    public boolean createSequence(String sequenceName) throws ServiceAccessException {
        if (!isSequenceSupported()) {
            throw new ServiceAccessException(this, "Sequences not supported");
        }
        Connection connection = null;
        try {
            connection = getNewConnection();
            return ((SequenceAdapter) adapter).createSequence(connection, sequenceName);
        } catch (SQLException e) {
            throw new ServiceAccessException(this, e);
        } finally {
            if (connection != null) {
                try {
                    if (!tmCommits) {
                        connection.commit();
                    }
                } catch (SQLException e) {
                    throw new ServiceAccessException(this, e);
                } finally {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
        }
    }

    /**
     * @see org.apache.slide.store.SequenceStore#isSequenceSupported()
     */
    public boolean isSequenceSupported() {
        // cache it for better performance as support of an adapter will 
        // not change will running, it is either yes or no all the time
        if (isSequenceSupported > 0)
            return true;
        if (isSequenceSupported < 0)
            return false;
        Connection connection = null;
        try {
            connection = getNewConnection();
            if (adapter instanceof SequenceAdapter && ((SequenceAdapter) adapter).isSequenceSupported(connection)) {
                isSequenceSupported = 1;
                return true;
            } else {
                isSequenceSupported = -1;
                return false;
            }
        } catch (SQLException e) {
            getLogger().log(
                "Error while trying to check for sequence support. Assuming false",
                e,
                LOG_CHANNEL,
                Logger.ERROR);
            isSequenceSupported = -1;
            return false;
        } finally {
            if (connection != null) {
                try {
                    if (!tmCommits) {
                        connection.commit();
                    }
                } catch (SQLException e) {
                    getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                } finally {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
        }
    }

    /**
     * @see org.apache.slide.store.SequenceStore#nextSequenceValue(java.lang.String)
     */
    public long nextSequenceValue(String sequenceName) throws ServiceAccessException {
        if (!isSequenceSupported()) {
            throw new ServiceAccessException(this, "Sequences not supported");
        }
        Connection connection = null;
        try {
            connection = getNewConnection();
            return ((SequenceAdapter) adapter).nextSequenceValue(connection, sequenceName);
        } catch (SQLException e) {
            throw new ServiceAccessException(this, e);
        } finally {
            if (connection != null) {
                try {
                    if (!tmCommits) {
                        connection.commit();
                    }
                } catch (SQLException e) {
                    throw new ServiceAccessException(this, e);
                } finally {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
        }
    }

    /**
     * @see org.apache.slide.store.SequenceStore#sequenceExists(java.lang.String)
     */
    public boolean sequenceExists(String sequenceName) throws ServiceAccessException {
        if (!isSequenceSupported()) {
            throw new ServiceAccessException(this, "Sequences not supported");
        }
        Connection connection = null;
        try {
            connection = getNewConnection();
            return ((SequenceAdapter) adapter).sequenceExists(connection, sequenceName);
        } catch (SQLException e) {
            throw new ServiceAccessException(this, e);
        } finally {
            if (connection != null) {
                try {
                    if (!tmCommits) {
                        connection.commit();
                    }
                } catch (SQLException e) {
                    throw new ServiceAccessException(this, e);
                } finally {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                    }
                }
            }
        }
    }

    // ----------------------------------------------- NodeStore Implementation

    /**
     * Retrieve an object.
     *
     * @param uri Uri of the object we want to retrieve
     * @exception ServiceAccessException Error accessing the Service
     * @exception ObjectNotFoundException The object to retrieve was not found
     */
    public ObjectNode retrieveObject(Uri uri) throws ServiceAccessException, ObjectNotFoundException {

        if (getCurrentlyActiveTransactionalResource() == null) {
            Connection connection = null;
            try {
                connection = getNewConnection();
                return adapter.retrieveObject(connection, uri);
            } catch (SQLException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (connection != null) {
                    try {
                        if (!tmCommits) {
                            connection.commit();
                        }
                    } catch (SQLException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            connection.close();
                        } catch (SQLException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return adapter.retrieveObject(getCurrentConnection(), uri);
        }
    }

    /**
     * Update an object.
     *
     * @param object Object to update
     * @exception ServiceAccessException Error accessing the Service
     * @exception ObjectNotFoundException The object to update was not found
     */
    public void storeObject(Uri uri, ObjectNode object) throws ServiceAccessException, ObjectNotFoundException {

        adapter.storeObject(getCurrentConnection(), uri, object);
    }

    /**
     * Create a new object.
     *
     * @param object ObjectNode
     * @param uri Uri of the object we want to create
     * @exception ServiceAccessException Error accessing the Service
     * @exception ObjectAlreadyExistsException An object already exists
     *            at this Uri
     */
    public void createObject(Uri uri, ObjectNode object) throws ServiceAccessException, ObjectAlreadyExistsException {

        adapter.createObject(getCurrentConnection(), uri, object);
    }

    /**
     * Remove an object.
     *
     * @param object Object to remove
     * @exception ServiceAccessException Error accessing the Service
     * @exception ObjectNotFoundException The object to remove was not found
     */
    public void removeObject(Uri uri, ObjectNode object) throws ServiceAccessException, ObjectNotFoundException {

        adapter.removeObject(getCurrentConnection(), uri, object);
    }

    // ------------------------------------------- SecurityStore Implementation

    /**
     * Grant a new permission.
     *
     * @param permission Permission we want to create
     * @exception ServiceAccessException Error accessing the Service
     */
    public void grantPermission(Uri uri, NodePermission permission) throws ServiceAccessException {

        adapter.grantPermission(getCurrentConnection(), uri, permission);
    }

    /**
     * Revoke a permission.
     *
     * @param permission Permission we want to create
     * @exception ServiceAccessException Error accessing the Service
     */
    public void revokePermission(Uri uri, NodePermission permission) throws ServiceAccessException {

        adapter.revokePermission(getCurrentConnection(), uri, permission);
    }

    /**
     * Revoke all the permissions on an object.
     *
     * @param permission Permission we want to create
     * @exception ServiceAccessException Error accessing the Service
     */
    public void revokePermissions(Uri uri) throws ServiceAccessException {

        adapter.revokePermissions(getCurrentConnection(), uri);
    }

    /**
     * Enumerate permissions on an object.
     *
     * @param permission Permission we want to create
     * @exception ServiceAccessException Error accessing the Service
     */
    public Enumeration enumeratePermissions(Uri uri) throws ServiceAccessException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            Connection connection = null;
            try {
                connection = getNewConnection();
                return adapter.enumeratePermissions(connection, uri);
            } catch (SQLException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (connection != null) {
                    try {
                        if (!tmCommits) {
                            connection.commit();
                        }
                    } catch (SQLException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            connection.close();
                        } catch (SQLException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return adapter.enumeratePermissions(getCurrentConnection(), uri);
        }
    }

    // ----------------------------------------------- LockStore Implementation

    /**
     * Create a new lock.
     *
     * @param lock Lock token
     * @exception ServiceAccessException Service access error
     */
    public void putLock(Uri uri, NodeLock lock) throws ServiceAccessException {

        adapter.putLock(getCurrentConnection(), uri, lock);
    }

    /**
     * Refresh a lock.
     *
     * @param lock the lock to renew
     * @exception ServiceAccessException Service access error
     * @exception LockTokenNotFoundException Lock token was not found
     */
    public void renewLock(Uri uri, NodeLock lock) throws ServiceAccessException, LockTokenNotFoundException {

        adapter.renewLock(getCurrentConnection(), uri, lock);
    }

    /**
     * Unlock.
     *
     * @param lock Token to remove
     * @exception ServiceAccessException Service access error
     * @exception LockTokenNotFoundException Lock token was not found
     */
    public void removeLock(Uri uri, NodeLock lock) throws ServiceAccessException, LockTokenNotFoundException {

        adapter.removeLock(getCurrentConnection(), uri, lock);
    }

    /**
     * Kill a lock.
     *
     * @param lock Token to remove
     * @exception ServiceAccessException Service access error
     * @exception LockTokenNotFoundException Lock token was not found
     */
    public void killLock(Uri uri, NodeLock lock) throws ServiceAccessException, LockTokenNotFoundException {

        adapter.killLock(getCurrentConnection(), uri, lock);
    }

    /**
     * Enumerate locks on an object.
     *
     * @param subject Subject
     * @return Enumeration List of locks which have been put on the subject
     * @exception ServiceAccessException Service access error
     */
    public Enumeration enumerateLocks(Uri uri) throws ServiceAccessException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            Connection connection = null;
            try {
                connection = getNewConnection();
                return adapter.enumerateLocks(connection, uri);
            } catch (SQLException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (connection != null) {
                    try {
                        if (!tmCommits) {
                            connection.commit();
                        }
                    } catch (SQLException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            connection.close();
                        } catch (SQLException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return adapter.enumerateLocks(getCurrentConnection(), uri);
        }
    }

    // -------------------------------- RevisionDescriptorsStore Implementation

    /**
     * Retrieve the revisions informations of an object.
     *
     * @param uri Uri
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public NodeRevisionDescriptors retrieveRevisionDescriptors(Uri uri)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            Connection connection = null;
            try {
                connection = getNewConnection();
                return adapter.retrieveRevisionDescriptors(connection, uri);
            } catch (SQLException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (connection != null) {
                    try {
                        if (!tmCommits) {
                            connection.commit();
                        }
                    } catch (SQLException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            connection.close();
                        } catch (SQLException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return adapter.retrieveRevisionDescriptors(getCurrentConnection(), uri);
        }
    }

    /**
     * Create a new revision information object.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptors(Uri uri, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException {

        adapter.createRevisionDescriptors(getCurrentConnection(), uri, revisionDescriptors);
    }

    /**
     * Update revision information.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptors(Uri uri, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {

        adapter.storeRevisionDescriptors(getCurrentConnection(), uri, revisionDescriptors);
    }

    /**
     * Remove revision information.
     *
     * @param uri Uri
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptors(Uri uri) throws ServiceAccessException {

        adapter.removeRevisionDescriptors(getCurrentConnection(), uri);
    }

    // --------------------------------- RevisionDescriptorStore Implementation

    /**
     * Retrieve an individual object's revision descriptor.
     *
     * @param Uri uri
     * @param revisionNumber Node revision number
     */
    public NodeRevisionDescriptor retrieveRevisionDescriptor(Uri uri, NodeRevisionNumber revisionNumber)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            Connection connection = null;
            try {
                connection = getNewConnection();
                return adapter.retrieveRevisionDescriptor(connection, uri, revisionNumber);
            } catch (SQLException e) {
                throw new ServiceAccessException(this, e);
            } finally {
                if (connection != null) {
                    try {
                        if (!tmCommits) {
                            connection.commit();
                        }
                    } catch (SQLException e) {
                        throw new ServiceAccessException(this, e);
                    } finally {
                        try {
                            connection.close();
                        } catch (SQLException e) {
                            getLogger().log(e, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
            }
        } else {
            return adapter.retrieveRevisionDescriptor(getCurrentConnection(), uri, revisionNumber);
        }
    }

    /**
     * Create a new revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptor(Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException {

        adapter.createRevisionDescriptor(getCurrentConnection(), uri, revisionDescriptor);
    }

    /**
     * Update a revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptor
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptor(Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {

        adapter.storeRevisionDescriptor(getCurrentConnection(), uri, revisionDescriptor);
    }

    /**
     * Remove a revision descriptor.
     *
     * @param uri Uri
     * @param revisionNumber Revision number
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptor(Uri uri, NodeRevisionNumber revisionNumber) throws ServiceAccessException {

        adapter.removeRevisionDescriptor(getCurrentConnection(), uri, revisionNumber);
    }

    // -------------------------------------------- ContentStore Implementation

    /**
     * Retrieve revision content.
     *
     * @param uri Uri
     * @param revisionNumber Node revision number
     */
    public NodeRevisionContent retrieveRevisionContent(Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException, RevisionNotFoundException {
        if (getCurrentlyActiveTransactionalResource() == null) {
            Connection connection = null;
            try {
                connection = getNewConnection();
                return adapter.retrieveRevisionContent(connection, uri, revisionDescriptor, true);
            } catch (SQLException e) {
                // only close connection in case of error as standard closing
                // will be done in adapter or upon closing of stream
                if (connection != null) {
                    try {
                        if (!tmCommits) {
                            connection.rollback();
                        }
                    } catch (SQLException e2) {
                        throw new ServiceAccessException(this, e2);
                    } finally {
                        try {
                            connection.close();
                        } catch (SQLException e2) {
                            getLogger().log(e2, LOG_CHANNEL, Logger.WARNING);
                        }
                    }
                }
                throw new ServiceAccessException(this, e);
            }
        } else {
            return adapter.retrieveRevisionContent(getCurrentConnection(), uri, revisionDescriptor, false);
        }
    }

    /**
     * Create a new revision
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     */
    public void createRevisionContent(
        Uri uri,
        NodeRevisionDescriptor revisionDescriptor,
        NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionAlreadyExistException {

        adapter.createRevisionContent(getCurrentConnection(), uri, revisionDescriptor, revisionContent);
    }

    /**
     * Modify the latest revision of an object.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     */
    public void storeRevisionContent(
        Uri uri,
        NodeRevisionDescriptor revisionDescriptor,
        NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionNotFoundException {

        adapter.storeRevisionContent(getCurrentConnection(), uri, revisionDescriptor, revisionContent);
    }

    /**
     * Remove revision.
     *
     * @param uri Uri
     * @param revisionNumber Node revision number
     */
    public void removeRevisionContent(Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException {

        adapter.removeRevisionContent(getCurrentConnection(), uri, revisionDescriptor);
    }

    // ------------------------------------------------------ Protected Methods

    // XXX just for visibility in RDBMSComparableResourcesPool
    protected TransactionalResource getCurrentlyActiveTransactionalResource() {
        return super.getCurrentlyActiveTransactionalResource();
    }
    
    /** 
     * Get the Connection object associated with the current transaction.
     */
    protected Connection getCurrentConnection() 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.connection;
    }

    abstract protected Connection getNewConnection() throws SQLException;

    protected TransactionalResource createTransactionResource(Xid xid) throws SQLException {
			return new TransactionId(xid);
    }
    
    private class TransactionId extends AbstractTransactionalResource {
        Xid xid;
        int status;
        Connection connection;

        TransactionId(Xid xid) throws SQLException {
            super(xid);
            
            status = STATUS_ACTIVE;
            connection = getNewConnection();
        }

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

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

        public void begin() throws XAException {
        }

        public void suspend() throws XAException {
        }

        public void resume() throws XAException {
        }

    }

}
