/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/XConnectionPool.java,v 1.6 2005/01/19 15:19:01 pnever Exp $
 * $Revision: 1.6 $
 * $Date: 2005/01/19 15:19:01 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.slide.store.tamino.datastore;

import com.softwareag.common.instrumentation.logging.Level;
import com.softwareag.common.instrumentation.logging.Logger;
import com.softwareag.common.instrumentation.logging.LoggerFactory;
import com.softwareag.common.instrumentation.logging.LoggerUtil;
import com.softwareag.tamino.db.api.common.TException;
import com.softwareag.tamino.db.api.connection.TConnection;
import com.softwareag.tamino.db.api.connection.TConnectionFactory;
import com.softwareag.tamino.db.api.connection.TConnectionPoolDescriptor;
import com.softwareag.tamino.db.api.connection.TConnectionPoolManager;
import com.softwareag.tamino.db.api.connection.TIsolationDegree;
import com.softwareag.tamino.db.api.connection.TLockwaitMode;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.slide.store.tamino.common.XDatastoreException;
import org.apache.slide.util.ClassName;
import org.apache.slide.util.XAssertionFailed;
import org.apache.slide.common.Domain;
import com.softwareag.tamino.db.api.connection.TConnectionNotAvailableException;

/**
 * Holds a bunch of XConnections using TConnectionPool. If a connection
 * is not available (Tamino down), several attempts are done to connect
 * (adjustable in domain.xml).
 *
 * @author martin.wallmer@softwareag.com
 *
 * @version $Revision: 1.6 $
 */
public class XConnectionPool {
    
    private static final String LOGNAME = LoggerUtil.getThisClassName();
    private static final String CLASSNAME = new ClassName(LOGNAME).getPlainName();
    private static Logger logger = LoggerFactory.getLogger(LOGNAME);
    
    /** connection pool manager */
    private TConnectionPoolManager theCpManager;
    
    /** the pool name */
    private String poolName;
    
    /** number of tries if DB not available */
    private int tryConnect = 1;
    
    /** base delay for retry */
    private int tryConnectDelay = 2000;
    
    /** use TConnectionPool? */
    private boolean useTConnectionPool = true;
    
    /** holds current active xconnections. Key is url, db, user and threadname */
    private Map xConnections = Collections.synchronizedMap (new HashMap());
    
    /** singleton instance of XConnectionPool */
    private static XConnectionPool theOneAndOnly = new XConnectionPool ();
    
    /** cache for failed connections */
    private Map failedConnections = new HashMap();
    
    /**
     * Method getInstance
     *
     * @return   the singleton XConnectionPool
     *
     */
    public static XConnectionPool getInstance () {
        return theOneAndOnly;
    }
    
    /**
     * Release the Tamino connection pool
     *
     */
    public static void closeConnectionPool () {
        if (theOneAndOnly == null) throw new XAssertionFailed ("not initialized");
        theOneAndOnly.close();
    }
    /**
     * private constructor
     */
    private XConnectionPool () {
        if (useTConnectionPool) {
            try {
                theCpManager = TConnectionPoolManager.getInstance();
            }
            catch (TConnectionNotAvailableException e) {
                throw new IllegalStateException(e.getMessage());
            }
        }
    }
    
    
    /**
     * Method close
     *
     */
    private void close () {
        if (useTConnectionPool)
            theCpManager.release();
    }
    
    
    
