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

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.connection.TConnection;
import com.softwareag.tamino.db.api.objectModel.TXMLObject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.XUtilDBAccessor;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.JDom;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.DOMOutputter;
import org.jdom.output.XMLOutputter;

// TODO (wam) This file should get rid of TSD2 and TSD3, if we can rely on
// tamino 4.x


/**
 ** Factory class for the automatic creation of Tamino schemas for metadata and
 ** content data.
 **
 ** @author    peter.nevermann@softwareag.com
 ** @version   $Revision: 1.4 $
 **/
public class XSchemaFactory implements XGlobals {

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

    // constants
    private static final String PHYSICAL_METADATA_SCHEMA_LOCATION = "metadata_schema";
    private static final String PHYSICAL_XMLCONTENT_SCHEMA_LOCATION = "xmlcontent_schema";
    private static final String PHYSICAL_NONXMLCONTENT_SCHEMA_LOCATION = "nonxmlcontent_schema";
    private static final String DOCTYPE_TAG = "doctype";
    private static final String NODE = "node";
    private static final String NAME = "name";
    private static final String ANNOTATION_TAG = "annotation";
    private static final String APPINFO_TAG = "appinfo";
    private static final String SCHEMAINFO_TAG = "schemaInfo";
    private static final String COLLECTION_TAG = "collection";
    private static final String ELEMENT_TAG = "element";

    private static final String COLLECTION_TOKEN = "${collection}";
    private static final String DOCTYPE_TOKEN = "${doctype}";
    private static final String INO_COLLECTION = "ino:collection";

    private static final Namespace XS_SCHEMA_NAMESPACE =
        Namespace.getNamespace ("xs", XGlobals.XML_SCHEMA_NAMESPACE_URI);


    /**
     ** Create an XML content schema for the given collection and doctype using a specific schemaVersion.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      collection the Tamino collection
     ** @param      prefixedDoctype the doctype
     ** @return     a new schema
     **
     ** @exception  XException
     **/
    public static TXMLObject getXMLContentSchema( String collection,
                                                 String prefixedDoctype,
                                                 String namespaceURI )
        throws TException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getXMLContentSchema",
                            new Object[]{collection, prefixedDoctype}  );

        // if there is a namespace uri but no prefix
        //      if ((!"".equals (namespaceURI)) && (doctype.indexOf (':') == -1))
        //          doctype = "DUMMY:" + doctype;
