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

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.io.ByteArrayInputStream;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Vector;
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.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.RevisionNotFoundException;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.macro.ForbiddenException;
import org.apache.slide.store.tamino.common.XConflictException;
import org.apache.slide.store.tamino.common.XForbiddenException;
import org.apache.slide.store.tamino.common.XGlobals;
import org.apache.slide.store.tamino.jdomobjects.XFactory;
import org.apache.slide.store.tamino.store.XMemoryStore;
import org.apache.slide.store.tamino.tools.Env;
import org.apache.slide.structure.ObjectAlreadyExistsException;
import org.apache.slide.structure.ObjectNode;
import org.apache.slide.structure.SubjectNode;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.Configuration;
import org.apache.slide.util.JDom;
import org.apache.slide.util.Strings;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.util.XException;
import org.apache.slide.util.XUri;
import org.apache.tools.ant.util.FileUtils;
import org.xml.sax.InputSource;


/**
 ** <p>Manages the store-specific configuration values (Domain.xml). Basically, this class wraps
 ** a DomainFileHandler to make it accessible as a store. </p>
 **
 ** <p>All public methods of this class are synchronized to deal with concurrent invokations
 ** of this store. It might be possible to dump some of the synchronized modifiers, but since
 ** this class is not important with respect to performance, I didn't try ...</p>
 **
 ** @author    peter.nevermann@softwareag.com
 ** @version   $Revision: 1.3 $
 **
 **/
