/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/tools/repairer/XRepairerStore.java,v 1.3 2004/07/30 06:52:05 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:52:05 $
 *
 * ====================================================================
 *
 * 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.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.connection.TConnection;
import com.softwareag.tamino.db.api.connection.TConnectionFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import org.apache.slide.authenticate.CredentialsToken;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.Scope;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceConnectionFailedException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionDescriptorNotFoundException;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.store.tamino.common.XConflictException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.datastore.MetaDataURLAccessor;
import org.apache.slide.store.tamino.store.XFileStore;
import org.apache.slide.store.tamino.store.XParentStore;
import org.apache.slide.store.tamino.tools.stores.XDomain;
import org.apache.slide.store.tamino.tools.stores.XDomainFileHandler;
import org.apache.slide.store.tamino.tools.stores.XStoreGroup;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;
import org.apache.slide.util.cli.Abort;
import org.apache.slide.util.os.Catalina;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

/**
 ** Supports the WebDAV-interface for the Repair Tool
 **
 ** @author    peter.nevermann@softwareag.com
 ** @version   $Revision: 1.3 $
 **/
public class XRepairerStore extends XFileStore 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);
    
    /** Credentials */
    private String user = "";
    private String pwd = "";
    
    private XDomain domain;
    
    private String ns;
    
    /**
     ** Default constructor.
     **/
    public XRepairerStore() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>" );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }
    
    /**
     * Checks whether repair mode token exists for given token.
     * To be called at initialization time of the Tamino stores in order to set initially
     * the repair mode.
     * @param store  the store
     * @return true if repair mode token exists, false otherwise
     */
    public boolean existsRepairModeToken( String store ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "existsRepairModeToken", new Object[]{store} );
        
        boolean result = false;
        File rmtf = new File( rootpath+XUri.SEP+store+XUri.SEP+XRepairModeToken.REPAIR_MODE_TOKEN );
        if( rmtf.exists() )
            result = true;
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "existsRepairModeToken", new Boolean(result) );
        return result;
    }
    
    /**
     * This function tells whether or not this child store is connected.
     *
     * @return boolean true if we are connected
     * @exception ServiceAccessException Service access error
     */
    public boolean isConnected() throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "isConnected" );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "isConnected",
                                                           new Boolean(false) );
        return false;
    }
    
    /**
     * Overwriting inherited in order to get the credentials.
     *
     * @param crdtoken the slide token containing e.g. the credential
     * @exception ServiceConnectionFailedException Connection failed
     */
    public void connect(CredentialsToken crdtoken) throws ServiceConnectionFailedException {
        connect();
    }
    
    /**
     * Initializes this child store.
     * @param token  the namespace token
     * @exception ServiceInitializationFailedException Throws an exception
     * if this child 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)} );
        
        super.initialize( token );
        this.ns= token.getName();
        try {
            File f = new File( rootpath );
            if( !f.exists() )
                f.mkdir();
            
            this.domain = XDomainFileHandler.get().getDomain();
            List sl = domain.getNamespace(namespace.getName()).getPublicStoreGroupNames();
            Iterator stores = sl.iterator();
            while( stores.hasNext() ) {
                // create the folder /administration/repairer/<store> if not there
                String store = (String)stores.next();
                File sf = new File( rootpath+XUri.SEP+store );
                if( !sf.exists() )
                    sf.mkdir();
                
                XParentStore targetStore = (XParentStore)namespace.getStore( new Scope(XUri.SEP+store) );
                if( existsRepairModeToken(store) && targetStore != null && targetStore.isInitialized() ) {
                    if( CLEAR_REPAIR_MODE_TOKEN_ON_INIT ) {
                        removeRepairModeToken( store );
                    }
                    else {
                        // set repair mode for target store if repair mode token exists
                        try {
                            targetStore.setRepairMode( ON_OPEN_TA_WAIT, ON_OPEN_TA_WAIT_TIMEOUT );
                        }
                        catch( XException x ) {
                            throw new ServiceInitializationFailedException( this, x );
                        }
                    }
                }
            }
            
            // Clean-up folders of disabled stores disabled stores
            File[] dl = f.listFiles();
            for( int j = 0; j < dl.length; j++ ) {
                File d = dl[j];
                if( d.isDirectory() && d.exists() && !sl.contains(d.getName()) ) {
                    File[] fl = d.listFiles();
                    for( int k = 0; k < fl.length; k++ )
                        fl[k].delete();
                    d.delete();
                }
            }
        }
        catch( XException x ) {
            throw new ServiceInitializationFailedException( this, x );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "initialize" );
    }
    
    /**
     * Remove the repair-mode token.
     *
     * @param store the Store
     */
    public void removeRepairModeToken( String store ) {
        File rmToken = new File(
            rootpath+File.separator+store+File.separator+XRepairModeToken.REPAIR_MODE_TOKEN );
        if( rmToken.exists() )
            rmToken.delete();
    }
    
    /**
     * Create a new revision
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @param revisionContent Node revision content
     * @exception ServiceAccessException
     * @exception RevisionAlreadyExistException
     */
    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} );
        
        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        XUri xuri = new XUri( uriRel );
        String store = getStoreName(xuri);
        
        if( XStartRepairerCommand.START_REPAIRER.equalsIgnoreCase(xuri.lastSegment()) ) {
            // Save the bytes to allow multiple reads
            revisionContent.getContentBytes();
            
            XStartRepairerCommand startCmd = null;
            try {
                startCmd = new XStartRepairerCommand( domain, namespace.getName(), store, revisionContent.streamContent() );
            }
            catch( IOException x ) {
                x.printStackTrace();
                throw new ServiceAccessException( this, new ConflictException(uriStr, x) );
            }
            catch( JDOMException x ) {
                x.printStackTrace();
                throw new ServiceAccessException(this, new ConflictException(uriStr, x));
            }
            
            if( logger.isLoggable(Level.FINE) )
                logger.fine( "Starting Repairer on "+startCmd.store+" ...", new Object[]{startCmd.url, new Boolean(startCmd.checkOnly)} );
            new XRepairerThread(startCmd, user, pwd, ns).start();
            
            //this comes last because XFileStore has no TA handling
            if( XStartRepairerCommand.isPersistent() ) {
                super.createRevisionContent( uri, revisionDescriptor, revisionContent );
            }
            else {
                clearCaches();
            }
            
        }
        else if( XRepairModeToken.REPAIR_MODE_TOKEN.equalsIgnoreCase(xuri.lastSegment()) ) {
            // Save the bytes to allow multiple reads
            revisionContent.getContentBytes();
            
            XRepairModeToken rmToken = null;
            try {
                rmToken = new XRepairModeToken( revisionContent.streamContent() );
            }
            catch( IOException x ) {
                x.printStackTrace();
                throw new ServiceAccessException( this, new ConflictException(uriStr, x) );
            }
            catch( JDOMException x ) {
                x.printStackTrace();
                throw new ServiceAccessException(this, new ConflictException(uriStr, x));
            }
            try {
                XStoreGroup group = domain.getNamespace(namespace.getName()).getStoreGroup(getStoreName(xuri));
                XParentStore[] ps = group.getParentStores(namespace);
                for (int i = 0; i < ps.length; i++) {
                    ps[i].setRepairMode( rmToken.getOnOpenTA(), rmToken.getWaitTimeout());
                }
            }
            catch( XConflictException x ) {
                x.printStackTrace();
                throw new ServiceAccessException( this, new ConflictException(uriStr, x) );
            }
            catch( XException x ) {
                throw new ServiceAccessException( this, x );
            }
            
            // this comes last because XFileStore has no TA handling
            if( XRepairModeToken.isPersistent() ) {
                super.createRevisionContent( uri, revisionDescriptor, revisionContent );
            }
            else {
                clearCaches();
            }
        }
        else {
            super.createRevisionContent( uri, revisionDescriptor, revisionContent );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createRevisionContent" );
    }
    
    /**
     * Remove revision.
     *
     * @param uri Uri
     * @param revisionDescriptor revision descriptor
     * @exception ServiceAccessException
     */
    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)} );
        
        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        XUri uh = new XUri( uriRel );
        
        if( XStartRepairerCommand.START_REPAIRER.equalsIgnoreCase(uh.lastSegment()) ) {
            if( XStartRepairerCommand.isPersistent() ) {
                super.removeRevisionContent( uri, revisionDescriptor );
            }
        }
        else if( XRepairModeToken.REPAIR_MODE_TOKEN.equalsIgnoreCase(uh.lastSegment()) ) {
            
            try {
                XStoreGroup group = domain.getNamespace(namespace.getName()).getStoreGroup(getStoreName(uh));
                XParentStore[] ps = group.getParentStores(namespace);
                for (int i = 0; i < ps.length; i++) {
                    ps[i].releaseRepairMode();
                }
            }
            catch( XException x ) {
                //should never occur
                throw new ServiceAccessException( this, new ConflictException(uriStr, x) );
            }
            
            // this comes last because XFileStore has no TA handling
            if( XRepairModeToken.isPersistent() ) {
                super.removeRevisionContent( uri, revisionDescriptor );
            }
        }
        else {
            super.removeRevisionContent( uri, revisionDescriptor );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeRevisionContent" );
    }
    
    /**
     * Create new revision descriptor.
     *
     * @param uri Uri
     * @param revisionDescriptor Node revision descriptor
     * @exception ServiceAccessException Service access error
     */
    public void createRevisionDescriptor
        (Uri uri, NodeRevisionDescriptor revisionDescriptor) throws ServiceAccessException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "createRevisionDescriptor", new Object[] {uri, (revisionDescriptor!=null
                                                                                                                ? revisionDescriptor.getRevisionNumber()+"@"+revisionDescriptor.getBranchName()
                                                                                                                : null)} );
        
        String uriRel = uri.getRelative();
        XUri uh = new XUri( uriRel );
        
        if( XStartRepairerCommand.START_REPAIRER.equalsIgnoreCase(uh.lastSegment()) ) {
            if( XStartRepairerCommand.isPersistent() ) {
                super.createRevisionDescriptor( uri, revisionDescriptor );
            }
        }
        else if( XRepairModeToken.REPAIR_MODE_TOKEN.equalsIgnoreCase(uh.lastSegment()) ) {
            if( XRepairModeToken.isPersistent() ) {
                super.createRevisionDescriptor( uri, revisionDescriptor );
            }
        }
        else {
            super.createRevisionDescriptor( uri, revisionDescriptor );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "createRevisionDescriptor" );
    }
    
    /**
     * Update revision descriptors.
     *
     * @param uri Uri
     * @param revisionDescriptors Node revision descriptors
     * @exception ServiceAccessException Service access error
     * @exception RevisionDescriptorNotFoundException Revision descriptor
     * was not found
     */
    public void storeRevisionDescriptors
        (Uri uri, NodeRevisionDescriptors revisionDescriptors)
        throws ServiceAccessException, RevisionDescriptorNotFoundException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME,
                                                            "storeRevisionDescriptors", new Object[] {uri,
                            (revisionDescriptors!=null ? revisionDescriptors.getUri() : null)} );
        
        String uriRel = uri.getRelative();
        XUri uh = new XUri( uriRel );
        
        if( XStartRepairerCommand.START_REPAIRER.equalsIgnoreCase(uh.lastSegment()) ) {
            if( XStartRepairerCommand.isPersistent() ) {
                super.storeRevisionDescriptors( uri, revisionDescriptors );
            }
        }
        else if( XRepairModeToken.REPAIR_MODE_TOKEN.equalsIgnoreCase(uh.lastSegment()) ) {
            if( XRepairModeToken.isPersistent() ) {
                super.storeRevisionDescriptors( uri, revisionDescriptors );
            }
        }
        else {
            super.storeRevisionDescriptors( uri, revisionDescriptors );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME,
                                                           "storeRevisionDescriptors" );
    }
    
    /**
     ** Get the store name (=folder name).
     ** @return     the store name
     **/
    public static String getStoreName(XUri uri) {
        if( uri.isFolder() || (uri.size() == 2 && uri.isXmlFile())) {
            return uri.firstSegment();
        } else {
            return null;
        }
    }
    
    /**
     ** Start repairer command.
     **
     ** @author    peter.nevermann@softwareag.com
     ** @version   $Revision: 1.3 $
     **/
    static class XStartRepairerCommand {
        
        private final static String START_REPAIRER = "StartRepairer.xml";
        
        private final static String URL          = "url";
        private final static String PARAMETER    = "parameter";
        private final static String NAME         = "name";
        private final static String CHECK_ONLY   = "checkOnly";
        private final static String ON_OPEN_TA   = "onOpenTA";
        private final static String WAIT_TIMEOUT = "waitTimeout";
        private final static String REP_FALSE    = "false";
        private final static String WAIT         = "wait";
        private final static String ERROR        = "error";
        private final static String ROLLBACK     = "rollback";
        private final static boolean PERSISTENT  = true;
        
        private Document doc = null;
        private String url = null;
        private String store = null;
        private boolean checkOnly = true;
        private int onOpenTA = 0;
        private long waitTimeout = ON_OPEN_TA_WAIT_TIMEOUT; //default: 60 sec
        
        
        /**
         * Test for persistence.
         * @return true, if the start token is persistent.
         */
        static boolean isPersistent() {
            return PERSISTENT;
        }
        
        /**
         * Default constructor
         * @param   store the store
         * @param   in    the input stream
         */
        XStartRepairerCommand( XDomain domain, String namespace, String store, InputStream in ) throws JDOMException, IOException {
            
            try {
                String taminoDbUrl = domain.getNamespace(namespace).getStore(store).getTaminoDbUrl();
                String taminoCollection = domain.getNamespace(namespace).getStore(store).getTaminoCollection();
                TConnection myTcon = TConnectionFactory.getInstance().newConnection(taminoDbUrl);
                MetaDataURLAccessor mdua = new MetaDataURLAccessor( myTcon );
                url = mdua.readWebdavURL( taminoCollection );
            } catch (Exception e) {}
            
            this.store = store;
            SAXBuilder sax = new SAXBuilder();
            doc = sax.build( in );
            Element root = doc.getRootElement();
            Iterator i = root.getChildren(PARAMETER).iterator();
            while( i.hasNext() ) {
                Element p = (Element)i.next();
                if( CHECK_ONLY.equals(p.getAttributeValue(NAME)) ) {
                    checkOnly = !REP_FALSE.equalsIgnoreCase( p.getText() );
                }
                else if( ON_OPEN_TA.equals(p.getAttributeValue(NAME)) ) {
                    if( WAIT.equalsIgnoreCase(p.getText()) )
                        onOpenTA = ON_OPEN_TA_WAIT;
                    else if( ERROR.equalsIgnoreCase(p.getText()) )
                        onOpenTA = ON_OPEN_TA_ERROR;
                    else if( ROLLBACK.equalsIgnoreCase(p.getText()) )
                        onOpenTA = ON_OPEN_TA_ROLLBACK;
                }
                else if( WAIT_TIMEOUT.equals(p.getAttributeValue(NAME)) ) {
                    try {
                        long wt = (new Long(p.getText())).longValue();
                        waitTimeout = wt;
                    }
                    catch( NumberFormatException x ) {}
                }
            }
        }
    }
    
    /**
     ** Start repairer command.
     **
     ** @author    peter.nevermann@softwareag.com
     ** @version   $Revision: 1.3 $
     **/
    static class XRepairerThread extends Thread {
        
        /** start repairer command */
        final XStartRepairerCommand start;
        final String user;
        final String pwd;
        final String namespace;
        
        /**
         * Default constructor
         */
        XRepairerThread(XStartRepairerCommand start, String user, String pwd, String namespace) {
            this.start = start;
            this.user = user;
            this.pwd = pwd;
            this.namespace = namespace;
        }
        
        /**
         * The run method of this thread.
         */
        public void run() {
            try {
                new RepairHandler(namespace, Catalina.create(), start.url, user, pwd, false, RepairHandler.DEFAULT_NAMING_CLASS, null).repair(
                    start.store,
                    new OnOpenTaToken(start.onOpenTA,start.waitTimeout),
                    start.checkOnly
                );
            }
            catch( Abort x) {
                x.printStackTrace();
            }
            catch( XException x ) {
                x.printStackTrace();
            }
        }
    }
}



