/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/store/XDescriptorsStore.java,v 1.5 2005/02/23 16:36:54 pnever Exp $
 * $Revision: 1.5 $
 * $Date: 2005/02/23 16:36:54 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 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.tamino.store;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.apache.slide.common.NamespaceAccessToken;
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.ServiceResetFailedException;
import org.apache.slide.common.Uri;
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.lock.LockTokenNotFoundException;
import org.apache.slide.lock.NodeLock;
import org.apache.slide.search.basic.IBasicExpressionFactory;
import org.apache.slide.search.basic.IBasicExpressionFactoryProvider;
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.tamino.common.IDescriptors;
import org.apache.slide.store.tamino.common.IDescriptorsHandler;
import org.apache.slide.store.tamino.common.XDescriptorsHandler;
import org.apache.slide.store.tamino.common.XResolver;
import org.apache.slide.store.tamino.datastore.search.XBasicExpressionFactory;
import org.apache.slide.store.tamino.jdomobjects.XNodeRevisionDescriptor;
import org.apache.slide.store.tamino.jdomobjects.XTLock;
import org.apache.slide.store.tamino.jdomobjects.XUuri;
import org.apache.slide.store.tamino.store.monitoring.Monitor;
import org.apache.slide.store.tamino.tools.stores.XDomainFileHandler;
import org.apache.slide.store.tamino.tools.stores.XNamespace;
import org.apache.slide.store.tamino.tools.stores.XStoreTypeList;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.urm.URMException;
import org.apache.slide.urm.authenticator.URMAuthenticator;
import org.apache.slide.urm.common.URMPrincipal;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;

/**
 * Tamino-based implementation of DescriptorsStore.
 *
 * @author    peter.nevermann@softwareag.com
 *
 * @version   $Revision: 1.5 $
 *
 */
