/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/XContentAccessor.java,v 1.4 2004/12/15 10:38:26 pnever Exp $
 * $Revision: 1.4 $
 * $Date: 2004/12/15 10:38:26 $
 *
 * ====================================================================
 *
 * 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.accessor.TNonXMLObjectAccessor;
import com.softwareag.tamino.db.api.accessor.TRetrieveException;
import com.softwareag.tamino.db.api.accessor.TStreamAccessor;
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.TXMLObject;
import com.softwareag.tamino.db.api.objectModel.jdom.TJDOMObjectModel;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.apache.slide.store.tamino.common.IContent;
import org.apache.slide.store.tamino.common.XContent;
import org.apache.slide.store.tamino.common.XContentNotFoundException;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XException;

/**
 * <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.4 $
 */
public class XContentAccessor {
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    
    /** collection for the content documents */
    private final String contentCollection;
    
    private final TNonXMLObjectAccessor nonXMLContentAccessor;
    
    private final TStreamAccessor streamContentAccessor;
    
    private boolean repairMode;
    
    
    public XContentAccessor(String contentCollection, TNonXMLObjectAccessor nonXMLContentAccessor, TStreamAccessor streamContentAccessor) {
        this.contentCollection = contentCollection;
        this.nonXMLContentAccessor = nonXMLContentAccessor;
        this.streamContentAccessor = streamContentAccessor;
        this.repairMode = false;
    }
    
    public void setRepairMode(boolean repairMode) {
        this.repairMode = repairMode;
    }
    
    // only needed for readContent
    private int recursionCounter = 0;
    private static final int MAX_RECURSION = 5;
    
    /**
     * reads the content of a resource from database.
     *
     * @pre        contentId != null
     * @pre        !(contentId.equals (""))
     * @post       true
     *
     * @param      id   contentId to be read -- this is not the inoId
     *
     * @return     object representing the content
     *
     * @exception  XException
     */
    public IContent readContent (String id) throws XException {
        if (logger.isLoggable (Level.FINE))
            logger.entering (CLASSNAME, "readContent", new Object[] {id, super.toString()});
        
        if( repairMode )
            recursionCounter = MAX_RECURSION;
        
        IContent result = null;
        XContentId contentId = new XContentId (id);
        
        if (contentId.isEmptyContent()) {
            InputStream is = null;
            is = new ByteArrayInputStream (new byte [0]);
            result = new XContent (is);
        }
        else {
            TInputStream is;
            
            try {
                // decide if XML or nonXML
                if (contentId.isXml()) {
                    TXMLObject xmlObject = TXMLObject.newInstance (TJDOMObjectModel.getInstance());
                    xmlObject.setId(contentId.getId());
                    xmlObject.setDoctype (contentId.getSchema());
                    
                    is = streamContentAccessor.retrieve (xmlObject);
                    
                    if (xmlObject == null)
                        throw new XDatastoreException (contentId.getSchema() + "/@" + contentId.getId() + " not found");
                    
                }
                else {
                    InputStream dummyIs = new ByteArrayInputStream (new byte [0]);
                    TNonXMLObject nonXmlObject =
                        TNonXMLObject.newInstance (dummyIs, contentCollection,
                                                   contentId.getSchema(), null, null);
                    
                    nonXmlObject.setId (contentId.getId());
                    nonXmlObject = nonXMLContentAccessor.retrieve (nonXmlObject);
                    
                    if (nonXmlObject == null)
                        throw new XDatastoreException (contentId.getSchema() + "/@" + contentId.getId() + " not found");
                    
                    is = (TInputStream)nonXmlObject.getInputStream();
                }
                
                result = new XContent (is);
                result.setContentId( id );
                result.setIsXml(contentId.isXml());
                try {
                    long l = new Long( is.getHeader().getValue(XGlobals.CONTENT_LENGTH) ).longValue();
                    result.setLength( l );
                }
                catch( NumberFormatException x ) {}
            }
            catch (TException e) {
                // WAM 10.08.2001 work around:
                // in multiuser scenario tamino may return a TRetrieveException for an existing
                // document. Just try again
                // This workaround should no longer be necessary witzh Tamino 3.1
                
                if (e instanceof TRetrieveException && recursionCounter < MAX_RECURSION) {
                    // System.out.println("sleep a little bit");
                    recursionCounter++;
                    StringBuffer msg = new StringBuffer ();
                    
                    msg.append ("ReadContent delayed due to conflict for resource - ");
                    msg.append (" content ID: ").append(id);
                    msg.append (" [timeout: 100 ms, try ").append (recursionCounter);
                    msg.append (" of ").append (MAX_RECURSION).append("]");
                    
                    if( logger.isLoggable(Level.WARNING) ) logger.warning (msg.toString());
                    
                    try {Thread.sleep (100);} catch (InterruptedException ie) {}
                    result = readContent(id);
                }
                else {
                    if (e instanceof TRetrieveException && recursionCounter == MAX_RECURSION) {
                        recursionCounter = 0;
                        StringBuffer msg = new StringBuffer ();
                        
                        msg.append ("ReadContent was not successful for resource - ");
                        msg.append (" content ID: ").append(id);
                        throw new XContentNotFoundException(msg.toString(), e);
                    } else {
                        recursionCounter = 0;
                        throw new XDatastoreException (e);
                    }
                }
            }
        }
        recursionCounter = 0;
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting (CLASSNAME, "readContent", result);
        
        return result;
    }
    
    
    // TODO: use this in dbSession
    public void deleteContent(String id) throws XException {
        XContentId contentId;
        
        contentId = new XContentId (id);
        if (contentId.isEmptyContent()) {
            // do nothing
        } else {
            
            try {
                // decide if XML or nonXML
                if (contentId.isXml()) {
                    TXMLObject xmlObject = TXMLObject.newInstance (TJDOMObjectModel.getInstance());
                    xmlObject.setId(contentId.getId());
                    xmlObject.setDoctype (contentId.getSchema());
                    
                    streamContentAccessor.delete(xmlObject);
                }
                else {
                    throw new XAssertionFailed("TODO");
                }
            }
            catch (TException e) {
                throw new XDatastoreException (e);
            }
        }
    }
}

