/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/XUtilDBAccessor.java,v 1.4 2004/07/30 06:51:54 ozeigermann Exp $
 * $Revision: 1.4 $
 * $Date: 2004/07/30 06:51: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.datastore;

import com.softwareag.tamino.db.api.accessor.TAccessLocation;
import com.softwareag.tamino.db.api.accessor.TAccessorException;
import com.softwareag.tamino.db.api.accessor.TQuery;
import com.softwareag.tamino.db.api.accessor.TQueryException;
import com.softwareag.tamino.db.api.accessor.TSchemaDefinition2Accessor;
import com.softwareag.tamino.db.api.accessor.TSchemaDefinition3Accessor;
import com.softwareag.tamino.db.api.accessor.TXMLObjectAccessor;
import com.softwareag.tamino.db.api.common.TException;
import com.softwareag.tamino.db.api.connection.TConnection;
import com.softwareag.tamino.db.api.connection.TConnectionFactory;
import com.softwareag.tamino.db.api.connection.TConnectionImpl;
import com.softwareag.tamino.db.api.connection.TLocalTransaction;
import com.softwareag.tamino.db.api.connection.TPooledConnection;
import com.softwareag.tamino.db.api.objectModel.TXMLObject;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.schema.XSchemaFactory;
import org.apache.slide.store.tamino.datastore.schema.XSchemaKnownProperty;
import org.apache.slide.util.JDom;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.output.XMLOutputter;




/**
 * Accessor class to implement some basic Tamino utility methods
 *
 * @author juergen.pill@softwareag.com
 * @version $Revision: 1.4 $
 */
public class XUtilDBAccessor implements XGlobals {


    private TConnection             tConnection;

    static private Map doctypeCache       = Collections.synchronizedMap(new HashMap());
    static private Map schemaCache        = Collections.synchronizedMap(new HashMap());
    static private Map schemaVersionCache = Collections.synchronizedMap(new HashMap());

    static private long schemaCacheLastModified = 0;


    /**
     * Method getSchemaCacheLastModified
     *
     * @return   a long
     *
     */
    static public long getSchemaCacheLastModified() {
        return schemaCacheLastModified;
    }

    /**
     * Constructor.
     *
     * @param tCon a valid connection to Tamino
     *
     */
    public XUtilDBAccessor (TConnection tCon) {
        this.tConnection  = tCon;
    }


    /**
     * get the schema language version of the given collection
     *
     * @param      taminoCollection     name of the collection
     *
     * @return     TSD2 if a tsd2 schema exists, else tsd 3
     *
     * @exception  XException error in data store
     *
     */
    public String getSchemaLanguage(String taminoCollection) throws XDatastoreException {

        String result = getSchemaVersionFromCache(taminoCollection);
        if (result != null) return result;
        result = TSD3_SCHEMA_VERSION;

        try {
            String version = tConnection.newSystemAccessor().getServerVersion();
            if (version.startsWith("2.3")) {
                throw new XDatastoreException ("Tamino version " + version + " not supported");
            }

            //      System.out.println("###########"+ tConnection.newSystemAccessor().getServerVersion());
            TSchemaDefinition2Accessor schema2Accessor = tConnection.newSchemaDefinition2Accessor(TDOMObjectModel.getInstance());
            Iterator schema2Names = schema2Accessor.getSchemaNames(taminoCollection);
            if (schema2Names.hasNext()) {
                throw new XDatastoreException ("Tamino version " + version + " not supported");
            }

            if (getMajorVersion (version) >= 4) {
                result = TSD4_SCHEMA_VERSION;
            }

        }
        catch (TAccessorException e) {
            throw new XDatastoreException (e);
        }
        addSchemaVersionToCache (taminoCollection, result);

        return result;
    }

    /**
     * Creates the schema for desrciptors in Tamino. The schema has the name of the
     * content collection and is stored in xdav:metadata.
     *
     * @pre        (descriptors != null)
     * @post
     *
     * @param      contentCollection the name of the content collection to be webDAV enabled.
     *
     * @exception  XException
     */
    public void createMetaDataSchema (String contentCollection) throws XDatastoreException {
        TXMLObject xml;
        String language;

        try {

            // either TSD3 or TSD4,
            language = getSchemaLanguage (XGlobals.META_COLLECTION);

            xml = XSchemaKnownProperty.getMetadataSchema (contentCollection, language);

            createSchema(XGlobals.META_COLLECTION, xml);

        } catch (XDatastoreException e) {
            e.fillInStackTrace();
            throw e;
        }
        catch (Exception e) {
            throw new XDatastoreException(e);
        }
    }

