/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/tools/repairer/XTaminoClient.java,v 1.2 2004/12/15 10:38:27 pnever Exp $
 * $Revision: 1.2 $
 * $Date: 2004/12/15 10:38:27 $
 *
 * ====================================================================
 *
 * 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.tools.repairer;

import com.softwareag.tamino.db.api.accessor.*;
import org.apache.slide.store.tamino.datastore.*;

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.io.TInputStream;
import com.softwareag.tamino.db.api.objectModel.TNonXMLObject;
import com.softwareag.tamino.db.api.objectModel.TXMLObject;
import com.softwareag.tamino.db.api.objectModel.TXMLObjectIterator;
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.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.store.tamino.common.IContent;
import org.apache.slide.store.tamino.common.XContent;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.store.tamino.common.XForbiddenException;
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.XSchemaFactory;
import org.apache.slide.store.tamino.tools.Env;
import org.apache.slide.store.tamino.tools.repairer.DocumentImpl;
import org.apache.slide.store.tamino.tools.stores.XStore;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XException;
import org.jdom.Element;

/**
 ** TODO: Merge with DatastoreBase.
 **/
public class XTaminoClient {
    public static XTaminoClient create(XStore store, String user, String password) throws XException {
        TConnection connection;
        
        try {
            connection = TConnectionFactory.getInstance().newConnection (store.getTaminoDbUrl(), user, password);
        } catch (TException e) {
            throw new XDatastoreException(e);
        }
        XTaminoClient taminoClient = new XTaminoClient(connection, store.getTaminoCollection());
        return taminoClient;
    }
    
    //--
    
    private final XConnection connection;
    private final String contentCollection;
    
    private final TXMLObjectAccessor jdomContentAccessor;  // TODO: merge into some other class?
    private final XDescriptorsAccessor descriptorsAccessor;
    private final XUtilDBAccessor utilAccessor;
    private final TNonXMLObjectAccessor nonXMLContentAccessor;  // TODO: move into XContentAccessor?
    private final TStreamAccessor streamContentAccessor;
    private final XContentAccessor contentAccessor;
    private final TSchemaDefinition3Accessor schemaAccessor;
    
    public XTaminoClient(TConnection connection, String contentCollection) throws XException {
        this.connection = new XConnection(connection);
        this.contentCollection = contentCollection;
        this.schemaAccessor = this.connection.getTConnection().newSchemaDefinition3Accessor(TDOMObjectModel.getInstance());
        this.utilAccessor = new XUtilDBAccessor(this.connection.getTConnection());
        String tsdLanguage = utilAccessor.getSchemaLanguage(XGlobals.META_COLLECTION);
        
        try {
            
            this.descriptorsAccessor = XDescriptorsAccessor.create(this.connection, contentCollection, tsdLanguage);
            this.jdomContentAccessor = newJDomAccessor(this.connection.getTConnection(), contentCollection);
            this.nonXMLContentAccessor = connection.newNonXMLObjectAccessor (TAccessLocation.newInstance (contentCollection));
            this.streamContentAccessor = this.connection.newStreamAccessor(contentCollection);
            this.contentAccessor = new XContentAccessor(contentCollection, nonXMLContentAccessor, streamContentAccessor);
        } catch (TException e) {
            throw new XDatastoreException(e);
        }
        
        this.contentAccessor.setRepairMode(true);
        
    }
    
    public String getContentCollection() {
        return contentCollection;
    }
    
    public XDescriptorsAccessor getDescriptorsAccessor() {
        return descriptorsAccessor;
    }
    
    public XContentAccessor getContentAccessor() {
        return contentAccessor;
    }
    
    public XUtilDBAccessor getUtilAccessor() {
        return utilAccessor;
    }
    
    private static TXMLObjectAccessor newJDomAccessor(TConnection connection, String collection) throws TException {
        TAccessLocation location = TAccessLocation.newInstance( collection );
        return connection.newXMLObjectAccessor(location, TJDOMObjectModel.getInstance());
    }
    
    public void deleteContent(String queryString) throws XException {
        TQuery query = TQuery.newInstance( queryString );
        try {
            jdomContentAccessor.delete(query);
        } catch (TDeleteException e ) {
            // silently ignoed becaue a delete exception is thrown if there are no matching documents
        }
    }
    
    /**
     * this test insert a non-xml content element with schema in Tamino.
     **/
    public void insertText(String fileName, byte[] data) throws Exception {
        System.out.println("Test enabling of a non-XML document with schema:");
        String contentType = (String) Env.get().getMimeMapping().getMimeTypes("txt").iterator().next();
        TXMLObject nonXmlschema = XSchemaFactory.getNonXMLContentSchema(contentCollection, XGlobals.NONXML_CONTENT);
        schemaAccessor.define(nonXmlschema);
        TNonXMLObject obj = TNonXMLObject.newInstance(new ByteArrayInputStream(data),
                                                      contentCollection, XGlobals.NONXML_CONTENT, fileName, contentType);
        nonXMLContentAccessor.insert(obj);
    }
    
