/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/XDbHandler.java,v 1.4 2005/01/19 15:19:01 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2005/01/19 15:19:01 $
 *
 * ====================================================================
 *
 * 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.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.TPreference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.schema.XDoctype;
import org.apache.slide.store.tamino.datastore.schema.XDoctypeHandler;
import org.apache.slide.store.tamino.datastore.schema.XKnownPropertyHelper;
import org.apache.slide.store.tamino.store.ISlideAccessor;
import org.apache.slide.store.tamino.store.monitoring.IMonitor;
import org.apache.slide.store.tamino.store.monitoring.IMonitorable;
import org.apache.slide.store.tamino.store.monitoring.Monitor;
import org.apache.slide.store.tamino.store.monitoring.MonitoredCounter;
import org.apache.slide.store.tamino.store.monitoring.MonitoredTimer;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;
import org.jdom.Namespace;

/**
 * <p>Acts as a container for IDbSessions. One transaction is always
 * performed in one thread within Slide. XDbHandler guarantees, that
 * during runtime of one transaction the participating IDbSessions are
 * bound to the transaction's thread.</p>
 * <p>There is one XDbHandler for one parent store (scope).</p>
 * <p>XDbHandler maintains a list of all webdav enabled collections
 * and a list for all available schemas of its parent store's collection. </p>
 * <p> An instance of XDbHandler has one instance of XUtilDbSession (adminDbSession)
 * </p>
 * <p> Maintains the connection pool </p>
 *
 * @author    martin.wallmer@softwareag.com
 *
 * @version   $Revision: 1.4 $
 */