    /**
     * Creates a namespace specific schema for the known properties
     * of that namespace. Needs TSD4.
     *
     * @param    namespace           a  Namespace
     *
     * @throws   XDatastoreException
     *
     */
    public void createKnownPropertySchema (Namespace namespace) throws XDatastoreException {
        TXMLObject xml;
        String language;
        language = getSchemaLanguage (XGlobals.META_COLLECTION);
        xml = XSchemaKnownProperty.getKnownPropertySchema (namespace, language);
        createSchema (XGlobals.META_COLLECTION, xml);
    }




    /**
     * Create a mini schema based on the given version(tsd2 or 3) collection and schema name
     *
     * @param      taminoCollection     name of the Tamino collection
     * @param      prefixedSchemaName   name of the Tamino schema to be created
     *
     * @return     true if schema nonXML
     *
     * @exception  XException error in data store
     *
     */
    public void createSchema (String taminoCollection,
                              String prefixedSchemaName, String namespaceURL)
        throws XException {

        try {
            createSchema (taminoCollection,
                          XSchemaFactory.getXMLContentSchema (
                              taminoCollection,
                              prefixedSchemaName,
                              namespaceURL));
        }
        catch (TException e) {
            throw new XException(
                "Could not create schema (doctype="+prefixedSchemaName+
                    ") in collection "+taminoCollection, e );
        }
    }

    /**
     * Method createSchema
     *
     * @param    taminoCollection    a  String
     * @param    schema              a  TXMLObject
     *
     * @throws   TException
     *
     */
    public void createSchema( String taminoCollection, TXMLObject schema ) throws XDatastoreException {
        try {
            TSchemaDefinition3Accessor schema3Accessor = null;

            // System.out.println("Tconnection: " + tConnection);
            schema3Accessor = tConnection.newSchemaDefinition3Accessor(TJDOMObjectModel.getInstance());
            schema3Accessor.define( schema );

            clearCache(taminoCollection);
        }
        catch (TException e) {
            e.printStackTrace();
            throw new XDatastoreException(
                "Could not create schema (docname="+schema.getDocname()+", doctype="+schema.getDoctype()+", id="+schema.getId()+
                    ") in collection "+taminoCollection, e );
        }
    }

    /**
     * Method createSchema
     *
     * @param    taminoCollection    a  String
     * @param    schema              a  Document
     *
     * @throws   XDatastoreException
     *
     */
    public void createSchema (String taminoCollection, Document schema) throws XDatastoreException {
        TXMLObject schemaObject = TXMLObject.newInstance (schema);
        createSchema (taminoCollection, schemaObject);
    }
    /**
     * Method getSchema
     *
     * @param    taminoCollection    a  String
     * @param    prefixedSchemaName  a  String
     *
     * @return   a String
     *
     * @throws   XException
     *
     */
    public String getSchemaAsString( String taminoCollection, String prefixedSchemaName )
        throws XException  {
        String result = null;
        Element outroot = getSchemaElement( taminoCollection, prefixedSchemaName );
        if( outroot != null ) {
            Document outdoc = new Document( outroot );
            XMLOutputter out = JDom.outputter();
            result = out.outputString( outdoc );
        }
        return result;
    }

    /**
     * Method getSchema
     *
     * @param    taminoCollection    a  String
     * @param    prefixedSchemaName  a  String
     *
     * @return   a String
     *
     * @throws   XException
     *
     */
    public Element getSchemaElement( String taminoCollection, String prefixedSchemaName ) throws XException {
        Element result = null;
        try {
            String schemaVersion = getSchemaLanguage( taminoCollection );
            TResponse response = null;

            TSchemaDefinition3Accessor schema3Accessor = null;
            schema3Accessor = tConnection.newSchemaDefinition3Accessor(TJDOMObjectModel.getInstance());
            response = schema3Accessor.getSchema( taminoCollection, prefixedSchemaName );

            TXMLObject obj = response.getFirstXMLObject();
            if( obj != null ) {
                Document   doc = (Document)obj.getDocument();
                Element root = doc.getRootElement();
                Namespace xqlNsp = Namespace.getNamespace( XQL_NAMESPACE_PREFIX, XQL_NAMESPACE_URI );
                Element resultElm = root.getChild( "result", xqlNsp );
                Element schemaElm = null;
                Namespace inoNsp = Namespace.getNamespace( TAMINO_TSD_2_NAMESPACE_PREFIX, TAMINO_TSD_2_NAMESPACE_URI );
                Namespace xsNsp = Namespace.getNamespace( XML_SCHEMA_NAMESPACE_PREFIX, XML_SCHEMA_NAMESPACE_URI );

                schemaElm = resultElm.getChild( "schema", xsNsp );
                schemaElm.removeAttribute( "id", inoNsp );
                schemaElm.removeAttribute( "docname", inoNsp );

                result = (Element)schemaElm.clone();
            }
        }
        catch (TException e) {
            throw new XException(
                "Could not retrieve schema "+prefixedSchemaName+" in collection "+taminoCollection, e );
        }

        return result;
    }

