/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/store/XContentStore.java,v 1.3 2004/07/30 06:52:03 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:52:03 $
 *
 * ====================================================================
 *
 * 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.store;

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 java.util.Date;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceConnectionFailedException;
import org.apache.slide.common.ServiceDisconnectionFailedException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.ServiceResetFailedException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionNumber;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.store.ContentStore;
import org.apache.slide.store.tamino.common.IContent;
import org.apache.slide.store.tamino.common.IContentHandler;
import org.apache.slide.store.tamino.common.IDescriptors;
import org.apache.slide.store.tamino.common.XContentHandler;
import org.apache.slide.store.tamino.common.XContentNotFoundException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.common.XResolver;
import org.apache.slide.store.tamino.datastore.XContentId;
import org.apache.slide.store.tamino.jdomobjects.XTLock;
import org.apache.slide.store.tamino.store.monitoring.Monitor;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XException;
import org.apache.slide.webdav.util.WebdavConstants;


/**
 * Tamino-based implementation of DescriptorsStore.
 *
 * @author    peter.nevermann@softwareag.com
 *
 * @version   $Revision: 1.3 $
 *
 */
public class XContentStore extends XChildStore implements ContentStore {
    
    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 int BUFFER_SIZE = 2048;
    private static final String CHARACTER_ENCODING = "8859_1";
    
    /** the content handler */
    private IContentHandler contentHandler;
    
    
    
    /**
     ** Default constructor.
     **/
    public XContentStore() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>" );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }
    
