
package org.mule.modules.salesforce.adapters;

import java.net.URL;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.mule.api.Capabilities;
import org.mule.api.Capability;
import org.mule.api.ConnectionManager;
import org.mule.api.MuleContext;
import org.mule.api.construct.FlowConstruct;
import org.mule.api.context.MuleContextAware;
import org.mule.api.lifecycle.Disposable;
import org.mule.api.lifecycle.Initialisable;
import org.mule.api.lifecycle.Startable;
import org.mule.api.lifecycle.Stoppable;
import org.mule.api.store.ObjectStore;
import org.mule.config.PoolingProfile;
import org.mule.modules.salesforce.SalesforceModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * A {@code SalesforceModuleConnectionManager} is a wrapper around {@link SalesforceModule } that adds connection management capabilities to the pojo.
 * 
 */
public class SalesforceModuleConnectionManager
    implements Capabilities, ConnectionManager<SalesforceModuleConnectionManager.ConnectionKey, SalesforceModuleInjectionAdapter> , MuleContextAware, Initialisable
{

    /**
     * 
     */
    private String username;
    /**
     * 
     */
    private String password;
    /**
     * 
     */
    private String securityToken;
    private URL url;
    private String proxyHost;
    private int proxyPort;
    private String proxyUsername;
    private String proxyPassword;
    private ObjectStore objectStore;
    private static Logger logger = LoggerFactory.getLogger(SalesforceModuleConnectionManager.class);
    /**
     * Mule Context
     * 
     */
    private MuleContext muleContext;
    /**
     * Flow construct
     * 
     */
    private FlowConstruct flowConstruct;
    /**
     * Connector Pool
     * 
     */
    private GenericKeyedObjectPool connectionPool;
    protected PoolingProfile connectionPoolingProfile;

    /**
     * Sets url
     * 
     * @param value Value to set
     */
    public void setUrl(URL value) {
        this.url = value;
    }

    /**
     * Retrieves url
     * 
     */
    public URL getUrl() {
        return this.url;
    }

    /**
     * Sets proxyHost
     * 
     * @param value Value to set
     */
    public void setProxyHost(String value) {
        this.proxyHost = value;
    }

    /**
     * Retrieves proxyHost
     * 
     */
    public String getProxyHost() {
        return this.proxyHost;
    }

    /**
     * Sets proxyPort
     * 
     * @param value Value to set
     */
    public void setProxyPort(int value) {
        this.proxyPort = value;
    }

    /**
     * Retrieves proxyPort
     * 
     */
    public int getProxyPort() {
        return this.proxyPort;
    }

    /**
     * Sets proxyUsername
     * 
     * @param value Value to set
     */
    public void setProxyUsername(String value) {
        this.proxyUsername = value;
    }

    /**
     * Retrieves proxyUsername
     * 
     */
    public String getProxyUsername() {
        return this.proxyUsername;
    }

    /**
     * Sets proxyPassword
     * 
     * @param value Value to set
     */
    public void setProxyPassword(String value) {
        this.proxyPassword = value;
    }

    /**
     * Retrieves proxyPassword
     * 
     */
    public String getProxyPassword() {
        return this.proxyPassword;
    }

    /**
     * Sets objectStore
     * 
     * @param value Value to set
     */
    public void setObjectStore(ObjectStore value) {
        this.objectStore = value;
    }

    /**
     * Retrieves objectStore
     * 
     */
    public ObjectStore getObjectStore() {
        return this.objectStore;
    }

    /**
     * Sets connectionPoolingProfile
     * 
     * @param value Value to set
     */
    public void setConnectionPoolingProfile(PoolingProfile value) {
        this.connectionPoolingProfile = value;
    }

    /**
     * Retrieves connectionPoolingProfile
     * 
     */
    public PoolingProfile getConnectionPoolingProfile() {
        return this.connectionPoolingProfile;
    }

    /**
     * Sets username
     * 
     * @param value Value to set
     */
    public void setUsername(String value) {
        this.username = value;
    }

    /**
     * Retrieves username
     * 
     */
    public String getUsername() {
        return this.username;
    }

    /**
     * Sets securityToken
     * 
     * @param value Value to set
     */
    public void setSecurityToken(String value) {
        this.securityToken = value;
    }

    /**
     * Retrieves securityToken
     * 
     */
    public String getSecurityToken() {
        return this.securityToken;
    }

    /**
     * Sets password
     * 
     * @param value Value to set
     */
    public void setPassword(String value) {
        this.password = value;
    }

    /**
     * Retrieves password
     * 
     */
    public String getPassword() {
        return this.password;
    }

    /**
     * Sets flow construct
     * 
     * @param flowConstruct Flow construct to set
     */
    public void setFlowConstruct(FlowConstruct flowConstruct) {
        this.flowConstruct = flowConstruct;
    }

    /**
     * Set the Mule context
     * 
     * @param context Mule context to set
     */
    public void setMuleContext(MuleContext context) {
        this.muleContext = context;
    }

    public void initialise() {
        GenericKeyedObjectPool.Config config = new GenericKeyedObjectPool.Config();
        if (connectionPoolingProfile!= null) {
            config.maxIdle = connectionPoolingProfile.getMaxIdle();
            config.maxActive = connectionPoolingProfile.getMaxActive();
            config.maxWait = connectionPoolingProfile.getMaxWait();
            config.whenExhaustedAction = ((byte) connectionPoolingProfile.getExhaustedAction());
        }
        connectionPool = new GenericKeyedObjectPool(new SalesforceModuleConnectionManager.ConnectionFactory(this), config);
    }

    public SalesforceModuleInjectionAdapter acquireConnection(SalesforceModuleConnectionManager.ConnectionKey key)
        throws Exception
    {
        return ((SalesforceModuleInjectionAdapter) connectionPool.borrowObject(key));
    }

    public void releaseConnection(SalesforceModuleConnectionManager.ConnectionKey key, SalesforceModuleInjectionAdapter connection)
        throws Exception
    {
        connectionPool.returnObject(key, connection);
    }

    public void destroyConnection(SalesforceModuleConnectionManager.ConnectionKey key, SalesforceModuleInjectionAdapter connection)
        throws Exception
    {
        connectionPool.invalidateObject(key, connection);
    }

    /**
     * Returns true if this module implements such capability
     * 
     */
    public boolean isCapableOf(Capability capability) {
        if (capability == Capability.LIFECYCLE_CAPABLE) {
            return true;
        }
        if (capability == Capability.CONNECTION_MANAGEMENT_CAPABLE) {
            return true;
        }
        return false;
    }

    private static class ConnectionFactory
        implements KeyedPoolableObjectFactory
    {

        private SalesforceModuleConnectionManager connectionManager;

        public ConnectionFactory(SalesforceModuleConnectionManager connectionManager) {
            this.connectionManager = connectionManager;
        }

        public Object makeObject(Object key)
            throws Exception
        {
            if (!(key instanceof SalesforceModuleConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            SalesforceModuleInjectionAdapter connector = new SalesforceModuleInjectionAdapter();
            connector.setUrl(connectionManager.getUrl());
            connector.setProxyHost(connectionManager.getProxyHost());
            connector.setProxyPort(connectionManager.getProxyPort());
            connector.setProxyUsername(connectionManager.getProxyUsername());
            connector.setProxyPassword(connectionManager.getProxyPassword());
            connector.setObjectStore(connectionManager.getObjectStore());
            if (connector instanceof Initialisable) {
                connector.initialise();
            }
            if (connector instanceof Startable) {
                connector.start();
            }
            if (connector instanceof MuleContextAware) {
                connector.setMuleContext((connectionManager.muleContext));
            }
            return connector;
        }

        public void destroyObject(Object key, Object obj)
            throws Exception
        {
            if (!(key instanceof SalesforceModuleConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            if (!(obj instanceof SalesforceModuleInjectionAdapter)) {
                throw new RuntimeException("Invalid connector type");
            }
            try {
                ((SalesforceModuleInjectionAdapter) obj).destroySession();
            } catch (Exception e) {
                throw e;
            } finally {
                if (((SalesforceModuleInjectionAdapter) obj) instanceof Stoppable) {
                    ((SalesforceModuleInjectionAdapter) obj).stop();
                }
                if (((SalesforceModuleInjectionAdapter) obj) instanceof Disposable) {
                    ((SalesforceModuleInjectionAdapter) obj).dispose();
                }
            }
        }

        public boolean validateObject(Object key, Object obj) {
            if (!(obj instanceof SalesforceModuleInjectionAdapter)) {
                throw new RuntimeException("Invalid connector type");
            }
            try {
                return ((SalesforceModuleInjectionAdapter) obj).isConnected();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                return false;
            }
        }

        public void activateObject(Object key, Object obj)
            throws Exception
        {
            if (!(key instanceof SalesforceModuleConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            if (!(obj instanceof SalesforceModuleInjectionAdapter)) {
                throw new RuntimeException("Invalid connector type");
            }
            try {
                if (!((SalesforceModuleInjectionAdapter) obj).isConnected()) {
                    ((SalesforceModuleInjectionAdapter) obj).connect(((SalesforceModuleConnectionManager.ConnectionKey) key).getUsername(), ((SalesforceModuleConnectionManager.ConnectionKey) key).getPassword(), ((SalesforceModuleConnectionManager.ConnectionKey) key).getSecurityToken());
                }
            } catch (Exception e) {
                throw e;
            }
        }

        public void passivateObject(Object key, Object obj)
            throws Exception
        {
        }

    }


    /**
     * A tuple of connection parameters
     * 
     */
    public static class ConnectionKey {

        /**
         * 
         */
        private String username;
        /**
         * 
         */
        private String password;
        /**
         * 
         */
        private String securityToken;

        public ConnectionKey(String username, String password, String securityToken) {
            this.username = username;
            this.password = password;
            this.securityToken = securityToken;
        }

        /**
         * Sets username
         * 
         * @param value Value to set
         */
        public void setUsername(String value) {
            this.username = value;
        }

        /**
         * Retrieves username
         * 
         */
        public String getUsername() {
            return this.username;
        }

        /**
         * Sets securityToken
         * 
         * @param value Value to set
         */
        public void setSecurityToken(String value) {
            this.securityToken = value;
        }

        /**
         * Retrieves securityToken
         * 
         */
        public String getSecurityToken() {
            return this.securityToken;
        }

        /**
         * Sets password
         * 
         * @param value Value to set
         */
        public void setPassword(String value) {
            this.password = value;
        }

        /**
         * Retrieves password
         * 
         */
        public String getPassword() {
            return this.password;
        }

        public int hashCode() {
            int hash = 1;
            hash = ((hash* 31)+ this.username.hashCode());
            return hash;
        }

        public boolean equals(Object obj) {
            return ((obj instanceof SalesforceModuleConnectionManager.ConnectionKey)&&(this.username == ((SalesforceModuleConnectionManager.ConnectionKey) obj).username));
        }

    }

}