public class XConfigurationStore extends XMemoryStore 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);
    
    
    /**
     ** The domain file handler. This is a private instance of the DomainFileHander, it's not
     ** the publically shared instance to avoid synchronization issues for the latter.
     **/
    private XDomainFileHandler domainFileHandler = null;
    
    private String defaultNsName;
    /**
     ** Default constructor.
     **/
    public XConfigurationStore() {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "<init>" );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "<init>" );
    }
    
    // Inherited from XMemoryStore
    public synchronized void initialize( NamespaceAccessToken token )
        throws ServiceInitializationFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "initialize",
                                                            new Object[] {(token!=null ? token.getName() : null)} );
        
        defaultNsName = token.getName();
        super.initialize( token );
        
        try {
            boolean validating = !"false".equalsIgnoreCase((String)parameters.get("validating"));
            domainFileHandler = XDomainFileHandler.create(validating);
            initNamespace();
        }
        catch( Exception x ) {
            throw new ServiceInitializationFailedException( this, x );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "initialize" );
    }
    
    // Inherited XMemoryStore
    public synchronized void commit( Xid xid, boolean onePhase ) throws XAException {
        boolean modified;
        
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "commit",
                                                            new Object[] {xid, new Boolean(onePhase)}  );
        
        super.commit( xid, onePhase );
        
        try {
            modified = domainFileHandler.save();
        }
        catch( XException x ) {
            throw new XAException( x.getMessage() );
        }
        if (modified && isAutoreload()) {
            restartWebapp();
        }
        if (logger.isLoggable(Level.FINE)) logger.exiting( CLASSNAME, "commit" );
    }
    
    private static boolean isAutoreload() {
        String autoreload = Configuration.getDefault().getProperty(
            "org.apache.slide.store.tamino.servlet.autoreload", "true" );
        return !"false".equalsIgnoreCase(autoreload);
    }
    
    /** touch web.xml to trigger appl reload **/
    public static void restartWebapp() {
        File webXmlFile = new File(
            Env.get().home+File.separator+XGlobals.WEB_INF+File.separator+XGlobals.WEB_XML );
        FileUtils.newFileUtils().setFileLastModified( webXmlFile, System.currentTimeMillis() );
        System.out.println("\nReloading web application ...");
    }
    
    
    // Inherited from XMemoryStore
    public synchronized void rollback( Xid xid ) throws XAException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "rollback",
                                                            new Object[] {xid}  );
        
        super.rollback( xid );
        
        try {
            domainFileHandler.rollback();
            clearCaches();
            initNamespace();
        }
        catch( Exception x ) {
            throw new XAException( x.getMessage() );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "rollback" );
    }
    
    // Inherited from XMemoryStore
    public synchronized 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) } );
        
        NodeRevisionContent result = new NodeRevisionContent();
        
        try {
            String uriRel = uri.getRelative();
            ConfigurationUri uh = new ConfigurationUri( uriRel );
            
            if( !uh.isValid() || uh.isRoot() || GLOBAL_FOLDER.equals(uh) )
                throw new RevisionNotFoundException( uri.toString(), revisionDescriptor.getRevisionNumber());
            
            // Ignore if content-length <= 0
            if( revisionDescriptor.getContentLength() > 0 ) {
                String xmlcontent;
                if( uh.isStoreConfig() ) {
                    // the plain file name (without .xml suffix) is the store name
                    xmlcontent = JDom.toString(domainFileHandler.getStoreConfig(namespace.getName(), uh.getFileName()));
                }
                else if( GLOBAL_FILE.equals(uh) ) {
                    xmlcontent = JDom.toString(domainFileHandler.getGlobalConfig());
                }
                else {
                    throw new XAssertionFailed();
                }
                // utf-8 is always supported
                try { result.setContent( xmlcontent.getBytes(JDom.UTF_8) ); }
                catch (UnsupportedEncodingException e) {
                    throw new XAssertionFailed(e);
                }
            }
        }
        catch( XException x ) {
            throw new RevisionNotFoundException( String.valueOf(uri), revisionDescriptor.getRevisionNumber() );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "retrieveRevisionContent", result );
        return result;
    }
    
    // Inherited from XMemoryStore
    public synchronized 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} );
        
        try {
            String uriRel = uri.getRelative();
            ConfigurationUri uh = new ConfigurationUri( uriRel );
            
            if( !uh.isValid()  || uh.isRoot() || GLOBAL_FOLDER.equals(uh) )
                throw new ServiceAccessException( this,
                                                 new ForbiddenException(String.valueOf(uri), new XForbiddenException( "Invalid uri: "+uri.toString() )) );
            
            // Ignore if content-length <= 0
            if( revisionDescriptor.getContentLength() > 0 ) {
                if( uh.isStoreConfig() ) {
                    // the plain file name (without .xml suffix) is the store name
                    domainFileHandler.setStoreConfig( defaultNsName, uh.getFileName(),
                                                     new InputSource(new ByteArrayInputStream(revisionContent.getContentBytes())) );
                    // the content length may change
                    String configXml = JDom.toString(domainFileHandler.getStoreConfig(namespace.getName(), uh.getFileName()));
                    try { revisionDescriptor.setContentLength( configXml.getBytes(JDom.UTF_8).length ); }
                    catch (java.io.UnsupportedEncodingException e) {e.printStackTrace();}
                }
                else if( GLOBAL_FILE.equals(uh) ) {
                    domainFileHandler.setGlobalConfig( new InputSource(new ByteArrayInputStream(revisionContent.getContentBytes())) );
                }
            }
        }
        catch( XException x ) {
            throw new ServiceAccessException( this, new ConflictException(String.valueOf(uri), x) );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createRevisionContent" );
    }
    
    // Inherited from XMemoryStore
    public synchronized 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} );
        
        try {
            String uriRel = uri.getRelative();
            ConfigurationUri uh = new ConfigurationUri( uriRel );
            
            if( !uh.isValid()  || uh.isRoot() || GLOBAL_FOLDER.equals(uh) )
                throw new ServiceAccessException( this,
                                                 new ConflictException(String.valueOf(uri), new XConflictException("Invalid uri: "+uri.toString())) );
            
            // Ignore if content-length <= 0
            if( revisionDescriptor.getContentLength() > 0 ) {
                if( uh.isStoreConfig() ) {
                    // the plain file name (without .xml suffix) is the store name
                    domainFileHandler.setStoreConfig(defaultNsName, uh.getFileName(),
                                                     new InputSource(new ByteArrayInputStream(revisionContent.getContentBytes())) );
                    // the content length may change
                    String configXml = JDom.toString(domainFileHandler.getStoreConfig(namespace.getName(), uh.getFileName()));
                    try { revisionDescriptor.setContentLength( configXml.getBytes("utf-8").length ); }
                    catch (java.io.UnsupportedEncodingException e) {e.printStackTrace();}
                }
                else if( GLOBAL_FILE.equals(uh) ) {
                    domainFileHandler.setGlobalConfig( new InputSource(new ByteArrayInputStream(revisionContent.getContentBytes())) );
                }
            }
        }
        catch( XException x ) {
            throw new ServiceAccessException( this, new ConflictException(String.valueOf(uri), x) );
        }
        
        if (logger.isLoggable(Level.FINE))
            logger.exiting( CLASSNAME, "storeRevisionContent" );
    }
    
    // Inherited from XMemoryStore
    public synchronized 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)} );
        
        try {
            String uriRel = uri.getRelative();
            ConfigurationUri uh = new ConfigurationUri( uriRel );
            
            if( !uh.isValid()  || uh.isRoot() || GLOBAL_FOLDER.equals(uh) )
                throw new ServiceAccessException( this,
                                                 new ConflictException(String.valueOf(uri), new XConflictException("Invalid uri: "+uri.toString())) );
            if( uh.isRoot() || GLOBAL_FOLDER.equals(uh) ) {
                throw new ServiceAccessException(
                    this,
                    new ForbiddenException(String.valueOf(uri), new XForbiddenException( "Invalid uri: "+uri.toString() )) );
            }
            else {
                if( uh.isStoreConfig() )
                    domainFileHandler.getDomain().getNamespace(namespace.getName()).removeStoreGroup(uh.getFileName());
                else if( GLOBAL_FILE.equals(uh) )
                    domainFileHandler.clearGlobalConfig();
            }
        }
        catch( XException x ) {
            throw new ServiceAccessException( this, new ConflictException(String.valueOf(uri), x) );
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeRevisionContent" );
    }
    
    /**
     * Create a new object in the Descriptors Store.
     *
     * @param uri Uri of the object we want to create
     * @param object SlideObject
     * @exception ServiceAccessException Error accessing the Descriptors Store
     * @exception ObjectAlreadyExistsException An object already exists
     * at this Uri
     */
    public synchronized void createObject(Uri uri, ObjectNode object)
        throws ServiceAccessException, ObjectAlreadyExistsException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "createObject",
                                                            new Object[] {uri, (object!=null ? object.getUri() : null)} );
        
        String uriStr = uri.toString();
        String uriRel = uri.getRelative();
        ConfigurationUri uh = new ConfigurationUri( uriRel );
        
        if( !uh.isValid()  || uh.isRoot() || GLOBAL_FOLDER.equals(uh) )
            //            throw new ServiceAccessException( this, "The uri '"+uri+"' is invalid in this scope" );
            throw new ServiceAccessException( this,
                                             new ForbiddenException(uriStr, new XForbiddenException( "Invalid uri: "+uriStr )) );
        
        super.createObject( uri, object );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "createObject" );
    }
    
    /**
     * Clear the caches
     */
    private void clearCaches() {
        objects.clear();
        descriptors.clear();
        descriptor.clear();
        content.clear();
        permissions.clear();
        locks.clear();
    }
    
    /**
     * Method initNamespace
     *
     */
    private void initNamespace() throws ServiceAccessException {
        String rUri = scope.toString();
        Vector rCld = new Vector();
        
        try {
            Iterator stit = domainFileHandler.getDomain().getNamespace(namespace.getName()).getPublicStoreGroups().iterator();
            while( stit.hasNext() ) {
                XStoreGroup group = (XStoreGroup)stit.next();
                String stUri = rUri+XUri.SEP+group.name+XUri.XML_SUFF;
                rCld.add( stUri );
                ObjectNode stOn = new SubjectNode( stUri, new Vector(), new Vector() );
                objects.put( stUri, stOn );
                NodeRevisionDescriptors stNrds = XFactory.createNRDs( stUri );
                descriptors.put( stUri, stNrds );
                NodeRevisionDescriptor stNrd = XFactory.createMemberNRD( stUri, "text/xml" );
                
                try {
                    String xmlcontent = JDom.toString(domainFileHandler.getStoreConfig(namespace.getName(), group));
                    stNrd.setContentLength( xmlcontent.getBytes(JDom.UTF_8).length );
                }
                catch( Exception e ) {
                    throw new ServiceAccessException(this, e);
                }
                
                descriptor.put( stUri+"-1.0", stNrd );
            }
        }
        catch (XException e) {
            throw new ServiceAccessException( this, e );
        }
        
        String gcUri = rUri+XUri.SEP+DELTAV_GLOBAL_PARMS_COLNAME;
        String gfUri = gcUri+XUri.SEP+DELTAV_GLOBAL_PARMS_FILENAME+XUri.XML_SUFF;
        ObjectNode gfOn = new SubjectNode( gfUri, new Vector(), new Vector() );
        objects.put( gfUri, gfOn );
        NodeRevisionDescriptors gfNrds = XFactory.createNRDs( gfUri );
        descriptors.put( gfUri, gfNrds );
        NodeRevisionDescriptor gfNrd = XFactory.createMemberNRD( gfUri, "text/xml" );
        
        try {
            String xmlcontent = JDom.toString(domainFileHandler.getGlobalConfig());
            gfNrd.setContentLength( xmlcontent.getBytes(JDom.UTF_8).length );
        }
        catch( Exception e ) {
            throw new ServiceAccessException(this, e);
        }
        
        descriptor.put( gfUri+"-1.0", gfNrd );
        
        Vector gcCld = new Vector();
        gcCld.add( gfUri );
        ObjectNode gcOn = new SubjectNode( gcUri, gcCld, new Vector() );
        objects.put( gcUri, gcOn );
        NodeRevisionDescriptors gcNrds = XFactory.createNRDs( gcUri );
        descriptors.put( gcUri, gcNrds );
        NodeRevisionDescriptor gcNrd = XFactory.createCollectionNRD( gcUri );
        descriptor.put( gcUri+"-1.0", gcNrd );
        
        rCld.add( gcUri );
        ObjectNode rOn = new SubjectNode( rUri, rCld, new Vector() );
        objects.put( rUri, rOn );
        NodeRevisionDescriptors rNrds = XFactory.createNRDs( rUri );
        descriptors.put( rUri, rNrds );
        NodeRevisionDescriptor rNrd = XFactory.createCollectionNRD( rUri );
        descriptor.put( rUri+"-1.0", rNrd );
    }
    
    private static final ConfigurationUri GLOBAL_FOLDER =
        new ConfigurationUri(new String[] { DELTAV_GLOBAL_PARMS_COLNAME } );
    
    private static final ConfigurationUri GLOBAL_FILE =
        new ConfigurationUri(new String[] { DELTAV_GLOBAL_PARMS_COLNAME, DELTAV_GLOBAL_PARMS_FILENAME+XUri.XML_SUFF } );
    
    /**
     ** Uri handler.
     **
     ** @author    peter.nevermann@softwareag.com
     ** @version   $Revision: 1.3 $
     **/
    static class ConfigurationUri extends XUri {
        public ConfigurationUri(String[] segments) {
            super(segments);
        }
        
        public ConfigurationUri(String uri) {
            super(uri);
        }
        
        /**
         ** Check whether the uriRel is valid. Valid uriRels are:
         ** <li>/</li>
         ** <li>/globals/deltav.xml</li>
         ** <li>/&ltsome_file_name&gt.xml</li>
         **
         ** @return     true if the uriRel is valid
         **/
        public boolean isValid() {
            if( GLOBAL_FILE.equals(this) || GLOBAL_FOLDER.equals(this))
                return true;
            if( size() > 1 )
                return false;
            if( size() == 1 && !Strings.endsWithIgnoreCase(firstSegment(), XML_SUFF) )
                return false;
            return true;
        }
        
        /**
         ** Check whether the uriRel is corresponds to the config XML file
         **
         ** @return     true if the uriRel corresponds to config.xml
         **/
        public boolean isStoreConfig() {
            return (isValid() && size() == 1 && !GLOBAL_FOLDER.equals(this));
        }
        
        /**
         ** Get the file name of this uri (without XML suffix).
         **
         ** @return     the file name
         **/
        public String getFileName() {
            return Strings.stripExtension(firstSegment());
        }
    }
}

