/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/XDbSession.java,v 1.5 2005/02/25 15:58:37 pnever Exp $
 * $Revision: 1.5 $
 * $Date: 2005/02/25 15:58:37 $
 *
 * ====================================================================
 *
 * 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.datastore;

import com.softwareag.tamino.db.api.accessor.*;
import java.util.*;
import org.apache.slide.store.tamino.common.*;

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 com.softwareag.tamino.db.api.common.TException;
import com.softwareag.tamino.db.api.io.TInputStream;
import com.softwareag.tamino.db.api.objectModel.TNonXMLObject;
import com.softwareag.tamino.db.api.objectModel.dom.TDOMObjectModel;
import com.softwareag.tamino.db.api.objectModel.jdom.TJDOMObjectModel;
import com.softwareag.tamino.db.api.response.TResponse;
import org.apache.slide.common.Domain;
import org.apache.slide.store.tamino.datastore.TaminoCodes;
import org.apache.slide.store.tamino.datastore.schema.XDoctype;
import org.apache.slide.store.tamino.jdomobjects.XDescriptors;
import org.apache.slide.store.tamino.store.ISlideAccessor;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;

/**
 * <p>Offers a kind of CRUD interface for IContent and IDescriptors objects,
 * as well as methods for transactional behaviour. </p><p>
 * This class hides the physical store. It is the
 * only one to talk to the physical store (Tamino). </p>
 *
 * @author martin.wallmer@softwareag.com
 *
 * @version $Revision: 1.5 $
 */
public class XDbSession implements IDbSession, XGlobals {
    
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    /** action create */
    protected static final int ACTION_CREATE = 1;
    /** action update */
    protected static final int ACTION_UPDATE = 2;
    /** action delete */
    protected static final int ACTION_DELETE = 3;
    
    /** the list of submonitorables */
    protected List subMonitorables = new ArrayList();
    
    /** tamino base i.e. http://bolrep2.software-ag.de/tamino */
    protected String taminoBase;
    
    /** tamino database */
    protected String database;
    
    /** collection for the content documents */
    protected String contentCollection;
    
    /** contains the TConnection */
    protected XConnection xConnection;
    
    /** indicates, if it is allowed to store nonXML data in this collection */
    protected boolean allowNonXML;
    
    /** indicates, if nixe schemas are to be used for nonXML content */
    protected boolean useNixe;
    
    /** the tamino version */
    private String taminoVersion = null;
    
    /** the first 2 digits of the Tamino Version */
    private float taminoMajorVersion;
    
    /** list of items to be deleted. */
    Set toBeDeleted = new HashSet();
    
    private int tryConnect = 5;
    private int tryConnectDelay = 2000;
    
    /** nonXMLContentAccessor */
    private TNonXMLObjectAccessor nonXMLContentAccessor;
    /** streamContentAccessor */
    private TStreamAccessor       streamContentAccessor;
    /** streamMetaAccessor */
    private TStreamAccessor       streamMetaAccessor;
    /** domContentAccessor */
    protected TXMLObjectAccessor    domContentAccessor;
    /** descriptorsAccessor */
    private XDescriptorsAccessor descriptorsAccessor;
    /** inoCollectionAccessor */
    protected TXMLObjectAccessor    inoCollectionAccessor;
    
    protected XUtilDBAccessor utilAccessor;
    
    /** contentLocation */
    protected TAccessLocation contentLocation;
    
    /** The backpointer to the DB handler */
    protected XDbHandler dbHandler = null;
    
    /** query builder */
    protected TQueryBuilder queryBuilder = TQueryBuilder.getInstance();
    
    private XContentAccessor contentAccessor;
    
    // get rid off as soon writing to stream is stable
    private boolean writeDescriptorsAsStream = true;
    
    /**
     * Method getInoId
     *
     * @param    resultStream        a  TInputStream
     *
     * @return   a  String
     */
    private String getInoId (TInputStream resultStream) {
        // should be checked, if hasFirstObjectId. If not, you need to parse
        return resultStream.getFirstObjectId();
    }
    
    //  /** requested schema language ( TSD2 / TSD3 ) default = TSD3 */
    //  hak: for later version ?  protected String schemaLanguageRequest = "";
    