    //--
    
    /**
     ** Returnes a List of contentIds of all Tamino documents available
     ** in the configured collection, which do not have an associated meta data
     ** document (descriptors).
     **
     ** @return List of XContentId objects
     ** @exception XDatastoreException  datastore exception
     **/
    public List getNotWebDAVenabledDocuments() throws XDatastoreException {
        List result;
        List enabledContentIdStrings;
        Iterator doctypeIter;
        String doctype;
        TQuery query;
        TXMLObjectIterator instanceInoIter;
        Element element;
        XContentId contentId;
        
        try {
            result = new ArrayList();
            enabledContentIdStrings = new ArrayList(getDescriptorsAccessor().queryAllContentIds(contentCollection).values());
            doctypeIter = utilAccessor.getDoctypeNames(contentCollection).iterator();
            while (doctypeIter.hasNext()){
                doctype = (String)doctypeIter.next();
                query = TQuery.newInstance (doctype + "/@ino:id");
                instanceInoIter = jdomContentAccessor.query (query).getXMLObjectIterator();
                while (instanceInoIter.hasNext()) {
                    element = (Element)instanceInoIter.next().getElement();
                    contentId = new XContentId(XUtilDbSession.getInoId(element), doctype);
                    if( !(enabledContentIdStrings.contains(contentId.toString())) ){
                        result.add (contentId);
                    }
                }
            }
        }
        catch (TException e) {
            throw new XDatastoreException (e);
        }
        
        return result;
    }
    
    public String getSchemaLanguage (String collection) throws XDatastoreException {
        try {
            return utilAccessor.getSchemaLanguage(collection);
        } catch (Exception e) {
            throw new XDatastoreException (e);
        }
    }
    
    /**
     * This method read the MIME type from a Tamino document given by content id.
     * TODO: mimeType should be available from IContent instead ...
     *
     * @pre        (contentId != null)
     * @post       true
     * @param      contentId      content id
     * @return     none
     */
    public String getMimeType( XContentId contentId ) throws XDatastoreException {
        String mime;
        int idx;
        InputStream dummyIs;
        TNonXMLObject nonXmlObject;
        
        try {
            dummyIs = new ByteArrayInputStream (new byte [0]);
            nonXmlObject = TNonXMLObject.newInstance(dummyIs, contentCollection, contentId.getSchema(), null, null);
            nonXmlObject.setId (contentId.getId());
            nonXmlObject = nonXMLContentAccessor.retrieve (nonXmlObject);
            mime = nonXmlObject.getContentType();
            idx = mime.indexOf(";");
            if (idx != -1) {
                // in case "text/xml;charset=iso..." catch only the part with the real mime type:
                mime = mime.substring(0, idx);
            }
            return mime;
        }
        catch (TException e) {
            throw new XDatastoreException(e);
        }
    }
    
    /**
     * 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      doctype the doctype in xdav:metadata to create or update
     * @param      schemaTemplate the template name, e.g. metadata-schema
     *
     * @exception  XException
     */
    public void createMetaDataSchema (String doctype, String schemaTemplate) throws XException {
        // a change in the collection does take place
        TXMLObject schema = null;
        try {
            schema = XSchemaFactory.getXMLSchema(XGlobals.META_COLLECTION, doctype, schemaTemplate, "http://do/not/care");
        }
        catch (TException e) {
            throw new XException (e);
        }
        utilAccessor.createSchema( XGlobals.META_COLLECTION, schema );
        // a change in the collection did take place
    }
    
    // TODO: call me!!
    public void startTransaction() throws XDatastoreException {
        try {
            connection.startTransactionIfNecessary();
        } catch (TException e) {
            throw new XDatastoreException(e);
        }
    }
    
    public void rollback() throws XDatastoreException {
        try {
            connection.rollback();
        } catch (TException e) {
            throw new XDatastoreException(e);
        }
    }
    
    public void commit() throws XException {
        try {
            connection.commit();
        } catch (TException e) {
            throw new XDatastoreException(e);
        }
    }
    
    public void wipeDatabase() throws Exception {
        getDescriptorsAccessor().deleteByQuery("*");
        deleteContent("*");
    }
    
    
    public DocumentImpl toDocument(XContentId contentId) throws XException {
        IContent content;
        
        // TODO: determin contentLength and mimeType with one database access
        content = contentAccessor.readContent(contentId.toString());
        if (content == null) {
            throw new XAssertionFailed("zero-length content: " + contentId);
        }
        
        byte[] contentbytes = content.getContent().getContentBytes();
        String inoDocname = getTaminoDocname(contentId);
        
        return new DocumentImpl(contentId, contentbytes, getMimeType(contentId), inoDocname);
    }
    