//
        TXMLObject result = getXMLSchema(collection, prefixedDoctype, PHYSICAL_XMLCONTENT_SCHEMA_LOCATION, namespaceURI);

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

    /**
     ** Create a non-XML content schema for the given collection, the doctype will
     ** be as stated in parameter doctype and the given schema version is used.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      collection the Tamino collection
     ** @param      doctype the doctype (schema name) for the new Tamino schema
     **
     ** @return     a new schema
     **
     ** @exception  XException
     **/
    public static TXMLObject getNonXMLContentSchema (String collection, String doctype)
        throws TException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getNonXMLContentSchema",
                            new Object[]{collection, doctype}  );

        TXMLObject result = getXMLSchema(collection, doctype,
                                         PHYSICAL_NONXMLCONTENT_SCHEMA_LOCATION,
                                         DUMMY_NAMESPACE_URI);


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

    /**
     ** Create an XML content schema for the given collection and doctype using a specific schemaVersion.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      collection the Tamino collection
     ** @param      doctype the doctype
     ** @param      schemaVersion the schemaVersion ( TSD2 / TSD3 )
     ** @param      the template as stream
     ** @return     a new schema
     **
     ** @exception  XException
     **/
    private static TXMLObject getXMLSchema (String collection, String prefixedDoctype,
                                            Document template,
                                            String namespaceURL ) throws TException  {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getXMLSchema", new Object[]{collection,
                            prefixedDoctype, template}  );

        TXMLObject result = null;

        try {
            // TSD3 or TSD 4
            result = adoptTSD3template (template, collection, prefixedDoctype, namespaceURL);

        }
        catch (JDOMException x) {
            x.printStackTrace();
        }
        // TEST ONLY
        // print the schema
        // try { new XMLOutputter().output (new org.jdom.input.DOMBuilder().build((org.w3c.dom.Element )result.getElement()), System.out);}
        // catch (java.io.IOException printException) {printException.printStackTrace();}
        // TEST ONLY

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


    /**
     ** Create an XML content schema for the given collection and doctype using a specific schemaVersion.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      collection the Tamino collection
     ** @param      prefixedDoctype the doctype
     ** @param      templateLocation The (physical) location of the template
     ** @return     a new schema
     **
     ** @exception  XException
     **/
    public static TXMLObject getXMLSchema (String collection, String prefixedDoctype,
                                           String templateLocation,
                                           String namespaceURL ) throws TException  {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getXMLSchema", new Object[]{collection, prefixedDoctype, templateLocation}  );

        TXMLObject result = null;

        // get the template
        InputStream in;

        // TSD3 or 4
        in = XSchemaFactory.class.getResourceAsStream (templateLocation + "3.xml");

        try {
            SAXBuilder sax = new SAXBuilder();
            Document template = sax.build( in );

            result = getXMLSchema (collection, prefixedDoctype,
                                   template, namespaceURL);

        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

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

    /**
     ** Replace the tokens for collection name, schema name, doctype name
     ** in the given TSD3 template
     **
     ** @pre        (template != null)
     ** @pre        (collection != null)
     ** @pre        (doctype != null)
     **
     ** @param      template     template of a TSD3 schema/doctype
     ** @param      collection   collection name of the schema
     ** @param      doctype      doctype name
     **
     ** @return     schema/doctype definition
     **
     ** @exception  JDOMException
     **
     */
    private static TXMLObject adoptTSD3template (Document template,
                                                 String collection,
                                                 String doctype,
                                                 String namespaceURL) throws JDOMException {

        if (doctype.indexOf(":") > 0) {
            return adoptTSD3template(template,
                                     collection,
                                     doctype.substring(doctype.indexOf(":")+1, doctype.length()),
                                     doctype.substring(0, doctype.indexOf(":") ),
                                     namespaceURL);
        }
        else if  (namespaceURL != null && namespaceURL.length() > 0) {
            return adoptTSD3template(template, collection, doctype,
                                     null, namespaceURL);
        }
        else {
            return adoptTSD3template(template, collection, doctype, null, null);

        }
    }

    /**
     ** Replace the tokens for collection name, schema name, doctype name
     ** in the given TSD3 template
     **
     ** @pre        (template != null)
     ** @pre        (collection != null)
     ** @pre        (doctype != null)
     **
     ** @param      template     template of a TSD3 schema/doctype
     ** @param      collection   collection name of the schema
     ** @param      doctype      doctype name
     **
     ** @return     schema/doctype definition
     **
     ** @exception  JDOMException
     **
     */
    private static TXMLObject adoptTSD3template (Document template,
                                                 String collection,
                                                 String doctype,
                                                 String namespacePrefix,
                                                 String namespaceURL)
        throws JDOMException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "adoptTSD3template", new Object[]{template, collection, doctype, namespacePrefix, namespaceURL}  );

        String qualifiedName = doctype;

        if (namespacePrefix != null) {
            qualifiedName = namespacePrefix + ":" + doctype;
        }


        TXMLObject result = null;

        Element schemaElem = template.getRootElement();

        // test only!
        //        if( logger.isLoggable(Level.FINE) ) {
        //            try {
        //                XMLOutputter xmlout = new XMLOutputter("  ", true);
        //                xmlout.setTextTrim ( true );
        //                logger.fine( xmlout.outputString(schemaElem) );
        //            }
        //            catch( Exception x ) {};
        //        }

        // add namespace definitions
        if (namespacePrefix != null && namespaceURL != null) {
            schemaElem.addNamespaceDeclaration(Namespace.getNamespace(namespacePrefix, namespaceURL));
            schemaElem.setAttribute("targetNamespace", namespaceURL);
        }



        List schemaChildList = schemaElem.getChildren();
        Iterator it3 = schemaChildList.iterator();
        while (it3.hasNext()) {
            Element annotElem = (Element)it3.next();

            if (annotElem.getName().equals(ANNOTATION_TAG)) {
                List annotationChildList = annotElem.getChildren();
                Iterator it4 = annotationChildList.iterator();
                while (it4.hasNext()) {
                    Element appInfoElem = (Element)it4.next();

                    if (appInfoElem.getName().equals(APPINFO_TAG)) {
                        List appInfoChildList = appInfoElem.getChildren();
                        Iterator it = appInfoChildList.iterator();
                        while (it.hasNext()) {
                            Element schemaInfoElem = (Element)it.next();
                            String name = schemaInfoElem.getName();
                            if (name.equals(SCHEMAINFO_TAG)) {
                                // set schema name:
                                schemaInfoElem.removeAttribute( NAME );
                                schemaInfoElem.setAttribute( new Attribute(NAME, qualifiedName ) );

                                List schemaInfoList = schemaInfoElem.getChildren();
                                Iterator it2 = schemaInfoList.iterator();
                                while (it2.hasNext()) {
                                    Element elem2 = (Element)it2.next();
                                    String name2 = elem2.getName();
                                    if (name2.equals(COLLECTION_TAG)) {
                                        // set schema name:
                                        elem2.removeAttribute( NAME );
                                        elem2.setAttribute( new Attribute(NAME, collection ) );
                                    }
                                    if (name2.equals(DOCTYPE_TAG)) {
                                        // set schema name:
                                        elem2.removeAttribute( NAME );
                                        elem2.setAttribute( new Attribute(NAME, qualifiedName ) );
                                    }
                                }
                            }
                        }
                    }
                }
            } // end-if ANNOTATION_TAG
            if (annotElem.getName().equals(ELEMENT_TAG)) {
                // set doctype name:
                annotElem.removeAttribute( NAME );
                annotElem.setAttribute( new Attribute(NAME, doctype ) );
            }
        }


        // test only!
        if( logger.isLoggable(Level.FINE) ) {
            try {
                XMLOutputter xmlout = JDom.outputter();
                logger.fine( xmlout.outputString(schemaElem) );
            }
            catch( Exception x ) {}
        }

        // create schema instance
        result = TXMLObject.newInstance(new DOMOutputter().output(template));
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "adoptTSD3template", result );
        return result;
    }


    /**
     * Creates TSD3/4 schema documents for all enabled collections. The collection
     * names are queried from xdav:metadata, the schema is created from scratch
     * as a TSD3/4 schema.
     *
     * @return   a List of TXMLObjects with TSD3 schema documents (as DOM)
     *
     */
    public static List getAllSchemas (TConnection tCon, String tsdLanguage) throws XDatastoreException, TException {

        List result = new ArrayList ();
        Iterator it = new XUtilDBAccessor(tCon).getDoctypeNames (META_COLLECTION).iterator();
        try {
            while (it.hasNext()) {
                String docType = (String)it.next();
                TXMLObject schemaObject = null;
                if (getDefaultNamespace(docType).equals(TAMINO_NAMESPACE_URI)) {
                    schemaObject = getXMLContentSchema(META_COLLECTION, docType, TAMINO_NAMESPACE_URI);
                } else {
                    schemaObject = XSchemaKnownProperty.getMetadataSchema(docType, tsdLanguage);
                }
                result.add (schemaObject);
            }
        } catch (JDOMException e) {
            throw new XDatastoreException (e);
        } catch (IOException e) {
            throw new XDatastoreException (e);
        }

        return result;
    }


    /**
     * Creates TSD3 schema documents for all enabled collections. The collection
     * names are queried from xdav:metadata, the schema is created from scratch
     * as a TSD3 schema.
     *
     * @return   a List of TXMLObjects with TSD3 schema documents (as DOM)
     *
     */
    public static String getDefaultNamespace (String doctypeName) {
        String result = DUMMY_NAMESPACE_URI;
        if (doctypeName.startsWith(TAMINO_NAMESPACE_PREFIX+":"))
            result = TAMINO_NAMESPACE_URI;
        return result;
    }
}