    /**
     * Set the DB handler managing this session.
     *
     * @pre        dbHandler != null
     * @post
     *
     * @param      dbHandler the DB handler for this session
     */
    public void setDbHandler( XDbHandler dbHandler ) {
        this.dbHandler = dbHandler;
    }
    
    /**
     * Set the connection for this session, called by XDbHandler.
     *
     * @param      xCon the connection for this session
     */
    void setXConnection (XConnection xCon) {
        xConnection = xCon;
    }
    
    /**
     * get the connection for this session, called by XDbHandler.
     *
     * @return      the connection for this session
     */
    public XConnection getXConnection () {
        return xConnection;
    }
    
    /**
     * get the schema language version of the given collection
     *
     * @param      collection     name of the collection
     *
     * @return     true if schema nonXML
     *
     * @exception  XException error in data store
     *
     */
    public String getSchemaLanguage (String collection) throws XDatastoreException {
        return utilAccessor.getSchemaLanguage(collection);
    }
    
    
    public XDescriptorsAccessor getDescriptorsAccessor() {
        return descriptorsAccessor;
    }
    
    /**
     * Initializes a DBSession object.
     *
     * @pre        taminoBase != null;
     * @pre        database != null;
     *
     * @param      slide allows access to parameters defined in slide's Domain.xml
     *
     * @exception  XException if underlying service fails
     */
    public void setParameter (ISlideAccessor slide) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setParameter", new Object[] {slide} );
        
        if (xConnection == null)
            throw new IllegalStateException ("internal error");
        xConnection.ensureActive();
        
        allowNonXML = "true".equalsIgnoreCase (slide.getParameter ("allowNonXML")) ? true : false;
        useNixe     = "true".equalsIgnoreCase (slide.getParameter ("nixeEnabled")) ? true : false;
        
        taminoBase = slide.getParameter(TAMINO_BASE);
        database   = slide.getParameter(TAMINO_DATABASE);
        StringBuffer sb = new StringBuffer (taminoBase);
        if (!taminoBase.endsWith (XUri.SEP))
            sb.append ('/');
        sb.append (database);
        
        contentCollection = slide.getParameter(TAMINO_COLLECTION);
        contentLocation        = TAccessLocation.newInstance (contentCollection);
        nonXMLContentAccessor = xConnection.newNonXMLObjectAccessor (contentLocation);
        streamContentAccessor = xConnection.newStreamAccessor (contentCollection);
        streamMetaAccessor    = xConnection.newStreamAccessor (META_COLLECTION);
        
        domContentAccessor = xConnection.newXMLObjectAccessor
            (contentCollection, TDOMObjectModel.getInstance());
        
        utilAccessor = new XUtilDBAccessor(xConnection.getTConnection());
        descriptorsAccessor = XDescriptorsAccessor.create
            (xConnection, contentCollection,
             getSchemaLanguage (META_COLLECTION));
        
        inoCollectionAccessor = xConnection.
            newXMLObjectAccessor(INO_COLLECTION, TJDOMObjectModel.getInstance());
        
        contentAccessor = new XContentAccessor(contentCollection, nonXMLContentAccessor, streamContentAccessor);
        