    private String getTaminoDocname(XContentId contentId) {
        String result = null;
        try {
            if (contentId.isXml()) {
                TResponse r = jdomContentAccessor.query(TQuery.newInstance("/"+contentId.getSchema()+"[@ino:id="+contentId.getId()+"]"));
                TXMLObject xmlObject = r.getFirstXMLObject();
                //System.out.println("@@@ id="+contentId+", hasDocname="+xmlObject.hasDocname()+", docname="+xmlObject.getDocname());
                if (xmlObject.hasDocname()) {
                    result = xmlObject.getDocname();
                }
            }
            else {
                TResponse r = nonXMLContentAccessor.query(TQuery.newInstance("/"+contentId.getSchema()+"[@ino:id="+contentId.getId()+"]"));
                TNonXMLObject nonxmlObject = r.getFirstNonXMLObject();
                //System.out.println("@@@ id="+contentId+", hasDocname="+nonxmlObject.hasDocname()+", docname="+nonxmlObject.getDocname());
                if (nonxmlObject.hasDocname()) {
                    result = nonxmlObject.getDocname();
                }
            }
        }
        catch (TQueryException e) {
            e.printStackTrace();
        }
        return result;
    }
    
    public IContent writeContent(XStore store, IContent content) throws XException {
        boolean isXml = true;
        boolean allowNonXML = true;
        
        IContent result = content;
        
        String     id = null;
        String     docType = null;
        //        XContentWrapper contentWrapper = content.getContentWrapper();
        XContentId xcid = new XContentId(content.getContentId());
        XContentWrapper contentWrapper = new XContentWrapper(content.getContent(), xcid.getSchema(), content.getIsXml());
        
        if (contentWrapper.isEmptyContent()) {
            content.setContentId (new XContentId ().toString());
            content.setLength (0);
        }
        else {
            try {
                //                connection.startTransactionIfNecessary();
                
                if (contentWrapper.isXml()) {
                    XDoctype weakDoctype = contentWrapper.getWeakDoctype();
                    
                    // are we allowed to store this doctype?
                    // ask dbHandler to use the adminDb connection
                    XDoctype strongDoctype = getDoctype (store, 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 response = resultParser.getResponse();
                            
                            if (!"0".equals (response)) {
                                if (allowNonXML) {
                                    docType = XGlobals.NONXML_CONTENT;
                                    id = createNonXmlContent (content);
                                    isXml = false;
                                }
                                else {
                                    throw new XForbiddenException(
                                        "Storing content as XML failed with Tamino error " +
                                            response + " 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 = XGlobals.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 = XGlobals.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 = XGlobals.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());
            result = setLength(content);
        }
        
        result.setIsXml (isXml);
        
        return result;
    }
    
    private XDoctype getDoctype (XStore store, XDoctype weakDoctype) throws XException {
        boolean autoCreateXmlSchema = false;
        XDoctypeHandler dtHandler =
            new XDoctypeHandler (connection.getTConnection());
        ContentCollection ccol = new ContentCollection(store.getTaminoBase(), store.getTaminoDatabase(), contentCollection);
        dtHandler.initCache(ccol);
        return dtHandler.getDoctype (ccol, weakDoctype, autoCreateXmlSchema);
    }
    
    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 = contentAccessor.readContent (id.toString ());
            content.setLength (tmpContent.getLength());
            return tmpContent;
        }
        else {
            content.setLength (content.getContentWrapper().getContentLength());
            return content;
        }
    }
    
    public void setContentId(String contentId, NodeRevisionDescriptor nrd) {
        NodeProperty property = new NodeProperty( XGlobals.CONTENT_ID, contentId, XGlobals.TAMINO_NAMESPACE_URI );
        property.setKind( NodeProperty.Kind.PROTECTED );
        nrd.setProperty( property );
        // remove any existing CONTENT_ID property within the default namespace
        nrd.removeProperty(XGlobals.CONTENT_ID);
    }
    
    private String createNonXmlContent (IContent content) throws XException {
        String result = null;
        String docType = content.getDocumentType();
        if( docType == null || "".equals(docType) )
            docType = "application/octet-stream";
        
        try {
            TNonXMLObject nonXmlObject = content.getContentWrapper().getNonXMLObject(docType);
            //            connection.startTransactionIfNecessary();
            nonXMLContentAccessor.insert (nonXmlObject);
            result = nonXmlObject.getId();
        }
        catch (TException e) {
            // try again with default
            try {
                docType = "application/octet-stream";
                TNonXMLObject nonXmlObject = content.getContentWrapper().getNonXMLObject(docType);
                //                connection.startTransactionIfNecessary();
                nonXMLContentAccessor.insert (nonXmlObject);
                result = nonXmlObject.getId();
            }
            catch (TException x) {
                throw new XDatastoreException (x);
            }
        }
        
        return result;
    }
}