public class XDbHandler implements IMonitorable, XGlobals {

    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);

    private static final String DBSESSION = XDbSession.class.getName();
    private static final String UTILDBSESSION = XUtilDbSession.class.getName();

    /** holds a bunch of  xConnections */
    private XConnectionPool connectionPool;


    /**
     * holds the active DbSessions.
     * The key for the map is the thread object. Must be synchronized
     */
    private Map sessions = Collections.synchronizedMap (new HashMap());

    /** adminDbSession **/
    private XUtilDbSession adminDbSession = null;

    /** The slide accessor */
    ISlideAccessor parentStore;

    // all available schemas within the content collection */
    //private static Set contentDoctypes = null;

    private boolean autoCreateXmlSchema;

    /** the content collection with URI, schemaversion info and collection name */
    private IContentCollection contentCollection;

    // monitoring...
    /** The name */
    protected String monName= null;
    /** the parent */
    protected IMonitorable monParent = null;
    /** The list of child monitorables */
    protected List monChildren = new ArrayList();
    /** The monitor */
    protected Monitor monitor = null;

    // monitoring counters and timers...
    /** createContentCounter **/
    protected MonitoredCounter createContentCounter = null;
    /** readContentCounter **/
    protected MonitoredCounter readContentCounter = null;
    /** updateContentCounter **/
    protected MonitoredCounter updateContentCounter = null;
    /** deleteContentCounter **/
    protected MonitoredCounter deleteContentCounter = null;
    /** createContentTimer **/
    protected MonitoredTimer createContentTimer = null;
    /** readContentTimer **/
    protected MonitoredTimer readContentTimer = null;
    /** updateContentTimer **/
    protected MonitoredTimer updateContentTimer = null;
    /** deleteContentTimer **/
    protected MonitoredTimer deleteContentTimer = null;
    /** createDescriptorsCounter **/
    protected MonitoredCounter createDescriptorsCounter = null;
    /** readDescriptorsCounter **/
    protected MonitoredCounter readDescriptorsCounter = null;
    /** updateDescriptorsCounter **/
    protected MonitoredCounter updateDescriptorsCounter = null;
    /** deleteDescriptorsCounter **/
    protected MonitoredCounter deleteDescriptorsCounter = null;
    /** createDescriptorsTimer **/
    protected MonitoredTimer createDescriptorsTimer = null;
    /** readDescriptorsTimer **/
    protected MonitoredTimer readDescriptorsTimer = null;
    /** updateDescriptorsTimer **/
    protected MonitoredTimer updateDescriptorsTimer = null;
    /** deleteDescriptorsTimer **/
    protected MonitoredTimer deleteDescriptorsTimer = null;
    /** commitCounter **/
    protected MonitoredCounter commitCounter = null;
    /** rollbackCounter **/
    protected MonitoredCounter rollbackCounter = null;
    /** commitTimer **/
    protected MonitoredTimer commitTimer = null;
    /** rollbackTimer **/
    protected MonitoredTimer rollbackTimer = null;

    // literals for the monitoring items
    private final static String SESSIONS                    = "Concurrent sessions [#]";
    private final static String TAMINO_URL                  = "Tamino URL";
    private final static String CREATE_CONTENT_COUNTER      = "Create content [#]";
    private final static String READ_CONTENT_COUNTER        = "Read content [#]";
    private final static String UPDATE_CONTENT_COUNTER      = "Update content [#]";
    private final static String DELETE_CONTENT_COUNTER      = "Delete content [#]";
    private final static String CREATE_CONTENT_TIMER        = "Create content [ms]";
    private final static String READ_CONTENT_TIMER          = "Read content [ms]";
    private final static String UPDATE_CONTENT_TIMER        = "Update content [ms]";
    private final static String DELETE_CONTENT_TIMER        = "Delete content [ms]";
    private final static String CREATE_DESCRIPTORS_COUNTER  = "Create descriptors [#]";
    private final static String READ_DESCRIPTORS_COUNTER    = "Read descriptors [#]";
    private final static String UPDATE_DESCRIPTORS_COUNTER  = "Update descriptors [#]";
    private final static String DELETE_DESCRIPTORS_COUNTER  = "Delete descriptors [#]";
    private final static String CREATE_DESCRIPTORS_TIMER    = "Create descriptors [ms]";
    private final static String READ_DESCRIPTORS_TIMER      = "Read descriptors [ms]";
    private final static String UPDATE_DESCRIPTORS_TIMER    = "Update descriptors [ms]";
    private final static String DELETE_DESCRIPTORS_TIMER    = "Delete descriptors [ms]";
    private final static String COMMIT_COUNTER              = "Commit [#]";
    private final static String ROLLBACK_COUNTER            = "Rollback [#]";
    private final static String COMMIT_TIMER                = "Commit [ms]";
    private final static String ROLLBACK_TIMER              = "Rollback [ms]";


    /** createContentCounter. */
    MonitoredCounter createContentCounter () {return createContentCounter;}
    /** readContentCounter. */
    MonitoredCounter readContentCounter   () {return readContentCounter;}
    /** updateContentCounter. */
    MonitoredCounter updateContentCounter () {return updateContentCounter;}
    /** deleteContentCounter. */
    MonitoredCounter deleteContentCounter () {return deleteContentCounter;}
    /** createContentTimer. */
    MonitoredTimer createContentTimer     () {return createContentTimer;}
    /** readContentTimer. */
    MonitoredTimer readContentTimer       () {return readContentTimer;}
    /** createContentCounter. */
    MonitoredTimer updateContentTimer     () {return updateContentTimer;}
    /** deleteContentTimer. */
    MonitoredTimer deleteContentTimer     () {return deleteContentTimer;}
    /** createDescriptorsCounter. */
    MonitoredCounter createDescriptorsCounter () {return createDescriptorsCounter;}
    /** readDescriptorsCounter. */
    MonitoredCounter readDescriptorsCounter   () {return readDescriptorsCounter;}
    /** updateDescriptorsCounter. */
    MonitoredCounter updateDescriptorsCounter () {return updateDescriptorsCounter;}
    /** deleteDescriptorsCounter. */
    MonitoredCounter deleteDescriptorsCounter () {return deleteDescriptorsCounter;}
    /** createDescriptorsTimer. */
    MonitoredTimer createDescriptorsTimer     () {return createDescriptorsTimer;}
    /** readDescriptorsTimer. */
    MonitoredTimer readDescriptorsTimer       () {return readDescriptorsTimer;}
    /** updateDescriptorsTimer. */
    MonitoredTimer updateDescriptorsTimer     () {return updateDescriptorsTimer;}
    /** deleteDescriptorsTimer. */
    MonitoredTimer deleteDescriptorsTimer     () {return deleteDescriptorsTimer;}
    /** commitCounter. */
    MonitoredCounter commitCounter            () {return commitCounter;}
    /** rollbackCounter. */
    MonitoredCounter rollbackCounter          () {return rollbackCounter;}
    /** commitTimer. */
    MonitoredTimer commitTimer                () {return commitTimer;}
    /** rollbackTimer. */
    MonitoredTimer rollbackTimer              () {return rollbackTimer;}


    /**
     * Creates a DbSession and binds it to the current thread
     *
     * @param      dbSessionClass the kind of store (UTILDBSESSION or DBSESSION)
     *
     * @exception  XException
     */
    private IDbSession createDbSession  (String dbSessionClass)
        throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "createDbSession", new Object[] {dbSessionClass});

        IDbSession dbSession = null;

        // Was ist wenn schon eine session da ist - verdr�ngen oder exitierende returnen??
        if( sessions.containsKey(Thread.currentThread()) ) {
            // if session already exists, return the existing???
            // dbSession = (IDbSession)sessions.get (Thread.currentThread());
            throw new IllegalStateException( "DB Session already exists" );
        }
        else {
            try {
                XConnectionKey conKey = new XConnectionKey (parentStore);
                Class sessionClass = Class.forName (dbSessionClass);
                dbSession = (IDbSession) sessionClass.newInstance();
                dbSession.setDbHandler (this);
                ((XDbSession)dbSession).setXConnection
                    (connectionPool.getXConnection (conKey));

                dbSession.setParameter (parentStore);
                sessions.put (Thread.currentThread(), dbSession );
            }
            catch (Exception e) {
                throw new XException (e);
            }
        }
        if (logger.isLoggable (Level.FINE))
            logger.exiting (CLASSNAME, "createDbSession", dbSession);

        return dbSession;
    }

    /**
     * creates the adminDbSession
     *
     * @pre        (slide != null && adminDbSession == null)
     * @post
     *
     * @return     remove if void
     *
     * @exception  Exception delete if no Exception is thrown
     *
     * @see #
     */
    private XUtilDbSession createAdminDbSession () throws XException {
        XUtilDbSession dbSession;
        try {
            String privateKey = "admin";

            XConnectionKey conKey = new XConnectionKey (parentStore, privateKey);

            Class sessionClass = Class.forName (UTILDBSESSION);
            dbSession = (XUtilDbSession) sessionClass.newInstance();
            ((XDbSession)dbSession).setXConnection
                (connectionPool.getXConnection (conKey));

            dbSession.setDbHandler(this);
            dbSession.setParameter (parentStore);
        }
        catch (Exception e) {
            throw new XException (e);
        }
        return dbSession;
    }


    /**
     * Constructs an XDBHandler specific to parentStore.
     * checks, if the contentCollection is webdav enabled.
     * If not, currently an XException is thrown (in future:
     * create if not available).
     *
     * @param      taminoBase  the http url of Tamino as specified in domain.xml
     * @param      taminoDatabase  the db name of Tamino as specified in domain.xml
     * @param      taminoCollection  the collection name of Tamino as specified in domain.xml
     * @pre        taminoBase != null
     * @pre        taminoDatabase != null
     * @pre        taminoCollection != null
     **
     */
    public XDbHandler( String namespaceName, String taminoBase, String taminoDatabase, String taminoCollection ) {
        this(new TaminoParameters(namespaceName, taminoBase, taminoDatabase, taminoCollection));
    }


    /**
     * Factory method to construct an XDBHandler specific to parentStore.
     * checks, if the contentCollection is webdav enabled.
     * If not, currently an XException is thrown (in future:
     * create if not available).
     *
     * @pre        taminoBase != null
     * @pre        taminoDatabase != null
     * @pre        taminoCollection != null
     *
     * @param      taminoBase  the http url of Tamino as specified in domain.xml
     * @param      taminoDatabase  the db name of Tamino as specified in domain.xml
     * @param      taminoCollection  the collection name of Tamino as specified in domain.xml
     * @param      dbSessionClass  dbSessionClass
     * @param      allowNonXML  allowNonXML true/false
     * @param      autoCreateXmlSchema  autoCreateXmlSchema true/false
     * @return     a XDbHandler
     **
     */
    public static XDbHandler newXDbHandler (String namespaceName, String taminoBase,
                                            String taminoDatabase,
                                            String taminoCollection,
                                            String dbSessionClass,
                                            boolean allowNonXML,
                                            boolean autoCreateXmlSchema) {
        TaminoParameters tp = new TaminoParameters(namespaceName, taminoBase, taminoDatabase, taminoCollection);
        tp.setParameter("dbSessionClass", dbSessionClass);
        tp.setParameter("allowNonXML", new Boolean (allowNonXML).toString());
        tp.setParameter("autoCreateXmlSchema", new Boolean (autoCreateXmlSchema).toString());
        XDbHandler result = new XDbHandler (tp);
        result.initialize();
        return result;
    }



    /**
     * Constructs an XDBHandler specific to parentStore.
     * checks, if the contentCollection is webdav enabled.
     * If not, currently an XException is thrown (in future:
     * create if not available)
     *
     * @param      parentStore   the Slide accessor (XParentStore)
     */
    public XDbHandler( ISlideAccessor parentStore ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>", new Object[] {parentStore} );

        this.parentStore = parentStore;

        TPreference.getInstance().setGetServerVersion( false );
        TPreference.getInstance().setCheckServerAvailability( false );

        TPreference.getInstance().setUseApacheLoadExternalDTD( false );

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }

    /**
     * Initializes this DB handler.
     */
    public void initialize() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "initialize" );

        connectionPool = XConnectionPool.getInstance();

        // monitoring variables
        this.monName = "DatabaseHandler";
        if (parentStore instanceof IMonitorable)
            this.monParent = (IMonitorable) parentStore;
        else
            this.monParent = null;

        // this.monChildren.add(...); no children
        this.monitor = Monitor.getMonitor( this ); // this comes last!!

        // monitoring items
        monitor.registerProperty (TAMINO_URL);
        monitor.registerProperty (SESSIONS);

        String sOpenContent = parentStore.getParameter ("autoCreateXmlSchema");
        autoCreateXmlSchema = sOpenContent == null ? true : sOpenContent.equalsIgnoreCase ("true");

        createContentCounter     = monitor.getCounter (CREATE_CONTENT_COUNTER);
        createContentTimer       = monitor.getTimer   (CREATE_CONTENT_TIMER);
        readContentCounter       = monitor.getCounter (READ_CONTENT_COUNTER);
        readContentTimer         = monitor.getTimer   (READ_CONTENT_TIMER);
        updateContentCounter     = monitor.getCounter (UPDATE_CONTENT_COUNTER);
        updateContentTimer       = monitor.getTimer   (UPDATE_CONTENT_TIMER);
        deleteContentCounter     = monitor.getCounter (DELETE_CONTENT_COUNTER);
        deleteContentTimer       = monitor.getTimer   (DELETE_CONTENT_TIMER);
        createDescriptorsCounter = monitor.getCounter (CREATE_DESCRIPTORS_COUNTER);
        createDescriptorsTimer   = monitor.getTimer   (CREATE_DESCRIPTORS_TIMER);
        readDescriptorsCounter   = monitor.getCounter (READ_DESCRIPTORS_COUNTER);
        readDescriptorsTimer     = monitor.getTimer   (READ_DESCRIPTORS_TIMER);
        updateDescriptorsCounter = monitor.getCounter (UPDATE_DESCRIPTORS_COUNTER);
        updateDescriptorsTimer   = monitor.getTimer   (UPDATE_DESCRIPTORS_TIMER);
        deleteDescriptorsCounter = monitor.getCounter (DELETE_DESCRIPTORS_COUNTER);
        deleteDescriptorsTimer   = monitor.getTimer   (DELETE_DESCRIPTORS_TIMER);
        commitCounter            = monitor.getCounter (COMMIT_COUNTER);
        commitTimer              = monitor.getTimer   (COMMIT_TIMER);
        rollbackCounter          = monitor.getCounter (ROLLBACK_COUNTER);
        rollbackTimer            = monitor.getTimer   (ROLLBACK_TIMER);

        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "initialize" );
    }

    /**
     * If you run dbHandler outside the slide context and you instantiate
     * dbhandler more than once,
     */
    public void cleanUp () {
        Monitor.deregister(this);
    }

    /**
     * Gets the DbSession bound to this transaction, correspond to a thread.
     *
     * @return  DbSession bound to current thread or null, when a
     *          connection is not yet established
     */
    public IDbSession getDbSession () throws XException {
        XDbSession result = (XDbSession)(sessions.get (Thread.currentThread()));

        // session might be burnt by another store, in this case reinitialize
        if (result != null && result.getXConnection().isActive() == false) {
            sessions.remove (Thread.currentThread());
            IDbSession newSession = createDbSession();

            if( logger.isLoggable(Level.FINE) ) {
                logger.fine(CLASSNAME, "getDbSession", "replaced " + result.toString() +
                                " with " + newSession.toString());
            }

            result = (XDbSession) newSession;
        }
        return result;
    }

    /**
     * Creates a DbSession and binds it to the current thread. If no adminDBSession is yet
     * created, it is done now.
     *
     * @pre        taminoBase != null
     * @pre        database != null
     * @post       true
     * @return     a database session
     * @exception  XException when a DbSession is already bound to this thread
     */
    public IDbSession createDbSession ()
        throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "createDbSession");

        // do some housekeeping at the first time called
        if (adminDbSession == null) {

            // String contentCollection = parentStore.getParameter (TAMINO_COLLECTION);

            adminDbSession = createAdminDbSession ();
            contentCollection = adminDbSession.getContentCollection();

            checkEnabled ();

            // Set contentDoctypes = adminDbSession.getDoctypeNames (contentCollection);
            // String key = adminDbSession.getFullQualifiedCollName (contentCollection);

            IContentCollection coll = adminDbSession.getContentCollection();
            XDoctypeHandler dtHandler = new XDoctypeHandler
                (adminDbSession.getXConnection().getTConnection());

            dtHandler.initCache (coll);
        }

        // allow several drivers
        String dbSessionClass = parentStore.getParameter ("dbSessionClass");
        if (dbSessionClass == null)
            dbSessionClass = DBSESSION;

        IDbSession result = createDbSession (dbSessionClass);
        result.setDbHandler( this );

        if (logger.isLoggable (Level.FINE))
            logger.exiting (CLASSNAME, "createDbSession", result);
        return result;
    }

    /**
     * Creates a UtilDbSession and binds it to the current thread
     * @return a XUtilDbSession
     * @exception  XException when a DbSession is already bound to this thread
     */
    public XUtilDbSession createUtilDbSession ()
        throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "createUtilDbSession");

        XUtilDbSession result = (XUtilDbSession) createDbSession (UTILDBSESSION);
        result.setDbHandler( this );

        if (logger.isLoggable (Level.FINE))
            logger.exiting (CLASSNAME, "createUtilDbSession", result);
        return result;
    }

    /**
     * checks, if the requested collection is webdav enabled. A metadata
     * schema will be created in xdav:metadata, if not yet done.
     *
     * @exception  XException if not webdav enabled
     */
    synchronized void checkEnabled () throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "checkEnabled");

        String collectionName = contentCollection.getCollectionName();
        String tsdLanguage = adminDbSession.getSchemaLanguage (XDbSession.META_COLLECTION);

        checkPropertiesEnabled(adminDbSession.getUtilDBAccessor(), tsdLanguage);
        if (!adminDbSession.getDoctypeNames (XDbSession.META_COLLECTION).contains (collectionName)) {
            adminDbSession.createMetaDataSchema (collectionName);
        }
        if (!adminDbSession.getDoctypeNames   (collectionName).contains (NONXML_CONTENT)) {
            adminDbSession.createNonXMLSchema (collectionName);
        }

        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "checkEnabled");
    }

    
    public static synchronized void checkPropertiesEnabled(XUtilDBAccessor acc, String tsdLanguage)
        throws XException
    {
        // check the schemas for known properties
        Iterator it = XKnownPropertyHelper.getKnownPropertyList(tsdLanguage).getNamespaceIterator ();
        while (it.hasNext()) {
            Namespace namespace = (Namespace)it.next();
            String schemaName = namespace.getPrefix();
            if (!acc.getSchemaNames (XDbSession.META_COLLECTION).contains (schemaName)) {
                acc.createKnownPropertySchema (namespace);
            }
        }
    }

    synchronized XDoctype getDoctype (XDoctype weakDoctype) throws XException {
        XDoctypeHandler dtHandler =
            new XDoctypeHandler (adminDbSession.getXConnection().getTConnection());

        return dtHandler.getDoctype (contentCollection, weakDoctype,
                                     autoCreateXmlSchema);
    }

    /**
     * Removes a DbSession, closes it TConnection and un-binds it
     * from the current thread
     *
     * @return   the removed DbSession
     *
     * @exception  XException
     */
    public IDbSession removeDbSession () throws XException {

        XDbSession session = (XDbSession) sessions.remove (Thread.currentThread ());

        session.getXConnection().close();
        return session;
    }

    //-------------------------------------------------------------
    // IMonitorable interface
    // ------------------------------------------------------------

    /**
     ** Get the name of this monitorable.
     **
     ** @return the name of this monitorable
     **
     **/
    public String getMonName() {
        return monName;
    }

    /**
     ** Get the parent monitorable.
     **
     ** @return the parent monitorable (IMonitorable), null if none
     **
     **/
    public IMonitorable getMonParent() {
        return monParent;
    }

    /**
     ** Get the child monitorables.
     **
     ** @return list of child monitorables (IMonitorable), empty list if none
     **
     **/
    public List getMonChildren() {
        return monChildren;
    }

    /**
     ** Get the monitor.
     **
     ** @return the monitor to this object
     **
     **/
    public IMonitor getMonitor() {
        return monitor;
    }

    /**
     ** Get the value of a registered property.
     ** @param  prop property name
     ** @return the value of the indicated property as String
     **
     **/
    public Object getMonProperty( String prop ) {
        Object result = null;

        if (SESSIONS.equals (prop) ) {
            result = String.valueOf (sessions.size());
        }
        else if (TAMINO_URL.equals (prop) ) {
            result = parentStore.getParameter( TAMINO_BASE )+XUri.SEP+
                parentStore.getParameter( TAMINO_DATABASE )+XUri.SEP+
                parentStore.getParameter( TAMINO_COLLECTION );
        }
        return result;
    }
}