public class XDescriptorsStore extends XChildStore
    implements LockStore, NodeStore,
    RevisionDescriptorsStore, RevisionDescriptorStore, IBasicExpressionFactoryProvider
{
    
    
    /**
     * Returns the store / indexer specific ExpressionFactory.
     *
     * @return   an IBasicExpressionFactory
     *
     */
    public IBasicExpressionFactory getBasicExpressionFactory()
    {
        return new XBasicExpressionFactory();
    }
    
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    private XNamespace ns;
    private List historyStores;
    
    /**
     * The descriptors handler
     */
    IDescriptorsHandler descriptorsHandler = null;
    
    
    /**
     ** Default constructor.
     **/
    public XDescriptorsStore() throws XException {
    }
    
    public IDescriptorsHandler getDescriptorsHandler() {
        return descriptorsHandler;
    }
    /**
     * Connects to descriptors store.
     *
     * @exception ServiceConnectionFailedException
     */
    public synchronized void connect() throws ServiceConnectionFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "connect" );
        
        super.connect();
        // something more to do ???
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "connect" );
    }
    
    /**
     * Disconnects from descriptors store.
     *
     * @exception ServiceDisconnectionFailedException
     */
    public void disconnect() throws ServiceDisconnectionFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "disconnect" );
        
        super.disconnect();
        // something more to do ???
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "disconnect" );
    }
    
    /**
     * <p>Initializes descriptors store.
     * <p>Instanciates a new descriptors handler.
     * @param token name space token
     * @exception ServiceInitializationFailedException Throws an exception
     * if the descriptors store has already been initialized before
     */
    public synchronized void initialize(NamespaceAccessToken token)
        throws ServiceInitializationFailedException {

        try {
            ns = XDomainFileHandler.get().getNamespace(token.getName());
        } catch (XException e) {
            throw new ServiceInitializationFailedException(this, e);
        }
        historyStores = ns.getStoresByType(XStoreTypeList.HISTORY_NAME);
        
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "initialize",
                                                            new Object[] {(token!=null ? token.getName() : null)} );
        
        descriptorsHandler = new XDescriptorsHandler(this, getTLockSettings());
        
        // monitoring variables
        this.monName = "DescriptorsStore";
        this.monParent = getParentStore();
        this.monChildren.add( descriptorsHandler );
        this.monitor = Monitor.getMonitor( this ); // this comes last!!
        
        // init the children
        descriptorsHandler.initialize();
        super.initialize( token );
        checkBindingConfig();
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "initialize" );
    }
    
    private void checkBindingConfig() throws ServiceInitializationFailedException {
        boolean withBinding;
        boolean withoutBinding;
        String scope;
        boolean configured;
        
        scope = getScope().toString();
        withoutBinding = probe(scope);
        withBinding = probe(XUuri.getStoreUuri(true, new XUri(scope)));
        if (withBinding && withoutBinding) {
            throw new XAssertionFailed();
        }
        if (!withBinding && !withoutBinding) {
            return; // no meta data yet
        }
        configured = descriptorsHandler.useBinding();
        if (configured != withBinding) {
            throw new ServiceInitializationFailedException(this,
                                                           "binding configuration does not match metadata: configured: " + configured + ", metadata: " + withBinding);
        }
    }
    
    private boolean probe(String uriOrUuri) {
        try {
            return descriptorsHandler.lookup(null, uriOrUuri, XTLock.NO_LOCK) != null;
        } catch (XException e) {
            return false;
        }
    }
    
    /**
     * Deletes descriptors store. Should remove stored data if possible.
     *
     * @exception ServiceResetFailedException Reset failed
     */
    public synchronized void reset() throws ServiceResetFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "reset" );
        
        // What to do?
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "reset" );
    }
    
    /**
     * Ask the resource manager to prepare for a transaction commit of the
     * transaction specified in xid.
     *
     * @param xid A global transaction identifier
     *
     * @return A value indicating the resource manager's vote on the outcome
     * of the transaction. The possible values are: XA_RDONLY or XA_OK. If
     * the resource manager wants to roll back the transaction, it should do
     * so by raising an appropriate XAException in the prepare method.
     *
     * @exception XAException An error has occurred. Possible exception
     * values are: XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL,
     * or XAER_PROTO.
     */
    public int prepare(Xid xid) throws XAException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "prepare", new Object[] {xid}  );
        
        getParentStore().prepareCounter.increment(); // monitoring
        Object timerId = getParentStore().prepareTimer.start(); // monitoring
        
        int result = super.prepare( xid );
        try {
            descriptorsHandler.prepare();
        }
        catch( XException x ) {
            x.printStackTrace();
            getParentStore().prepareTimer.stop(timerId); // monitoring
            throw new XAException( XAException.XA_RBCOMMFAIL );
        }
        
        getParentStore().prepareTimer.stop(timerId); // monitoring
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "prepare", new Integer(result) );
        return result;
    }
    
    /**
     * Commit the global transaction specified by xid.
     * @param xid transaction id
     * @param onePhase true in case of a one-phase-commit
     * @exception XAException transaction error
     */
    public void commit( Xid xid, boolean onePhase ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "commit",
                                                            new Object[] {xid, new Boolean(onePhase)}  );
        
        getParentStore().commitCounter.increment(); // monitoring
        Object timerId = getParentStore().commitTimer.start(); // monitoring
        
        super.commit( xid, onePhase );
        try {
            descriptorsHandler.commit( onePhase );
        }
        catch( XException x ) {
            x.printStackTrace();
            getParentStore().commitTimer.stop(timerId); // monitoring
            throw new XAException( XAException.XA_RBCOMMFAIL );
        }
        
        getParentStore().commitTimer.stop(timerId); // monitoring
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "commit" );
    }
    
    /**
     * Inform the resource manager to roll back work done on behalf of a
     * transaction branch.
     * @param xid transaction id
     * @exception XAException transaction error
     */
    public void rollback( Xid xid ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "rollback",
                                                            new Object[] {xid}  );
        
        getParentStore().rollbackCounter.increment(); // monitoring
        Object timerId = getParentStore().rollbackTimer.start(); // monitoring
        
        try {
            super.rollback( xid );
        }
        catch( XAException x ) {
            //print the stack trace here because the XAException could be overwritten in
            //finally
            x.printStackTrace();
            throw x;
        }
        finally {
            try {
                descriptorsHandler.rollback();
            }
            catch( XException x ) {
                x.printStackTrace();
                throw new XAException( XAException.XA_RBCOMMFAIL );
            }
            finally {
                getParentStore().rollbackTimer.stop(timerId); // monitoring
            }
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "rollback" );
    }
    
    /**
     * Retrieve an object from the Descriptors Store.
     *
     * @param uri Uri of the object we want to retrieve
     * @return object node
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to retrieve was not found
     */
    public ObjectNode retrieveObject(Uri uri)
        throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "retrieveObject",
                                                            new Object[] {uri} );
        
        getParentStore().retrieveObjectCounter.increment(); // monitoring
        Object timerId = getParentStore().retrieveObjectTimer.start();
        
        ObjectNode result;
        IDescriptors d = resolve( uri );
        if( d == null ) {
            throw new ObjectNotFoundException( uri );
        }
        result = d.getObjectNode(uri.toString());
        if( result == null ) {
            throw new ObjectNotFoundException( uri );
        }
        getParentStore().retrieveObjectTimer.stop( timerId );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "retrieveObject",
                                                               (result!=null ? result.getUri() : null) );
        return result;
    }
    
    /**
     * Store an object in the Descriptors Store.
     *
     * @param uri Uri of the object we want to update
     * @param object Object to update
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to update was not found
     */
    public void storeObject(Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "storeObject",
                                                            new Object[] {uri, (object!=null ? object.getUri() : null)} );
        
        getParentStore().storeObjectCounter.increment(); // monitoring
        Object timerId = getParentStore().storeObjectTimer.start();
        
        IDescriptors d = resolveWriteable(uri);
        if( d == null || d.getObjectNode(uri.toString()) == null )
            throw new ObjectNotFoundException( uri );
        
        d.removeObjectNode();
        d.setObjectNode( object );
        getParentStore().storeObjectTimer.stop( timerId );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "storeObject" );
    }
    
    /**
     * Create a new object in the Descriptors Store.
     *
     * @param uri Uri of the object we want to create
     * @param object SlideObject
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectAlreadyExistsException An object already exists
     * at this Uri
     */
    public void createObject(Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectAlreadyExistsException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "createObject",
                                                            new Object[] {uri, (object!=null ? object.getUri() : null)} );
        
        getParentStore().createObjectCounter.increment(); // monitoring
        Object timerId = getParentStore().createObjectTimer.start();
        IDescriptors d = resolveCreate(uri);
        if (d.getObjectNode(uri.toString()) != null) {
            throw new ObjectAlreadyExistsException(uri.toString());
        }
        d.setObjectNode( object );
        
        if (Configuration.useIntegratedSecurity()) {
            try {
                getGuest(this).getURMAclAdministrator().addTransientEmptyAcl(d.getUuri());
            } catch (URMException e) {
                e.printStackTrace();
            }  // ignore the exception; the method is called for performance reasons only
        }
        getParentStore().createObjectTimer.stop( timerId );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "createObject" );
    }
    
    /**
     * Remove an object from the Descriptors Store.
     *
     * @param uri Uri of the object we want to remove
     * @param object Object to remove **IGNORED**
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectNotFoundException The object to remove was not found
     */
    public void removeObject(Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "removeObject",
                                                            new Object[] {uri, (object!=null ? object.getUri() : null)} );
        
        getParentStore().removeObjectCounter.increment(); // monitoring
        Object timerId = getParentStore().removeObjectTimer.start();
        
        IDescriptors d = resolveWriteable(uri);
        if( d == null || d.getObjectNode(String.valueOf(uri)) == null )
            throw new ObjectNotFoundException( uri );
        
        d.removeObjectNode();
        
        getParentStore().removeObjectTimer.stop( timerId );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "removeObject" );
    }
    
    /**
     * Puts a lock on a subject.
     *
     * @param uri Uri of the resource
     * @param lock Lock token
     * @exception ServiceAccessException Service access error
     */
    public void putLock(Uri uri, NodeLock lock) throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "putLock",
                                                            new Object[] {uri, (lock!=null ? lock.getLockId() : null)} );
        
        IDescriptors d = resolveCreate(uri);
        d.setNodeLock( lock );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "putLock" );
    }
    
    /**
     * Renews a lock.
     *
     * @param uri Uri of the resource
     * @param lock Token to renew
     * @exception ServiceAccessException Service access error
     * @exception LockTokenNotFoundException Lock token was not found
     */
    public void renewLock(Uri uri, NodeLock lock)
        throws ServiceAccessException, LockTokenNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "renewLock",
                                                            new Object[] {uri, (lock!=null ? lock.getLockId() : null)} );
        
        IDescriptors d = resolveWriteable(uri);
        if (d == null || !d.removeLock(lock) )
            throw new LockTokenNotFoundException( lock );
        
        d.setNodeLock( lock );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "renewLock" );
    }
    
    /**
     * Removes (cancels) a lock.
     *
     * @param uri Uri of the resource
     * @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 {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "removeLock",
                                                            new Object[] {uri, (lock!=null ? lock.getLockId() : null)} );
        
        IDescriptors d = resolveWriteable(uri);
        if( d == null || !d.removeLock(lock) )
            throw new LockTokenNotFoundException( lock );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "removeLock" );
    }
    
    /**
     * Kills a lock.
     *
     * @param uri Uri of the resource
     * @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 {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "killLock",
                                                            new Object[] {uri, (lock!=null ? lock.getLockId() : null)} );
        
        // What's the difference between killLock and removeLock??
        removeLock(uri, lock);
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "killLock" );
    }
    
    /**
     * Returns the list of locks put on a subject.
     *
     * @param uri Uri of the resource
     * @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( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "enumerateLocks",
                                                            new Object[] {uri} );
        
        Enumeration result;
        IDescriptors d = resolve( uri );
        if (d == null) {
            result = new Vector().elements();
        } else {
            result = d.getLocks(uri.toString());
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "enumerateLocks",
                                                           result );
        return result;
    }
    
    /**
     * Retrieve a revision descriptors.
     *
     * @param uri Uri
     * @return a node revision descriptors
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public NodeRevisionDescriptors retrieveRevisionDescriptors(Uri uri)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "retrieveRevisionDescriptors", new Object[] {uri} );
        
        NodeRevisionDescriptors result = null;
        IDescriptors d = resolve( uri );
        if( d == null ) {
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        }
        result = d.getRevisionDescriptors( uri.toString());
        if( result == null ) {
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "retrieveRevisionDescriptors", (result!=null ? result.getUri() : null) );
        return result;
    }
    
    /**
     * Create new revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptors
        (Uri uri, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "createRevisionDescriptors", new Object[] {uri,
                            (revisionDescriptors!=null ? revisionDescriptors.getUri() : null)} );
        
        IDescriptors d = resolveCreate(uri);
        
        // Just to be sure: remove existing
        NodeRevisionDescriptors rds = d.getRevisionDescriptors( uri.toString() );
        if( rds != null )
            //d.removeRevisionDescriptors( rds );
            throw new IllegalStateException(
                "Revision descriptors already exist: uri="+uri+" rds="+rds );
        
        d.setRevisionDescriptors( revisionDescriptors );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "createRevisionDescriptors" );
    }
    
    /**
     * Update revision descriptors.
     *
     * @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 {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "storeRevisionDescriptors", new Object[] {uri,
                            (revisionDescriptors!=null ? revisionDescriptors.getUri() : null)} );
        
        IDescriptors d = resolveWriteable(uri);
        if( d == null ) {
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        }
        NodeRevisionDescriptors rds = d.getRevisionDescriptors( uri.toString() );
        if( rds == null ) {
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        }
        d.removeRevisionDescriptors( rds );
        d.setRevisionDescriptors( revisionDescriptors );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "storeRevisionDescriptors" );
    }
    
    /**
     * Remove revision descriptors.
     *
     * @param uri Uri
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptors(Uri uri) throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "removeRevisionDescriptors", new Object[] {uri} );
        
        IDescriptors d = resolveWriteable(uri);
        if (d != null) {
            NodeRevisionDescriptors rds = d.getRevisionDescriptors(uri.toString());
            if( rds != null ) {
                d.removeRevisionDescriptors( rds );
            }
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "removeRevisionDescriptors" );
    }
    
    /**
     * Retrieve revision descriptor.
     *
     * @param uri uri
     * @param revisionNumber Node revision number
     * @return a node revision descriptor
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException revision descriptor not found
     */
    public NodeRevisionDescriptor retrieveRevisionDescriptor
        (Uri uri, NodeRevisionNumber revisionNumber)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "retrieveRevisionDescriptor", new Object[] {uri, revisionNumber} );
        
        NodeRevisionDescriptor result;
        IDescriptors d = resolve( convertHistory(uri, revisionNumber) );
        if( d == null ) {
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        }
        result = d.getRevisionDescriptor( revisionNumber );
        if( result == null ) {
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        }
        try {
            if (d.getUuriObjectNode() != null) {
                XNodeRevisionDescriptor.addSpecialProperties(getDescriptorsHandler(), d.getObjectNode(uri.toString()), result);
            }
        }
        catch (XException e) {
            throw serviceAccessException(e, uri);
        }
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "retrieveRevisionDescriptor", (result!=null
                                                                                              ? result.getRevisionNumber()+"@"+result.getBranchName()
                                                                                              : null) );
        return result;
    }
    
    /**
     * Create 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 {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME,
                            "createRevisionDescriptor",
                            new Object[] {uri, (revisionDescriptor!=null
                                                    ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                                    : null)} );
        
        
        IDescriptors d;
        
        NodeRevisionNumber nrn = revisionDescriptor.getRevisionNumber();
        d = resolveCreate( convertHistory(uri, nrn) );
        NodeRevisionDescriptor rd = d.getRevisionDescriptor( nrn );
        if( rd != null )
            //d.removeRevisionDescriptor( rd );
            throw new IllegalStateException(
                "Revision descriptor already exist: uri="+uri+" rd="+rd );
        
        d.setRevisionDescriptor( revisionDescriptor );
        
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "createRevisionDescriptor" );
    }
    
    /**
     * Update revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor 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 {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME,
                            "storeRevisionDescriptor",
                            new Object[] {uri, (revisionDescriptor!=null
                                                    ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                                    : null)} );
        
        NodeRevisionNumber nrn = revisionDescriptor.getRevisionNumber();
        IDescriptors d = resolveWriteable(convertHistory(uri, nrn));
        if(d == null )
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        NodeRevisionDescriptor rd = d.getRevisionDescriptor( nrn );
        if( rd == null )
            throw new RevisionDescriptorNotFoundException( String.valueOf(uri) );
        
        d.removeRevisionDescriptor( rd );
        d.setRevisionDescriptor( revisionDescriptor );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "storeRevisionDescriptor" );
    }
    
    /**
     * Remove revision descriptor.
     *
     * @param uri Uri
     * @param number Revision number
     * @exception ServiceAccessException Service access error
     */
    public void removeRevisionDescriptor(Uri uri, NodeRevisionNumber number)
        throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "removeRevisionDescriptor", new Object[] {uri, number} );
        
        IDescriptors d = resolveWriteable(convertHistory(uri, number));
        if (d != null) {
            NodeRevisionDescriptor rd = d.getRevisionDescriptor( number );
            if( rd != null )
                d.removeRevisionDescriptor( rd );
        }
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "removeRevisionDescriptor" );
    }
    
    private Uri convertHistory(Uri uri, NodeRevisionNumber number) {
        String sUri = String.valueOf(uri);
        
        if (isHistoryChild(sUri)) {
            if (!NodeRevisionNumber.HIDDEN_0_0.equals (number)) {
                sUri = sUri + "/" + String.valueOf (number);
            }
        }
        return uri.getNamespace().getUri(uri.getToken(), sUri);
    }
    
    private boolean isHistoryChild(String sUri) {
        XUri parent;
        parent = new XUri(sUri).getParent();
        return historyStores.contains(ns.lookupStoreByScope(parent));
    }
    
    /**
     ** Returns true if this store is in repair mode (i.e. read_only).
     ** @return     true if this store is in repair mode
     **/
    public boolean isInRepairMode() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "isInRepairMode" );
        
        boolean result = getParentStore().isInRepairMode();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "isInRepairMode", new Boolean(result) );
        return result;
    }
    
    /**
     ** Set repair mode for this store.
     ** @param     onOpenTA 0=WAIT, 1=ERROR, 2=ROLLBACK
     ** @param     waitTimeout the timeout for onOpenTA=0 (WAIT)
     ** @exception XException xdav error
     **/
    public void setRepairMode( int onOpenTA, long waitTimeout ) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setRepairMode", new Object[] {new Integer(onOpenTA), new Long(waitTimeout)}  );
        
        descriptorsHandler.setRepairMode( onOpenTA, waitTimeout );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setRepairMode" );
    }
    
    /**
     ** Release repair mode for this store.
     ** @exception XException xdav error
     **/
    public void releaseRepairMode() throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "releaseRepairMode" );
        
        descriptorsHandler.releaseRepairMode();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "releaseRepairMode" );
    }
    
    
    /**
     * getGuest returns the guest as URMPrincipal
     *
     * @param    parent              a  Service
     *
     * @return   an URMPrincipal
     *
     * @throws   ServiceAccessException
     *
     */
    private static URMPrincipal guest = null;
    static URMPrincipal getGuest(Service parent)
        throws ServiceAccessException {
        if ( guest != null )
            return guest;
        try {
            URMAuthenticator authenticator = URMAuthenticator.newInstance();
            guest = authenticator.authenticateUser();
            return guest;
        }
        catch (URMException e) {
            throw new ServiceAccessException(parent, e);
        }
    }
    
    
    //-------------------------------------------------------------
    // Resolve functionality
    // ------------------------------------------------------------
    
    /**
     * TLock the descriptors for the given uri and revision descriptor.
     * @param  uri the uri
     * @exception ServiceAccessException if the resource is tlocked by another thread and
     *            the tlock cannot be aquired after waiting for the period defined
     *            by waitForTLockTimeout (default: 3 sec).
     */
    public void tlockDescriptors( Uri uri ) throws ServiceAccessException {
        resolveWriteable( uri );
    }
    
    /**
     * Get descriptors object for the given uri.
     * @param  uri the uri
     * @return the tlocked descriptors object; null if not found
     * @exception XException if the resource is tlocked by another thread and
     *            the tlock cannot be aquired after waiting for the period defined
     *            by waitForTLockTimeout (default: 3 sec).
     */
    public IDescriptors resolve( Uri uri ) throws ServiceAccessException {
        if (isForceStoreEnlistment(uri)) {
            return doResolve( uri, XTLock.WRITE_LOCK, XTLock.READ_LOCK, false);
        } else {
            // we cannot be sure there's a transaction. Locks could get lost
            // if there's no commit
            return doResolve( uri, XTLock.NO_LOCK, XTLock.NO_LOCK, false);
        }
    }
    
    /**
     * Get descriptors object for the given uri and revision descriptor.
     * @param  uri the uri
     * @return the tlocked descriptors object; null if not found
     * @exception XException if the resource is tlocked by another thread and
     *            the tlock cannot be aquired after waiting for the period defined
     *            by waitForTLockTimeout (default: 3 sec).
     */
    public IDescriptors resolveWriteable( Uri uri ) throws ServiceAccessException {
        return doResolve( uri, XTLock.WRITE_LOCK, XTLock.READ_LOCK, false );
    }
    
    /**
     * @return never null
     */
    public IDescriptors resolveCreate( Uri uri ) throws ServiceAccessException {
        return doResolve(uri, XTLock.WRITE_LOCK, XTLock.READ_LOCK, true);
    }
    
    private IDescriptors doResolve( Uri uri, int lockType, int parentLockType, boolean create)
        throws ServiceAccessException {
        XResolver resolver;
        
        resolver = XResolver.create(getDescriptorsHandler(), uri.toString());
        try {
            return resolver.run(lockType, parentLockType, create);
        }
        catch (XException e) {
            throw serviceAccessException(e, uri);
        }
    }
}