    /**
     * Connects to store.
     *
     * @exception ServiceConnectionFailedException connection failed
     */
    public synchronized void connect() throws ServiceConnectionFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "connect" );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "connect" );
    }
    
    
    /**
     * Disconnects from content store.
     *
     * @exception ServiceDisconnectionFailedException
     */
    public synchronized void disconnect() throws ServiceDisconnectionFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "dicconnect" );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "disconnect" );
    }
    
    
    /**
     * Initializes content store.
     * @param token namespace access token
     * @exception ServiceInitializationFailedException Throws an exception
     * if the store has already been initialized before
     */
    public synchronized void initialize(NamespaceAccessToken token)
        throws ServiceInitializationFailedException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "initialize",
                            new Object[] {(token!=null ? token.getName() : null)} );
        
        contentHandler = new XContentHandler (this);
        
        // monitoring variables
        this.monName = "ContentStore";
        this.monParent = getParentStore();
        this.monChildren.add( contentHandler );
        this.monitor = Monitor.getMonitor( this ); // this comes last!!
        
        // init the children
        contentHandler.initialize();
        super.initialize( token ); // PN 31.3.01: added
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "initialize" );
    }
    
    
    /**
     * Deletes content store.
     *
     * @exception ServiceResetFailedException
     */
    public void reset() throws ServiceResetFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "reset" );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "reset" );
    }
    
    
    /**
     * This function tells whether or not the service is connected.
     *
     * @return boolean true if we are connected
     * @exception ServiceAccessException Service access error
     */
    public boolean isConnected() throws ServiceAccessException {
        return true;
    }
    
    /**
     * Ask the resource manager to prepare for a transaction commit of the
     * transaction specified in xid.
     *
     * @param xid A global transaction identifier
     *
     * @return A value indicating the resource manager's vote on the outcome
     * of the transaction. The possible values are: XA_RDONLY or XA_OK. If
     * the resource manager wants to roll back the transaction, it should do
     * so by raising an appropriate XAException in the prepare method.
     *
     * @exception XAException An error has occurred. Possible exception
     * values are: XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL,
     * or XAER_PROTO.
     */
    public int prepare (Xid xid) throws XAException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "prepare", new Object[] {xid}  );
        
        int result = super.prepare( xid );
        try {
            contentHandler.prepare ();
        }
        catch( XException x ) {
            x.printStackTrace();
            throw new XAException( XAException.XA_RBCOMMFAIL );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "prepare", new Integer(result) );
        return result;
    }
    
    /**
     * Commit the global transaction specified by xid.
     * @param xid transaction id
     * @param onePhase true in case of one-phase-commit
     * @exception XAException transaction error
     */
    public void commit( Xid xid, boolean onePhase ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "commit",
                                                            new Object[] {xid, new Boolean(onePhase)}  );
        
        getParentStore().commitCounter.increment(); // monitoring
        Object timerId = getParentStore().commitTimer.start(); // monitoring
        
        super.commit( xid, onePhase );
        try {
            contentHandler.commit( onePhase );
        }
        catch( XException x ) {
            getParentStore().commitTimer.stop( timerId ); // monitoring
            x.printStackTrace();
            throw new XAException( XAException.XA_RBCOMMFAIL );
        }
        
        getParentStore().commitTimer.stop( timerId ); // monitoring
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "commit" );
    }
    
    /**
     * Inform the resource manager to roll back work done on behalf of a
     * transaction branch.
     * @param xid transaction id
     * @exception XAException transaction error
     */
    public void rollback( Xid xid ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "rollback",
                                                            new Object[] {xid}  );
        
        try {
            super.rollback( xid );
        }
        catch( Exception x ) {
            x.printStackTrace();
            throw new XAException( XAException.XA_RBCOMMFAIL );
        }
        finally {
            try {
                contentHandler.rollback();
            }
            catch( XException x ) {
                x.printStackTrace();
                throw new XAException( XAException.XA_RBCOMMFAIL );
            }
        }
        
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "rollback" );
    }
    
    
    
    // --------------------------------------------------- ContentStore Methods
    
    
    /**
     * Retrieve revision content.
     *
     * @param uri Uri
     * @param revisionDescriptor revision descriptor
     * @return node revision content
     * @exception ServiceAccessException Service access error
     * @exception RevisionNotFoundException revision not found
     */
    public NodeRevisionContent retrieveRevisionContent
        (Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException, RevisionNotFoundException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME,
                            "retrieveRevisionContent",
                            new Object[]
                            {uri, (revisionDescriptor!=null
                                       ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                       : null) } );
        
        // slide ms 13 reads the content for the root element
        if (uri.getRelative().equals("")) {
            NodeRevisionContent result = new NodeRevisionContent();
            result.setContent("".toCharArray());
            return result;
        }
        
        
        getParentStore().retrieveContentCounter.increment(); // monitoring
        Object timerId = getParentStore().retrieveContentTimer.start();
        
        NodeRevisionContent result = null;
        NodeProperty contentIdProperty = revisionDescriptor.getProperty(CONTENT_ID,
                                                                        TAMINO_NAMESPACE_URI);
        String contentId = null;
        if( contentIdProperty != null )
            contentId = (String) contentIdProperty.getValue();
        else
            contentId = new XContentId().toString();
        
        try {
            result = contentHandler.read (contentId).getContent();
        }
        catch (XContentNotFoundException e) {
            throw new RevisionNotFoundException(uri.toString(), revisionDescriptor.getRevisionNumber());
        }
        catch (XException e) {
            throw serviceAccessException(e, uri);
        }
        
        getParentStore().retrieveContentTimer.stop( timerId );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "retrieveRevisionContent", result );
        return result;
    }
    
    
    /**
     * Create a new revision
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     * @exception ServiceAccessException Service access error
     * @exception RevisionAlreadyExistException revision already exists
     */
    public void createRevisionContent
        (Uri uri, NodeRevisionDescriptor revisionDescriptor,
         NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionAlreadyExistException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME,
                            "createRevisionContent",
                            new Object[] {uri,
                            (revisionDescriptor!=null
                                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                 : null), revisionContent} );
        
        getParentStore().createContentCounter.increment(); // monitoring
        Object timerId = getParentStore().createContentTimer.start();
        
        //aquire the tlock first
        getDescriptorsStore().tlockDescriptors( uri );
        try {
            IContent content = contentHandler.create (revisionContent, revisionDescriptor);
            String contentId = content.getContentId();
            
            setContentId(contentId, revisionDescriptor);
            revisionDescriptor.setContentLength (content.getLength());
            revisionDescriptor.setLastModified (new Date());
            
            revisionDescriptor.removeProperty (XGlobals.IS_XML,
                                               XGlobals.TAMINO_NAMESPACE_URI);
            
            NodeProperty isXmlProp =
                new NodeProperty (XGlobals.IS_XML,
                                  new Boolean (content.getIsXml()),
                                  XGlobals.TAMINO_NAMESPACE_URI);
            isXmlProp.setKind (NodeProperty.Kind.PROTECTED);
            
            revisionDescriptor.setProperty (isXmlProp);
        }
        catch (XException e) {
            throw serviceAccessException(e, uri);
        }
        
        getParentStore().createContentTimer.stop( timerId );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createRevisionContent" );
    }
    
    /**
     * Modify the latest revision of an object.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     * @exception ServiceAccessException service access error
     * @exception RevisionNotFoundException revision not found
     */
    public void storeRevisionContent
        (Uri uri, NodeRevisionDescriptor revisionDescriptor,
         NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionNotFoundException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME,
                            "storeRevisionContent",
                            new Object[] {uri,
                            (revisionDescriptor!=null
                                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                 : null), revisionContent} );
        
        
        getParentStore().storeContentCounter.increment(); // monitoring
        Object timerId = getParentStore().storeContentTimer.start();
        
        String contentId = null;
        
        // Don't rely on content ID in passed NRD
        NodeProperty cidProp = null;
        boolean isNullResource = false;
        XResolver r = XResolver.create( getDescriptorsHandler(), uri.toString() );
        try {
            IDescriptors actualDescs = r.run(XTLock.NO_LOCK, XTLock.NO_LOCK, false);
            NodeRevisionNumber actualNrn = new NodeRevisionNumber();
            NodeRevisionDescriptor actualNrd = actualDescs.getRevisionDescriptor(actualNrn);
            cidProp = actualNrd.getProperty(CONTENT_ID, TAMINO_NAMESPACE_URI);
            isNullResource =
                actualNrd.propertyValueContains(WebdavConstants.P_RESOURCETYPE, WebdavConstants.E_LOCKNULL);
        }
        catch (XException e) {
            // ignore silently
        }
        
        if (cidProp == null && !isNullResource) {
            cidProp = revisionDescriptor.getProperty(CONTENT_ID,
                                                     TAMINO_NAMESPACE_URI);
        }
        if (cidProp == null)
            contentId = new XContentId().toString();
            //            throw new RevisionNotFoundException(
            //                String.valueOf(uri), revisionDescriptor.getRevisionNumber() );
        else
            contentId = (String) cidProp.getValue();
        
        //aquire the tlock first
        getDescriptorsStore().tlockDescriptors( uri );
        try {
            
            IContent content = contentHandler.update (contentId, revisionContent, revisionDescriptor);
            // contentId might have changed
            contentId = content.getContentId();
            setContentId(contentId, revisionDescriptor);
            revisionDescriptor.setContentLength (content.getLength());
        }
        catch (XException e) {
            throw serviceAccessException(e, uri);
        }
        
        getParentStore().storeContentTimer.stop( timerId );
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting( CLASSNAME, "storeRevisionContent" );
    }
    
    
    /**
     * Remove revision.
     *
     * @param uri Uri
     * @param revisionDescriptor revision descriptor
     * @exception ServiceAccessException service access error
     */
    public void removeRevisionContent
        (Uri uri, NodeRevisionDescriptor revisionDescriptor)
        throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeRevisionContent",
                            new Object[] {uri,
                            (revisionDescriptor!=null
                                 ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                 : null)} );
        getParentStore().removeContentCounter.increment(); // monitoring
        Object timerId = getParentStore().removeContentTimer.start();
        
        NodeProperty prop = revisionDescriptor.getProperty(CONTENT_ID,
                                                           TAMINO_NAMESPACE_URI);
        
        //aquire the tlock first
        //NOTE: pass nrd to check for concurrent mod.
        getDescriptorsStore().tlockDescriptors( uri );
        try {
            // if this uri specifies a node, it may have no content
            if (prop != null)
                contentHandler.delete ((String)prop.getValue());
        }
        catch (XException e) {
            throw serviceAccessException(e, uri);
        }
        
        getParentStore().removeContentTimer.stop( timerId );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeRevisionContent" );
    }
    
    /**
     ** Returns true if this store is in repair mode (i.e. read_only).
     ** @return     true if this store is in repair mode
     **/
    public boolean isInRepairMode() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "isInRepairMode" );
        
        boolean result = getParentStore().isInRepairMode();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "isInRepairMode", new Boolean(result) );
        return result;
    }
    
    /**
     ** Set repair mode for this store.
     ** @param     onOpenTA 0=WAIT, 1=ERROR, 2=ROLLBACK
     ** @param     waitTimeout the timeout for onOpenTA=0 (WAIT)
     ** @exception XException xdav error
     **/
    public void setRepairMode( int onOpenTA, long waitTimeout ) throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setRepairMode", new Object[] {new Integer(onOpenTA), new Long(waitTimeout)}  );
        
        contentHandler.setRepairMode( onOpenTA, waitTimeout );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "setRepairMode" );
    }
    
    /**
     ** Release repair mode for this store.
     ** @exception XException xdav error
     **/
    public void releaseRepairMode() throws XException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "releaseRepairMode" );
        
        contentHandler.releaseRepairMode();
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "releaseRepairMode" );
    }
    
    /**
     * Sets the <code>xdavContentId</code> in the {@link org.apache.slide.store.tamino.common.XGlobals#TAMINO_NAMESPACE_URI
     * Tamino namespace} on the given NodeRevisionDescriptors. Any existing
     * <code>xdavContentId</code> in the <code>DAV:</code> namespace will be removed.
     *
     * @param      contentId           the value of the <code>xdavContentId</code> property.
     * @param      revisionDescriptor  the NodeRevisionDescriptor.
     */
    public static void setContentId(String contentId, NodeRevisionDescriptor revisionDescriptor) {
        
        NodeProperty property = new NodeProperty( CONTENT_ID, contentId, TAMINO_NAMESPACE_URI );
        property.setKind( NodeProperty.Kind.PROTECTED );
        revisionDescriptor.setProperty( property );
        // remove any existing CONTENT_ID property within the default namespace
        revisionDescriptor.removeProperty(CONTENT_ID);
    }
}