    /**
     * Checks if the schema based on the given collection and schema name exists
     *
     * @param      taminoCollection     name of the Tamino collection
     * @param      prefixedSchemaName   name of the Tamino schema to be searched
     *
     * @return     true if schema exists
     *
     * @exception  XException error in data store
     *
     */
    public boolean isSchema(String taminoCollection, String prefixedSchemaName) throws XDatastoreException {
        Set schemaNames = getDoctypeNames(taminoCollection);
        return schemaNames.contains(prefixedSchemaName);
    }




    /**
     * 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   TException
     *
     */
    public Set getDoctypeNames(String taminoCollection) throws XDatastoreException {

        Set result = getDoctypeFromCache(taminoCollection);
        if (result != null) return result;  // found in cache

        Iterator schemaNames;
        result = new HashSet();

        TSchemaDefinition3Accessor schema3Accessor = null;
        schema3Accessor = tConnection.newSchemaDefinition3Accessor(TDOMObjectModel.getInstance());

        try {
            schemaNames = schema3Accessor.getSchemaNames(taminoCollection);
            while (schemaNames.hasNext()) {
                String schema = (String)schemaNames.next();
                Iterator doctypeNames = schema3Accessor.getDoctypeNames
                    (taminoCollection, schema);

                while (doctypeNames.hasNext()) {
                    String doctype = (String)doctypeNames.next();
                    result.add (doctype);
                }
            }
        }
        catch (TQueryException e) {
            throw new XDatastoreException (e);
        }


        addDoctypeToCache(taminoCollection, result);

        return result;
    }

    /**
     * Method getSchemaNames. Always tries to read from cache
     *
     * @param    taminoCollection    a  String
     *
     * @return   a Set
     *
     * @throws   TException
     *
     */
    public Set getSchemaNames (String taminoCollection) throws XDatastoreException {
        return getSchemaNames (taminoCollection, true);
    }

    /**
     * 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    a  String
     * @param    useCache            if true, tries to read from cache
     *
     * @return   a set of schema names
     *
     * @throws   TException
     *
     */
    public Set getSchemaNames (String taminoCollection, boolean useCache) throws XDatastoreException {

        Set result = null;

        if (useCache)
            result = getSchemaFromCache (taminoCollection);

        if (result != null) return result;  // found in cache

        Iterator schemaNames;
        result = new HashSet();


        try {
            TSchemaDefinition3Accessor schema3Accessor = null;
            schema3Accessor = tConnection.newSchemaDefinition3Accessor(TDOMObjectModel.getInstance());
            schemaNames = schema3Accessor.getSchemaNames(taminoCollection);
        }
        catch (TQueryException e) {
            throw new XDatastoreException (e);
        }

        while (schemaNames.hasNext()) {
            String sname = (String)schemaNames.next();
            result.add( sname );
        }

        addSchemaToCache(taminoCollection, result);

        return result;
    }


    /**
     * returns a Set of Document, one document for each schema defined in
     * the collection
     *
     * @param    taminoCollection    a  String
     *
     * @return   a Set
     *
     */
    public Set getSchemas (String taminoCollection) throws XDatastoreException {

        Iterator schemaNames;
        Set result = new HashSet();

        TSchemaDefinition3Accessor schema3Accessor = null;
        schema3Accessor = tConnection.newSchemaDefinition3Accessor (TJDOMObjectModel.getInstance());
        try {
            schemaNames = schema3Accessor.getSchemaNames (taminoCollection);
            while (schemaNames.hasNext() ){
                String schema = (String)schemaNames.next();
                TResponse resp = schema3Accessor.getSchema(taminoCollection, schema);
                result.add (resp.getFirstXMLObject().getDocument());
            }
        }
        catch (TQueryException e) {
            throw new XDatastoreException (e);
        }

        return result;

    }

    /**
     * Checks if the schema based on the given collection and schema name exists
     *
     * @param      taminoCollection     name of the Tamino collection
     * @param      schemaName     name of the Tamino schema to be searched
     *
     * @return     true if schema exists
     *
     * @exception  XException error in data store
     *
     */
    public void deleteSchema(String taminoCollection, String schemaName) throws XException {
        try {
            String schemaVersion = getSchemaLanguage( taminoCollection );
            Iterator schemaNames;

            TSchemaDefinition3Accessor schema3Accessor = null;
            schema3Accessor = tConnection.newSchemaDefinition3Accessor(TDOMObjectModel.getInstance());
            schema3Accessor.undefine(taminoCollection, schemaName);

            clearCache(taminoCollection);


        }
        catch (TException e) {
            throw new XException(
                "Could not delete schema "+schemaName+" in collection "+taminoCollection, e );
        }
    }