    /**
     * creates a TConnectionPool for a specific tamino database and adds it to
     * the Map tConnectionPools.
     *
     * @param    slide    used to get databaseUri and other (global) parameters
     *                    from Domain.xml
     *
     */
    private void createTConnectionPool (XConnectionKey conKey) throws TException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "createTConnectionPool", new Object[] {conKey} );
        
        String poolName = conKey.getTPoolName();
        
        // should be set from outside via setX...() as attributes
        // of XConnectionPool???
        int maxConnection  = 200;
        int initConnection = 10;
        int connTimeout    = 10;
        
        if (useTConnectionPool) {
            
            TConnectionPoolDescriptor descriptor = new TConnectionPoolDescriptor();
            descriptor.setDatabaseURI (conKey.getDatabaseUri() );
            String mydomain = conKey.getDomain();
            descriptor.setUser (conKey.getUser());
            if (mydomain != null && ("".equals (mydomain) == false)) descriptor.setDomain(conKey.getDomain());
            else {
                if (conKey.getUser() != null) descriptor.setDomain("");
            }
            descriptor.setPassword (conKey.getPwd());
            descriptor.setInitConnections (initConnection);
            descriptor.setMaxConnections (maxConnection);
            descriptor.setTimeOut  (connTimeout);
            descriptor.setIsolationDegree (TIsolationDegree.COMMITTED_COMMAND);
            //descriptor.setNonActivityTimeout(30);
            descriptor.setLockwaitMode (TLockwaitMode.YES);
            
            
            theCpManager.addConnectionPool (poolName, descriptor);
        }
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createTConnectionPool" );
    }
    
    /**
     * Method createXConnection. It is in the responsibility of the caller to
     * close the XConnection after use!
     *
     * @param    databaseUri         a  String
     * @param    user                a  String
     * @param    pwd                 a  String
     *
     * @return   a XConnection
     *
     * @throws   XDatastoreException
     *
     */
    private XConnection createXConnection (XConnectionKey conKey)
        throws XDatastoreException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "createXConnection", new Object [] {conKey});
        
        TConnection tCon;
        int invalidTries = 0;
        TException tException;
        
        //        TPreference pref = TPreference.getInstance();
        //        pref.setUseRetryHandler (false);
        
        while (true) {
            try {
                if (useTConnectionPool) {
                    String poolName = conKey.getTPoolName();
                    if (!theCpManager.hasPool (poolName)) {
                        createTConnectionPool (conKey);
                    }
                    tCon = theCpManager.getConnection (poolName);
                }
                else {
                    TConnectionFactory connectionFactory   = TConnectionFactory.getInstance();
                    String domain = conKey.getDomain();
                    if (domain != null && ("".equals (domain) == false)) {
                        tCon =  connectionFactory.newConnection (conKey.getDatabaseUri(),
                                                                 domain,
                                                                 conKey.getUser(),
                                                                 conKey.getPwd());
                    }
                    else {
                        tCon =  connectionFactory.newConnection (conKey.getDatabaseUri(),
                                                                 conKey.getUser(),
                                                                 conKey.getPwd());
                    }
                }
                break;
            }
            catch (TException e) {
                // e.printStackTrace();
                if (e.hasCause())
                    tException = e.getRootTCause();
                else
                    tException = e;
                
                if (++invalidTries == tryConnect) {
                    failedConnections.put(
                        conKey.getDatabaseUri(), new FailedConnection(System.currentTimeMillis()+FailedConnection.EXPIRE, tException.getMessage()) );
                    throw new XDatastoreException ("Error getting connection for " + conKey, tException);
                }
                    
                else {
                    try {
                        int delay = tryConnectDelay * invalidTries;
                        logger.info ("Try " + invalidTries + " of " + tryConnect + " attempts to connect to " + poolName + " failed");
                        logger.info ("Now sleep for " + delay + " millseconds and try again");
                        Thread.sleep (delay);
                    } catch (InterruptedException ei) {}
                }
            }
        }
        
        XConnection xCon = new XConnection (tCon);
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "createXConnection", xCon);
        
        return xCon;
    }
    
    
    /**
     * Method getXConnection
     *
     * @throws   XDatastoreException
     *
     */
    public XConnection getXConnection (XConnectionKey conKey) throws XDatastoreException {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "getXConnection", new Object [] {conKey});
        
        XConnection xCon = null;
        
        synchronized (xConnections) {
            if (xConnections.containsKey (conKey)) {
                xCon = (XConnection)xConnections.get (conKey);
                
                if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                    System.out.println("   === ["+Thread.currentThread().getName()+"] -----REUSE  "+conKey);
                }
                if (logger.isLoggable (Level.FINE)) logger.fine ("use existing xConnection: " + xCon);
            }
            else {
                FailedConnection fcon = (FailedConnection)failedConnections.get(conKey.getDatabaseUri());
                if( fcon != null && System.currentTimeMillis() > fcon.expires ) {
                    failedConnections.remove(conKey.getDatabaseUri());
                    fcon = null;
                }
                if( fcon == null ) {
                    xCon = createXConnection (conKey);
                    
                    xCon.setPoolId (conKey);
                    xConnections.put (conKey, xCon);
                    
                    if ("true".equalsIgnoreCase(Domain.getParameter("debug_tamino", "false"))) {
                        System.out.println("   === ["+Thread.currentThread().getName()+"] ,,,,,CREATE "+conKey);
                    }
                }
                else {
                    throw new XDatastoreException(fcon.reason);
                }
            }
            if (logger.isLoggable(Level.FINE) )
                logger.exiting( CLASSNAME, "getXConnection", xCon);
        }
        // wam debug only
        // ConnectionChecker.add (xCon);
        return xCon;
    }
    
    /**
     * Method removeXConnection
     *
     * @param    key                 a  XConnectionKey
     *
     */
    void removeXConnection (XConnectionKey key) {
        if( logger.isLoggable(Level.FINE) )
            logger.entering( CLASSNAME, "removeXConnection", new Object [] {key});
        
        synchronized (xConnections) {
            xConnections.remove (key);
        }
        
        if( logger.isLoggable(Level.FINE) )
            logger.exiting( CLASSNAME, "removeXConnection");
    }
    
    
    
    private static class FailedConnection {
        static final long EXPIRE = 20000;
        long expires;
        String reason;
        
        FailedConnection( long expires, String reason ) {
            this.expires = expires;
            this.reason = reason;
        }
    }
}