        if (logger.isLoggable (Level.FINE)) logger.fine ("taminoBase:\t" + taminoBase);
        if (logger.isLoggable (Level.FINE)) logger.fine ("database:\t" + database);
        if (logger.isLoggable (Level.FINE)) logger.fine ("contentCollection:\t" + contentCollection);
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setParameter" );
    }
    
    /**
     * <p>Initialize this DB session. Checks, if the Schema version in Datastore
     * is compatible to this version.
     *
     * @exception  XException
     */
    public void initialize () throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "initialize");
        
        String version = descriptorsAccessor.getSchemaVersion();
        
        if (!version.equals (SCHEMA_VERSION)) {
            throw new XOldMetadataSchemaVersionException(
                "Old version "+version+" of metadata schema detected; "
                    +"migration to version "+SCHEMA_VERSION+" required. "
                    +"Please copy the document /administration/etc/StartRepairer.xml to the location "
                    +"/administration/repairer/<store-name> in order to start the migration process. "
                    +"Alternatively, in the Tamino WebDAV Server Console, please run the following command: "
                    +"inodavrepair -stores <store-name> -migrateOnly. "
                    +"If migration shall take place for all configured stores, use the -allStores parameter "
                    +"instead of -store <store-name>; if the Tamino WebDAV Server is running and requires "
                    +"authentication, use the -user <user> -password <password> parameters additionally. "
                    +"Restart Tamino WebDAV Server after migration has completed successfully."
            );
        }
        
        if (logger.isLoggable (Level.FINE))
            logger.exiting (CLASSNAME, "initialize");
    }
    
    
    /**
     * writes the content to the nonXML collection. It is checked,
     * if nonXML is allowed. After succesfull creation, the length
     * is written to the content object.
     *
     * @pre        (content != null)
     * @post
     *
     * @param      content   the content object to be made persistent
     *
     * @return     ino:id
     *
     * @exception  XException
     */
    protected String createNonXmlContent (IContent content) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "createNonXmlContent");
        
        if (!allowNonXML)
            throw new XForbiddenException ("Non-XML content not allowed");
        
        String result = null;
        TResponse response = null;
        String docType = content.getDocumentType();
        if( docType == null || "".equals(docType) )
            docType = "application/octet-stream";
        
        try {
            TNonXMLObject nonXmlObject = content.getContentWrapper().getNonXMLObject(docType);
            xConnection.startTransactionIfNecessary();
            response = nonXMLContentAccessor.insert (nonXmlObject);
            String code = response.getReturnValue();
            if (!"0".equals(code) && !TaminoCodes.isNonaborting(code)) {
                throw new XException("Tamino returned possibly aborting code: "+code);
            }
            result = nonXmlObject.getId();
        }
        catch (TException e) {
            // try again with default
            try {
                docType = "application/octet-stream";
                TNonXMLObject nonXmlObject = content.getContentWrapper().getNonXMLObject(docType);
                xConnection.startTransactionIfNecessary();
                response = nonXMLContentAccessor.insert (nonXmlObject);
                String code = response.getReturnValue();
                if (!"0".equals(code) && !TaminoCodes.isNonaborting(code)) {
                    throw new XException("Tamino returned possibly aborting code: "+code);
                }
                result = nonXmlObject.getId();
            }
            catch (TException x) {
                throw new XDatastoreException (x);
            }
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createNonXmlContent", result);
        
        return result;
    }
    
    /**
     * reads the content of a resource from database.
     *
     * @pre        contentId != null
     * @pre        !(contentId.equals (""))
     * @post       true
     *
     * @param      id   the content object to be made persistent
     *
     * @return     object representing the content
     *
     * @exception  XException
     */
    public IContent readContent (String id) throws XException {
        return contentAccessor.readContent(id);
    }
    
    
    /**
     * Updates a content object within the database. The new content is flushed
     * to the database, but not yet committed. It is taken into account, that
     * the doctype might change, change from XML to nonXML or vice versa.
     *
     * @pre        (contentId != null)
     * @pre        (!(contentId.equals ("")))
     * @pre        (content != null)
     * @post       true
     *
     * @param      content     the content object to be made persistent
     * @param      id          the contentId identifying the content object
     * @return     the content (possibly slightly modified by tamino)
     *
     * @exception  XException
     */
    public IContent updateContent (IContent content, String id) throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "updateContent", new Object[] {content, id, super.toString()});
        
        dbHandler.updateContentCounter().increment();
        Object timerId = dbHandler.updateContentTimer().start();
        IContent result = content;
        
        // check content
        String newSchema = content.getSchema();
        if (newSchema == null)
            newSchema = NONXML_CONTENT;
        
        XContentId contentId = new XContentId (id);
        String oldSchema = contentId.getSchema();
        
        // if it was XML content and doctype is still the same, do an updateXml (check for success)
        if (contentId.isXml() && newSchema.equals (oldSchema)) {
            if (!updateContent (content, contentId)) {
                deleteContent (id);
                result = createContent (content);
                if (!id.equals(EMPTY_CONTENT_ID) && id.equals(result.getContentId())) {
                    throw new XDatastoreException("Inconsistency detected: contentId="+id+" will be deleted and created by an update operation");
                }
            }
            else {
                content.setContentId (contentId.toString());
                if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                    System.out.println("   @@@ ["+Thread.currentThread().getName()+"] Tamino update: "+content.getContentId());
                }
                // hack to get the content length: read again
                result = setLength (content);
            }
        }
            // needs Tamino 3.1 or greater
        else if (taminoMajorVersion >= (float)3.1
                 && NONXML_CONTENT.equals (newSchema)
                 && NONXML_CONTENT.equals (oldSchema)
                 && content.getLength() > 0) {
            
            updateNonXmlContent (content, contentId);
            content.setContentId (contentId.toString());
            if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                System.out.println("   @@@ ["+Thread.currentThread().getName()+"] Tamino update: "+content.getContentId());
            }
            result = setLength (content);
            
        }
            
        else {
            deleteContent (id);
            result = createContent (content);
            if (!id.equals(EMPTY_CONTENT_ID) && id.equals(result.getContentId())) {
                throw new XDatastoreException("Inconsistency detected: contentId="+id+" will be deleted and created by an update operation");
            }
        }
        
        dbHandler.updateContentTimer().stop( timerId );
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "updateContent");
        return result;
    }
    
    /**
     * <p>writes the content of a resource to database. The content is flushed
     * to database, but not yet committed. The string representation of the
     * contentId is stored in the IContent object. </p>
     * <p>After createContent finished succesfully, the content object contains
     * the contentLength. In case of nonXML this is the size of the buffered
     * InputStream, in case of XML we have to read again and look, what Tamino
     * made out of it.</p>
     *
     * @pre        content != null
     * @pre        content.getWrapper () != null
     * @post
     *
     * @param      content   the content object to be made persistent
     * @return     the content (possibly slightly modified by tamino)
     *
     * @exception  XException
     */
    public IContent createContent (IContent content) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "createContent", new Object [] {content, super.toString()});
        
        boolean isXml = true;
        
        dbHandler.createContentCounter().increment();
        Object timerId = dbHandler.createContentTimer().start();
        IContent result = content;
        
        // XContentId contentId = null;
        String     id = null;
        String     docType = null;
        //        schema = content.getSchema();
        XContentWrapper contentWrapper = content.getContentWrapper();
        
        if (contentWrapper.isEmptyContent()) {
            content.setContentId (new XContentId ().toString());
            if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                System.out.println("   @@@ ["+Thread.currentThread().getName()+"] Tamino create: "+content.getContentId());
            }
            content.setLength (0);
        }
        else {
            try {
                xConnection.startTransactionIfNecessary();
                
                if (contentWrapper.isXml()) {
                    XDoctype weakDoctype = contentWrapper.getWeakDoctype();
                    
                    // are we allowed to store this doctype?
                    // ask dbHandler to use the adminDb connection
                    XDoctype strongDoctype = dbHandler.getDoctype (weakDoctype);
                    if (strongDoctype != null) {
                        
                        docType = strongDoctype.getPrefixedName ();
                        try {
                            
                            TNonXMLObject xmlObject = contentWrapper.getXMLObject();
                            TInputStream resultStream = streamContentAccessor.insert (xmlObject);
                            //nonXMLContentAccessor.insert (xmlObject);
                            
                            XProcessResultParser resultParser = new XProcessResultParser();
                            resultParser.parse (resultStream);
                            
                            String code = resultParser.getResponse();
                            if (!"0".equals(code) && !TaminoCodes.isNonaborting(code)) {
                                throw new XException("Tamino returned possibly aborting code: "+code);
                            }
                            
                            if (!"0".equals (code)) {
                                if (allowNonXML) {
                                    docType = NONXML_CONTENT;
                                    id = createNonXmlContent (content);
                                    isXml = false;
                                }
                                else {
                                    throw new XForbiddenException(
                                        "Storing content as XML failed with Tamino error " +
                                            code + " and non-XML content not allowed" );
                                }
                            }
                            else {
                                id = (String)resultParser.getInoIdList().get(0);
                            }
                        }
                        
                        /*
                         exception could be thrown because:
                         invalid xml (thrown by API as IOException, because
                         we have an underlying writer)
                         valid xml, but tamino complains
                         any kind of communication or database problems
                         */
                        catch (TInsertException e) {
                            if (allowNonXML) {
                                e.printStackTrace();
                                docType = NONXML_CONTENT;
                                id = createNonXmlContent (content);
                                isXml = false;
                            }
                            else {
                                throw new XForbiddenException (e.getCause().getMessage());
                            }
                        }
                    }
                        
                    else {
                        // it is valid XML, but this schema is not allowed.
                        // try to write as nonXML
                        if (allowNonXML) {
                            docType = NONXML_CONTENT;
                            id = createNonXmlContent (content);
                            isXml = false;
                        }
                        else
                            throw new XForbiddenException(
                                "XML Schema "+docType+" not found and auto-creation of XML schemas disabled and "+
                                    "non-XML content not allowed" );
                    }
                }
                else {
                    // it is nonXML data, try to write as nonXML
                    if (allowNonXML) {
                        docType = NONXML_CONTENT;
                        id = createNonXmlContent (content);
                        isXml = false;
                    }
                    else
                        throw new XForbiddenException(
                            "Non-XML content not allowed" );
                    
                }
            }
            catch (Exception e) {
                if (e instanceof XDatastoreException)
                    throw (XDatastoreException)e.fillInStackTrace();
                else if (e instanceof XForbiddenException)
                    throw (XForbiddenException)e;
                else
                    throw new XDatastoreException (e);
            }
            
            
            content.setContentId (new XContentId (id, docType).toString());
            if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                System.out.println("   @@@ ["+Thread.currentThread().getName()+"] Tamino create: "+content.getContentId());
            }
            result = setLength(content);
        }
        
        dbHandler.createContentTimer().stop( timerId );
        result.setIsXml (isXml);
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createContent");
        return result;
    }
    
    /**
     * updates an xml content.
     *
     * @pre        (descriptors != null)
     * @post
     *
     * @param      content   the content object
     * @param      contentId the contentId identifying the content object
     *
     * @return     true, if succeeded. If false, caller should try delete and create content
     *
     * @exception  XException
     *
     */
    protected boolean updateContent (IContent content, XContentId contentId) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "updateXmlContent");
        boolean succeeded = true;
        String inoIdReturnedFromTamino = null;
        TResponse response = null;
        try {
            
            TNonXMLObject nonXmlObject = content.getContentWrapper().getNonXMLObject(content.getSchema(), content.getDocumentType());
            
            nonXmlObject.setId (contentId.getId());
            
            xConnection.startTransactionIfNecessary();
            
            response = nonXMLContentAccessor.update (nonXmlObject);
            String code = response.getReturnValue();
            if (!"0".equals(code) && !TaminoCodes.isNonaborting(code)) {
                throw new XException("Tamino returned possibly aborting code: "+code);
            }
            
            inoIdReturnedFromTamino = nonXmlObject.getId();
            
            if (!inoIdReturnedFromTamino.equals (contentId.getId()))
                succeeded = false;
            
        }
        catch (TException e) {
            if (logger.isLoggable (Level.WARNING)) e.printStackTrace();
            succeeded = false;
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "updateXmlContent", new Boolean (succeeded));
        
        return succeeded;
    }
    
    /**
     * updates a nonXML content.
     *
     * @pre        (descriptors != null)
     * @post
     *
     * @param      content    the content object
     * @param      contentId  the contentId identifying the content object
     *
     * @exception  XException
     */
    protected void updateNonXmlContent (IContent content, XContentId contentId) throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "updateNonXmlContent", new Object[] {content, contentId});
        
        if (!allowNonXML)
            throw new XForbiddenException ("Non-XML not allowed");
        
        String result = null;
        TResponse response = null;
        String docType = content.getDocumentType();
        if( docType == null || "".equals(docType) )
            docType = "application/octet-stream";
        
        try {
            TNonXMLObject nonXmlObject = content.getContentWrapper().getNonXMLObject(docType);
            xConnection.startTransactionIfNecessary();
            nonXmlObject.setId (contentId.getId());
            response = nonXMLContentAccessor.update (nonXmlObject);
            String code = response.getReturnValue();
            if (!"0".equals(code) && !TaminoCodes.isNonaborting(code)) {
                throw new XException("Tamino returned possibly aborting code: "+code);
            }
            result = nonXmlObject.getId();
        }
        catch (TException e) {
            try {
                docType = "application/octet-stream";
                TNonXMLObject nonXmlObject = content.getContentWrapper().getNonXMLObject(docType);
                xConnection.startTransactionIfNecessary();
                nonXmlObject.setId (contentId.getId());
                response = nonXMLContentAccessor.update (nonXmlObject);
                String code = response.getReturnValue();
                if (!"0".equals(code) && !TaminoCodes.isNonaborting(code)) {
                    throw new XException("Tamino returned possibly aborting code: "+code);
                }
                result = nonXmlObject.getId();
            }
            catch (TException x) {
                throw new XDatastoreException (x);
            }
        }
        
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "updateNonXmlContent");
    }
    
    /**
     * deletes the content objects specified by id,
     * not yet committed. This is called in the prepare step of the two phase commit.
     *
     * @pre        (contentId != null)
     * @pre        (!(contentId.equals ("")))
     * @post       true
     *
     * @param      id the id representing the objectto be deleted
     *
     * @exception  XException
     */
    public void deleteContent (String id) throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "deleteContent", new Object[] {id, super.toString()});
        
        toBeDeleted.add (id);
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "deleteContent");
    }
    
    /**
     * delete all content objects marked for deletion. Called in the prepare
     * phase of the two phase commit
     *
     * @exception  XException delete if no Exception is thrown
     *
     */
    public void prepare () throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "prepare", new Object [] {super.toString()});
        
        Iterator it = toBeDeleted.iterator();
        try {
            while (it.hasNext()) {
                String id = (String)it.next();
                
                XContentId contentId = new XContentId (id);
                
                if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                    System.out.println("   @@@ ["+Thread.currentThread().getName()+"] Tamino delete: "+id);
                }
                
                if (!contentId.isEmptyContent()) {
                    dbHandler.deleteContentCounter().increment();
                    Object timerId = dbHandler.deleteContentTimer().start();
                    
                    if (contentId.isXml())
                        deleteXMLContent (contentId);
                    else
                        deleteNonXMLContent(contentId);
                    
                    dbHandler.deleteContentTimer().stop( timerId );
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
            throw new XDatastoreException (e);
        }
        finally {
            toBeDeleted.clear ();
        }
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "prepare");
    }
    
    /**
     * deletes a content object from the contentCollection.
     *
     * @pre        (contentId != null)
     * @pre        (contentCollection != null)
     * @post       true
     *
     * @param      contentId id representing object to be deleted
     *
     * @exception  XException wrapping the underlying exception
     */
    protected void deleteXMLContent (XContentId contentId) throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "deleteXMLContent", new Object[] {contentId});
        
        deleteXML (nonXMLContentAccessor, contentId.getSchema(), contentId.getId());
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "deleteXMLContent");
    }
    
    /**
     * Deletes any kind of XML data.
     *
     * @pre        true
     * @post
     *
     * @param      accessor     the Tamino accessor to be used
     * @param      schema       the doctype of the XML doc to be deleted
     * @param      inoId        the ino:id of the XML doc to be deleted
     *
     * @exception  XException
     */
    public void deleteXML (TAccessor accessor, String schema, String inoId) throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "deleteXML", new Object[] {schema, inoId});
        
        TQuery xQuery = queryBuilder.buildFromId (
            schema,
            inoId
        );
        
        TResponse response = null;
        
        try {
            xConnection.startTransactionIfNecessary();
            
            if (accessor instanceof TNonXMLObjectAccessor) {
                response = ((TNonXMLObjectAccessor)accessor).delete (xQuery);
            }
            else {
                response = ((TXMLObjectAccessor)accessor).delete (xQuery);
            }
            String code = response.getReturnValue();
            if (!"0".equals(code) && !TaminoCodes.isNonaborting(code)) {
                throw new XException("Tamino returned possibly aborting code: "+code);
            }
        }
        catch (TException e) {
            throw new XDatastoreException ("xpath: " + xQuery, e);
        }
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "deleteXML");
        
    }
    
    /**
     * delete content from the nonXML collection.
     *
     * @pre        (contentId != null)
     * @post
     *
     * @param      contentId that identifies the object to be deleted
     *
     * @exception  XException wrapping the underlying exception
     */
    protected void deleteNonXMLContent (XContentId contentId) throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "deleteNonXMLContent", new Object[] {contentId});
        
        deleteXML (nonXMLContentAccessor, contentId.getSchema(), contentId.getId());
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "deleteNonXMLContent");
    }
    
    /**
     * commits a database transaction
     *
     * @param onePhase indicate if one or two phase commit
     *
     * @exception  XException
     */
    public void commit (boolean onePhase) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "commit " + contentCollection, new Object [] {super.toString()});
        
        dbHandler.commitCounter().increment();
        Object timerId = dbHandler.commitTimer().start();
        
        if (onePhase) {
            try {
                prepare();
            }
            catch (Exception e) {
                try {
                    xConnection.rollback();
                }
                catch (TException t) {
                    throw new XDatastoreException (t);
                }
                throw new XDatastoreException (e);
            }
        }
        
        try {
            xConnection.commit();
        }
        
        catch (TException e) {
            throw new XDatastoreException (e);
        }
        
        dbHandler.commitTimer().stop( timerId );
        
        if (logger.isLoggable (Level.FINE))
            logger.exiting (CLASSNAME, "commit");
    }
    
    /**
     * commits a database transaction. One phase commit is assumed.
     *
     * @exception  XException
     */
    public void commit () throws XException {
        commit (true);
    }
    
    
    
    /**
     * aborts a database transaction
     *
     * @exception  XException
     */
    public void rollback () throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "rollback", new Object [] {super.toString()});
        
        dbHandler.rollbackCounter().increment();
        Object timerId = dbHandler.rollbackTimer().start();
        
        try {
            xConnection.rollback();
        }
        
        catch (TException e) {
            e.printStackTrace();
            throw new XDatastoreException (e);
        }
        
        dbHandler.rollbackTimer().stop( timerId );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "rollback");
    }
    
    /**
     * reads a descriptors object from database
     *
     * @pre        (uuri != null)
     * @post
     *
     * @param      uuri   the uuri identifying this resource
     *
     * @return     the IDescriptors object represented with the specified uuri, null if not found
     * @exception  XException
     *
     */
    public IDescriptors readDescriptor (String uuri) throws XException {
        XDescriptors desc = descriptorsAccessor.readDescriptorsByUuri(uuri);
        if (desc != null) {
            desc.validate();
        }
        return desc;
    }
    
    /**
     * Processes a collection of descriptors objects. The action on each element
     * (delete, update, create) depends on the object's state.
     *
     * @pre        (descriptors != null)
     * @post
     *
     * @param      descriptors   a collection of IDescriptorsDocument objects
     *
     * @exception  XException
     *
     * @see IDescriptors
     */
    public void processDescriptors (Collection descriptors) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "processDescriptors", new Object [] {super.toString()});
        
        XDescriptorsProcessor descProc;
        
        // for testing: make use of streaming switchable
        if (writeDescriptorsAsStream)
            descProc = new XDescriptorsProcessor (contentCollection);
        else
            descProc = new XDescriptorsProcessor (descriptorsAccessor);
        
        Iterator it = descriptors.iterator();
        try {
            xConnection.startTransactionIfNecessary();
        } catch (TException e) {
            throw new XDatastoreException(e);
        }
        
        while (it.hasNext()) {
            XDescriptors desc = (XDescriptors)it.next();
            if (logger.isLoggable (Level.FINE)) logger.fine ("process document\n" + desc.toString());
            
            desc.validate();
            
            int state = desc.getState();
            Object timerId = null;
            
            if (state == IDesc.CREATED || state == IDesc.CHANGED || state == IDesc.DELETED) {
                if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                    System.out.println("   $$$ ["+Thread.currentThread().getName()+"] Tamino process desc: "+desc.details());
                }
            }
            
            // System.out.println(desc.toString());
            switch (state) {
                case IDesc.CREATED:
                    if (logger.isLoggable (Level.FINE)) logger.fine ("CREATE " + desc.getUuri());
                    dbHandler.createDescriptorsCounter().increment();
                    timerId = dbHandler.createDescriptorsTimer().start();
                    
                    descProc.addInsert(desc);
                    // descriptorsAccessor.insert (desc);
                    
                    dbHandler.createDescriptorsTimer().stop( timerId );
                    break;
                    
                case IDesc.CHANGED:
                    if (logger.isLoggable (Level.FINE)) logger.fine ("CHANGE " + desc.getUuri());
                    dbHandler.updateDescriptorsCounter().increment();
                    timerId = dbHandler.updateDescriptorsTimer().start();
                    descProc.addUpdate (desc);
                    //descriptorsAccessor.update (desc);
                    
                    dbHandler.updateDescriptorsTimer().stop( timerId );
                    break;
                    
                case IDesc.DELETED:
                    if (logger.isLoggable (Level.FINE)) logger.fine ("DELETE " + desc.getUuri());
                    dbHandler.deleteDescriptorsCounter().increment();
                    timerId = dbHandler.deleteDescriptorsTimer().start();
                    
                    descProc.addDelete(desc);
                    //descriptorsAccessor.delete (desc);
                    
                    dbHandler.deleteDescriptorsTimer().stop( timerId );
                    break;
                    
                    // ignore
                case IDesc.CREATED_DELETED:
                case IDesc.UNCHANGED:
                    if (logger.isLoggable (Level.FINE)) logger.fine ("IGNORE " + desc.getUuri());
                    break;
                    
                case IDesc.NO_STATE:
                    throw new IllegalStateException ();
                    
                default:
                    throw new IllegalStateException ();
            }
        }
        
        descProc.process(streamMetaAccessor);
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "processDescriptors");
    }
    
    /**
     * Set the repair mode
     * @param  repairMode true/false
     */
    public void setRepairMode( boolean repairMode ) {
        contentAccessor.setRepairMode(repairMode);
    }
    
    /**
     * String representation of this DbSession.
     * <pre>
     * Format: <taminoBase>/<database>[<collection>]
     * </pre>
     *
     * @return     a string representing this DbSession.
     */
    public String toString () {
        StringBuffer sb = new StringBuffer(super.toString());
        sb.append('/');
        sb.append (taminoBase);
        sb.append('/');
        sb.append(database);
        sb.append ('[').append (contentCollection).append(']');
        return sb.toString();
    }
    
    /**
     * Creates a Tamino query to access a Descriptors object specified by uuri
     *
     * @pre        (uuri != null)
     * @post
     *
     * @param      the uuri identifying the ressource
     *
     * @return     the string representing the query
     */
    private String qDescByUri (String uuri) {
        StringBuffer sb = new StringBuffer (contentCollection);
        sb.append ("[@uuri=\"").append (uuri).append("\"]");
        return sb.toString();
    }
    
    private IContent setLength (IContent content) throws XException {
        // hack to get the content length: read again
        XContentId id = new XContentId (content.getContentId());
        
        if (id.isXml()) {
            IContent tmpContent = readContent (id.toString ());
            content.setLength (tmpContent.getLength());
            return tmpContent;
        }
        else {
            content.setLength (content.getContentWrapper().getContentLength());
            return content;
        }
        
    }
    
    private void setTaminoVersion() {
        try { taminoVersion = xConnection.getServerVersion(); }
        catch (TAccessorException e) {taminoVersion = "2.3.1.4"; }
        
        StringTokenizer st = new StringTokenizer (taminoVersion, ".");
        StringBuffer sb = new StringBuffer();
        sb.append (st.nextToken()).append ('.').append (st.nextToken());
        taminoMajorVersion = Float.parseFloat (sb.toString ());
    }
    
    /**
     * method description
     *
     * @return     the tamino major version as float
     */
    public float getTaminoMajorVersion() {
        if (taminoVersion == null)
            setTaminoVersion();
        return taminoMajorVersion;
    }
    
    /**
     * Return the doctype names in the specified collection. If the schema language is TSD2
     * the result of getDoctypeNames() and getSchemaNames() is the same.
     *
     * @param    taminoCollection    the collection
     *
     * @return   a set of doctype names
     *
     * @throws   XException
     *
     */
    public Set getDoctypeNames (String taminoCollection) throws XException {
        return utilAccessor.getDoctypeNames(taminoCollection);
    }
    
    /**
     * Return the schema names in the specified collection. If the schema language is TSD2
     * the result of getDoctypeNames() and getSchemaNames() is the same.
     *
     * @param    taminoCollection    the collection
     *
     * @return   a set of schema names
     *
     * @throws   XException
     *
     */
    public Set getSchemaNames (String taminoCollection) throws XException {
        return utilAccessor.getSchemaNames (taminoCollection);
        
    }
}