    /**
     * Method schemaHasInstances
     *
     * @param    taminoDbUrl         a  String
     * @param    taminoCollection    a  String
     * @param    schemaName          a  String
     *
     * @return   a boolean
     *
     * @throws   XException
     *
     */
    public boolean schemaHasInstances( String taminoDbUrl, String taminoCollection, String schemaName ) throws XException {
        TLocalTransaction lt = null;
        TConnection myTcon = null;
        try {
            boolean result = false;
            TSchemaDefinition3Accessor schema3Accessor = null;
            String schemaVersion = getSchemaLanguage( taminoCollection );
            List dtnames = new ArrayList();
            Iterator dtnamesIt;
            myTcon = TConnectionFactory.getInstance().newConnection(taminoDbUrl);

            schema3Accessor = myTcon.newSchemaDefinition3Accessor(TDOMObjectModel.getInstance());
            dtnamesIt = schema3Accessor.getDoctypeNames( taminoCollection, schemaName );
            while( dtnamesIt.hasNext() ) {
                String dtname = (String)dtnamesIt.next();
                dtnames.add( dtname );
            }

            TXMLObjectAccessor jdomContentAccessor = myTcon.newXMLObjectAccessor(
                TAccessLocation.newInstance(taminoCollection),
                TJDOMObjectModel.getInstance() );

            lt = myTcon.useLocalTransactionMode();

            dtnamesIt = dtnames.iterator();
            while( dtnamesIt.hasNext() ) {
                String dtname = (String)dtnamesIt.next();

                TQuery query = TQuery.newInstance( dtname+"/@ino:id" );
                TResponse response = jdomContentAccessor.query( query, 1 );
                if( response.hasFirstXMLObject() ) {
                    result = true;
                    break;
                }
            }

            lt.commit();
            return result;
        }
        catch (TException e) {
            try {
                if( lt != null )
                    lt.rollback();
            } catch (TException x) {}
            throw new XException(
                "Could not determine whether "+schemaName+" in collection "+taminoCollection+" has instances", e );
        }
        finally {
            try {
                myTcon.close();
            } catch (Exception e) {}
        }
    }


    /************************************/
    /**     cache manipulation methods **/
    /************************************/

    static public void clearCache() {
        doctypeCache.clear();
        schemaCache.clear();
        schemaCacheLastModified = System.currentTimeMillis();
        schemaVersionCache.clear();
    }

    private String getSchemaVersionFromCache(String collection) {
        String result = (String)schemaVersionCache.get(getCacheKey(collection));
        return result;
    }


    private void addSchemaVersionToCache(String collection, String schemaVersion) {
        schemaVersionCache.put(getCacheKey(collection), schemaVersion);
    }




    private void addDoctypeToCache(String collection, Set doctypes) {
        doctypeCache.put(getCacheKey(collection), doctypes);
    }

    private void addSchemaToCache(String collection, Set schemas) {
        schemaCache.put(getCacheKey(collection), schemas);
        schemaCacheLastModified = System.currentTimeMillis();
    }


    private void clearCache(String collection) throws TException {
        doctypeCache.remove(getCacheKey(collection));
        schemaCache.remove(getCacheKey(collection));
        schemaCacheLastModified = System.currentTimeMillis();
    }

    private Set getDoctypeFromCache(String collection) {
        Set result = (Set)doctypeCache.get(getCacheKey(collection));
        return result;
    }

    private Set getSchemaFromCache(String collection) {
        Set result = (Set)schemaCache.get(getCacheKey(collection));
        return result;
    }

    private String getCacheKey(String collection) {
        String dbUri;

        if (tConnection instanceof TConnectionImpl)
            dbUri = ((TConnectionImpl)tConnection).getDatabaseURI();

        else if (tConnection instanceof TPooledConnection)
            dbUri = ((TPooledConnection)tConnection).getDatabaseURI();

        else
            throw new XAssertionFailed ("unknown type of TConnection: " + tConnection.getClass().toString());

        String result = dbUri + "/"+collection;
        return result;
        //        return collection;
    }

    /**
     * returns the major Tamino version (first number before ".")
     *
     * @param    version             a  String
     *
     * @return   an int
     *
     */
    private int getMajorVersion (String version) {
        int pos = version.indexOf (".");
        if (pos == -1) throw new XAssertionFailed ();
        Integer majorVersion = new Integer (version.substring (0, pos));
        return majorVersion.intValue();
    }

}



