/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/store/XChildStore.java,v 1.6 2005/02/25 10:26:42 pnever Exp $
 * $Revision: 1.6 $
 * $Date: 2005/02/25 10:26:42 $
 *
 * ====================================================================
 *
 * 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 org.apache.slide.common.*;
import org.apache.slide.store.tamino.common.*;
import 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.connection.TConnection;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import org.apache.slide.macro.ConflictException;
import org.apache.slide.macro.ForbiddenException;
import org.apache.slide.store.tamino.datastore.metadata.GlobalsAccessor;
import org.apache.slide.store.tamino.store.monitoring.IMonitor;
import org.apache.slide.store.tamino.store.monitoring.IMonitorable;
import org.apache.slide.store.tamino.store.monitoring.Monitor;
import org.apache.slide.store.tamino.tools.stores.XDomainConfig;
import org.apache.slide.store.tamino.tools.stores.XDomainFileHandler;
import org.apache.slide.store.tamino.tools.stores.XNamespaceConfig;
import org.apache.slide.store.tamino.tools.stores.XStore;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XException;
import org.jdom.Element;


/**
 * A store used within an XParentStore.
 *
 * @author    peter.nevermann@softwareag.com
 *
 * @version   $Revision: 1.6 $
 */
public abstract class XChildStore extends AbstractService
    implements ISlideAccessor, IMonitorable, XGlobals {
    
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    
    /** Name of the WAIT_FOR_TLOCK_TIMEOUT parameter */
    private static final String WAIT_FOR_TLOCK_TIMEOUT = "waitForTlockTimeout";
    private static final String TLOCK_ISOLATION_LEVEL  = "tlockIsolationLevel";
    
    /** True, if this store is initialized. */
    protected boolean initialized = false;
    
    /** The parameters (from Domain.xml). */
    private Hashtable parameters = null;
    
    /** The parent store */
    private XParentStore parentStore = null;
    
    /** The namespace access token */
    protected NamespaceAccessToken nsaToken = null;
    
    // monitoring...
    /** The name */
    protected String monName= null;
    /** the parent */
    protected IMonitorable monParent = null;
    /** The list of child monitorables */
    protected List monChildren = new ArrayList();
    /** The monitor */
    protected Monitor monitor = null;
    
    
    /**
     * Return true, if this store is initialized.
     * @return true, if this store is initialized
     */
    public boolean isInitialized() {
        return initialized;
    }
    
    /**
     ** Get the parent store.
     **
     ** @return     return parent store
     **/
    public XParentStore getParentStore() {
        if( parentStore == null )
            parentStore = (XParentStore) namespace.getStore( scope );
        return parentStore;
    }
    
    public XDescriptorsStore getDescriptorsStore() {
        return getParentStore().getDescriptorsStore();
    }
    
    /**
     ** Get the associated namespace.
     **
     ** @return     return the namespace
     **/
    public Namespace getNamespace() {
        return namespace;
    }
    
    /**
     ** Get the associated scope.
     **
     ** @return     return the scope
     **/
    public Scope getScope() {
        return scope;
    }
    
    /**
     ** Get the associated DB handler.
     **
     ** @return     return the DB handler
     **/
    public XDbHandler getDbHandler() {
        XDbHandler dbHandler = getParentStore().getDbHandler();
        return dbHandler;
    }
    
    /**
     * Initializes this child store with a set of parameters.
     * These are:
     * <li>taminoBase (the base of the Tamino URL, e.g. http://myhost/tamino
     * <li>database (the Tamino database, i.e. mydb)
     * <li>collection (the Tamino collection, i.e. mycoll)
     *
     * @param parameters Hashtable containing the parameters' name
     * and associated value
     *
     * @exception   ServiceParameterErrorException
     * @exception   ServiceParameterMissingException
     */
    public void setParameters(Hashtable parameters)
        throws ServiceParameterErrorException, ServiceParameterMissingException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "setParameters", new Object[] {parameters} );
        
        this.parameters = parameters;
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "setParameters" );
    }
    
    /**
     * Connects to this child store.
     * <p>If needed, creates a DB session.
     *
     * @exception ServiceConnectionFailedException
     */
    public synchronized void connect() throws ServiceConnectionFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "connect" );
        
        IDbSession dbSession = null;
        
        try {
            dbSession = getDbHandler().getDbSession();
            if( dbSession == null ) {
                dbSession = getDbHandler().createDbSession();
            }
        }
        catch( XException x ) {
            throw new ServiceConnectionFailedException( this, x );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "connect" );
    }
    
    /**
     * Disconnects from this child store.
     * <p>If there is a remaining DB session, rollback and remove session.
     *
     * @exception ServiceDisconnectionFailedException
     */
    public void disconnect() throws ServiceDisconnectionFailedException {
        if( logger.isLoggable(Level.FINE) ) logger.entering( CLASSNAME, "disconnect" );
        
        IDbSession dbSession = null;
        
        try {
            dbSession = getDbHandler().getDbSession();
            if( dbSession != null ) {
                // rollback and remove db session
                dbSession.rollback();
                getDbHandler().removeDbSession();
                if( logger.isLoggable(Level.FINE) )
                    logger.fine(CLASSNAME, "disconnect", "Removed DB session");
            }
        }
        catch( XException x ) {
            throw new ServiceDisconnectionFailedException( this, x );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "disconnect" );
    }
    
    /**
     * Initializes this child store.
     * <p>If needed, initializes a DB session.
     * @param token namespace access 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)} );
        
        this.nsaToken = token;
        
        if( initialized )
            throw new ServiceInitializationFailedException( this, "Already initialized" );
        
        IDbSession dbSession = null;
        
        
        
        // check if a store specific user has been defined
        initSecurity(token.getName());
        
        try {
            dbSession = getDbHandler().getDbSession();
            
            if( dbSession == null ) {
                dbSession = getDbHandler().createDbSession( );
                dbSession.initialize();
                initialized = true;
            }
        }
        catch( XException x ) {
            if( x instanceof XOldMetadataSchemaVersionException )
                initMetadata( dbSession );
            throw new ServiceInitializationFailedException( this, x );
        }
        
        // do the webdav URL storage
        initMetadata( dbSession );
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "initialize" );
    }
    
    /**
     * Method initMetadata
     *
     * @param    dbSession           an IDbSession
     *
     * @throws   ServiceInitializationFailedException
     *
     */
    private void initMetadata( IDbSession dbSession ) throws ServiceInitializationFailedException {
        String taminoBase = getParameter("taminoBase");
        
        if ( (dbSession instanceof XDbSession) ) {
//            Catalina catalina;
            String context = System.getProperty("webapp.context", "taminowebdavserver");
            
//            try {
//                catalina = Catalina.create();
//            } catch (Abort e) {
//                throw new ServiceInitializationFailedException(this, e);
//            }
            try {
                String host = Domain.getParameter(HOST_SELECTION_NAME, InetAddress.getLocalHost().getHostAddress());
                String port = Domain.getParameter(PORT_SELECTION_NAME, "4000");
                XConnection xcon = ((XDbSession)dbSession).getXConnection();
                TConnection tcon = xcon.getTConnection();
                String taminoDb = getParameter(TAMINO_DATABASE);
                String taminoCollection = getParameter(TAMINO_COLLECTION);
                XDomainFileHandler dfh = XDomainFileHandler.get();
                String dbUrl = XStore.buildTaminoDbUrl( taminoBase, taminoDb );
                
                MetaDataURLAccessor urls = new MetaDataURLAccessor(tcon);
                String oldURL = urls.readWebdavURL( taminoCollection );
                
                if ( (oldURL != null) &&
                        (!oldURL.equals(urls.getDefaultWebdavURL())) &&
                        (!oldURL.equals(urls.getWebdavURL(host, port, context))) &&
                        (!getParameter(FORCE_CONNECT_SELECTION_NAME, "").equals("true")) ) {
                    throw new ServiceInitializationFailedException(
                        this,
                        "WebDAV Server " + oldURL + " is already defined. " + urls.getWebdavURL(host, port, context) + " can not initialize");
                }
                urls.storeWebdavURL(
                    getParameter(TAMINO_COLLECTION), host, port, context);
                
                GlobalsAccessor globAccessor = new GlobalsAccessor( xcon );
                Element taminoGlobals = globAccessor.readGlobalsElm( taminoCollection );
                
                if( taminoGlobals == null ) {
                    Element globalConfigRoot = (Element)dfh.getGlobalConfig().getRootElement().clone();
                    globAccessor.createGlobals( taminoCollection,  globalConfigRoot);
                }
                else if( !dfh.getDomain().getConfig().isCompatible(XDomainConfig.fromXml(taminoGlobals)) ) {
                    throw new ServiceInitializationFailedException(
                        this,
                        "The current DeltaV global parameters "+
                            "are not compatible with the global parameters with which this store was originally configured; "+
                            "See: "+
                            oldURL+"administration/configuration/globals/deltav.xml and "+
                            dbUrl+"/"+META_COLLECTION+"?_xql=/xsv:globals[taminoCollection=\""+taminoCollection+"\"]/configuration" );
                }
            }
            catch (ServiceInitializationFailedException e) {throw e;}
            catch (Exception e) {throw new ServiceInitializationFailedException(this, e);}
        }
    }
    
    /**
     * Method initSecurity
     *
     * @param    dbSession           an IDbSession
     *
     * @throws   ServiceInitializationFailedException
     *
     */
    private void initSecurity(String namespace) throws ServiceInitializationFailedException {
        
        String user = getParameter(TAMINO_USER);
        String passwd = getParameter(TAMINO_PASSWD);
        String domain = null;
        String secureTamino = getParameter("forceSecureTamino");
        String autoCreateSecurity = getParameter("autoCreateSecurity", "true");
        domain = getParameter(TAMINO_DOMAIN);
        String taminoBase = getParameter("taminoBase");
        String database = getParameter("database");
        String taminoFull = taminoBase+"/"+database;
        TConnection taminoConnection = null;
        float taminoVersion = 0;
        XNamespaceConfig config;
        try {
            config = XDomainFileHandler.get().getNamespace(namespace).getConfig();
            XConnectionKey ckey =
                new XConnectionKey (taminoFull, config.getAdminDomain(),
                                    config.getAdminUser(),
                                    config.getAdminPwd(),
                                    "XChildStore");
            
            XConnection xcon = XConnectionPool.getInstance().getXConnection( ckey );
            taminoConnection = xcon.getTConnection();
            String version = taminoConnection.newSystemAccessor().getServerVersion();
            xcon.close();
            
            int index1 = version.indexOf(".");
            int index2 = version.indexOf(".", index1+1);
            taminoVersion = (new Float(version.substring(0,index2))).floatValue();
            
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceInitializationFailedException(this, e);
        }
        // do 3.1 security only for DB >= 3.1 < 4.1 add a check if un-authentificated user in ON
        
        boolean enabled;
        try {
            enabled = XAuthenticator.getNonAuthenticatedUserIsEnabled(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd());
        }
        catch (Exception e) {
            throw new ServiceInitializationFailedException(this, e);
        }
        
        if (!"false".equalsIgnoreCase(autoCreateSecurity)) {
            if ((taminoVersion >= (float)3.1) && (taminoVersion < (float)4.1) && enabled) {
                try {
                    new XSecurity(config, getParameter(TAMINO_BASE),
                                  getParameter(TAMINO_DATABASE),
                                  getParameter(TAMINO_COLLECTION));
                }
                catch (Exception e) {
                    throw new ServiceInitializationFailedException(this, e);
                }
                
                // do 4.1 security only for DB >= 4.1
            } else if (taminoVersion >= (float)4.1) {
                try {
                    // check if a forceSecureTamino has been set. additional checks have to be done
                    if ("true".equalsIgnoreCase (secureTamino)) {
                        if (((XAuthenticator.isTaminoEnabled(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd())) &&
                                 ((XAuthenticator.getAuthenticationType(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd())).equalsIgnoreCase("basic"))) ||
                                ((!(XAuthenticator.isTaminoEnabled(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd()))) &&
                                     (!((XAuthenticator.getAuthenticationType(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd())).equalsIgnoreCase("basic"))))){
                            throw new ServiceInitializationFailedException(this,"untrusted environment");
                        }
                    }
                    
                    // if enviroment is not a not-secure initialize 4.1 security.
                    // A non-secure environment is available if Tamino authentication
                    // parameter is on web server and the HTTP authentication type is basic
                    String test = XAuthenticator.getAuthenticationType(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd());
                    if (!((XAuthenticator.isWebseverEnabled(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd()))
                              && (!((XAuthenticator.getAuthenticationType(taminoBase, database, config.getAdminDomain(), config.getAdminUser(), config.getAdminPwd())).equalsIgnoreCase("basic"))))) {
                        
                        
                        if ((user != null) && (passwd != null)) {
                            new XSecurityMultiUser(config, getParameter(TAMINO_BASE),
                                                   getParameter(TAMINO_DATABASE),
                                                   getParameter(TAMINO_COLLECTION),
                                                   domain,
                                                   user,
                                                   passwd );
                        } else {
                            new XSecurityMultiUser(config, getParameter(TAMINO_BASE),
                                                   getParameter(TAMINO_DATABASE),
                                                   getParameter(TAMINO_COLLECTION));
                        }
                    }
                }
                catch (Exception e){
                    throw new ServiceInitializationFailedException(this, e);
                }
                
            }
        }
    }
    
    /**
     * 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" );
        boolean result = false;
        try{
            result = (getDbHandler().getDbSession() != null);
        }
        catch( XException x ) {
            throw new ServiceAccessException( this, x );
        }
        
        if( logger.isLoggable(Level.FINE) ) logger.exiting( CLASSNAME, "isConnected",
                                                           new Boolean(result) );
        return result;
    }
    
    /**
     * Indicates whether or not the objects managed by this service should be
     * cached. Caching is enabled by default.
     *
     * @return boolean True if results should be cached
     */
    public boolean cacheResults() {
        boolean result = false;
        return result;
    }
    
    /**
     * Get the Descriptors Handler
     */
    protected IDescriptorsHandler getDescriptorsHandler() {
        return parentStore.getDescriptorsStore().descriptorsHandler;
    }
    
    //-------------------------------------------------------------
    // ISlideAccessor interface
    // ------------------------------------------------------------
    
    /**
     * Get the Domain parameter (from Domain.xml) given by name.
     *
     * @param  name    the parameter name
     * @return the parameter value
     */
    public String getParameter( String name ) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getParameter", new Object[] {name} );
        
        String result = (String) parameters.get( name );
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getParameter", result );
        return result;
    }
    /**
     * Get the Domain parameter (from Domain.xml) given by name.
     *
     * @param  name    the parameter name
     * @return the parameter value
     */
    public String getParameter( String name , String defaultValue) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getParameter", new Object[] {name, defaultValue} );
        
        String result = getParameter(name);
        if (result == null) result = defaultValue;
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "getParameter", result );
        return result;
    }
    
    /**
     * Get the associated namespace access token.
     * @return     the namespace access token
     */
    public NamespaceAccessToken getNamespaceAccessToken() {
        return nsaToken;
    }
    
    public String getNamespaceName() {
        return nsaToken.getName();
    }
    
    //-------------------------------------------------------------
    // IMonitorable interface
    // ------------------------------------------------------------
    
    /**
     ** Get the name of this monitorable.
     **
     ** @return the name of this monitorable
     **
     **/
    public String getMonName() {
        return monName;
    }
    
    /**
     ** Get the parent monitorable.
     **
     ** @return the parent monitorable (IMonitorable), null if none
     **
     **/
    public IMonitorable getMonParent() {
        return monParent;
    }
    
    /**
     ** Get the child monitorables.
     **
     ** @return list of child monitorables (IMonitorable), empty list if none
     **
     **/
    public List getMonChildren() {
        return monChildren;
    }
    
    /**
     ** Get the monitor.
     **
     ** @return the monitor to this object
     **
     **/
    public IMonitor getMonitor() {
        return monitor;
    }
    
    /**
     ** Get the value of a registered property.
     ** @param prop property name
     ** @return the value of the indicated property as String
     **
     **/
    public Object getMonProperty( String prop ) {
        return null;
    }
    
    
    /**
     * Return true if the store should also be enlisted for a read operation.
     * @param uri uri
     * @return true if the store should also be enlisted for a read operation
     */
    protected boolean isForceStoreEnlistment(Uri uri) {
        SlideToken token = uri.getToken();
        if (token == null)
            return false;
        return token.isForceStoreEnlistment();
    }
    
    /**
     * Wraps XException with the appropriate ServiceAccessException
     */
    protected ServiceAccessException serviceAccessException(XException e, Uri uri) {
        if (e instanceof XForbiddenException) {
            return new ServiceAccessException (this, new ForbiddenException(uri.toString(), e));
        } else if (e instanceof XConflictException) {
            return new ServiceAccessException (this, new ConflictException(uri.toString(), e));
        } else {
            // XDataStoreException or XException
            return new ServiceAccessException( this, e);
        }
    }
    
    //--
    
    /** helper for initialization */
    private long getTimeout() throws ServiceInitializationFailedException {
        String str;
        long num;
        
        str = getParameter(WAIT_FOR_TLOCK_TIMEOUT);
        if (str == null) {
            return 3000;
        } else {
            try {
                num = Long.parseLong(str);
            } catch (NumberFormatException e) {
                throw new ServiceInitializationFailedException(this, "number expected for parameter '" + WAIT_FOR_TLOCK_TIMEOUT + "': " + str);
            }
            if (num <= 0) {
                throw new ServiceInitializationFailedException(this, "number > 0 expected for parameter '" + WAIT_FOR_TLOCK_TIMEOUT + "': " + str);
            }
            return num;
        }
    }
    
    private int getIsolationLevel() throws ServiceInitializationFailedException {
        String p = getParameter(TLOCK_ISOLATION_LEVEL);
        if (XTLockSettings.S_READ_COMMITTED.equalsIgnoreCase(p)) {
            return XTLockSettings.READ_COMMITTED;
        }
        else {
            return XTLockSettings.SERIALIZABLE;
        }
    }
    
    public XTLockSettings getTLockSettings() throws ServiceInitializationFailedException {
        return new XTLockSettings(getTimeout(), getIsolationLevel());
    }
    
    public String toString() {
        return getMonName();
    }
}




