
package org.mule.module.jira.adapters;

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.config.PoolingProfile;
import org.mule.module.jira.JiraConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


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

    /**
     * 
     */
    private String connectionUser;
    /**
     * 
     */
    private String connectionPassword;
    /**
     * 
     */
    private String connectionAddress;
    private static Logger logger = LoggerFactory.getLogger(JiraConnectorConnectionManager.class);
    /**
     * Mule Context
     * 
     */
    private MuleContext muleContext;
    /**
     * Flow construct
     * 
     */
    private FlowConstruct flowConstruct;
    /**
     * Connector Pool
     * 
     */
    private GenericKeyedObjectPool connectionPool;
    protected PoolingProfile connectionPoolingProfile;

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

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

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

    /**
     * Retrieves connectionUser
     * 
     */
    public String getConnectionUser() {
        return this.connectionUser;
    }

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

    /**
     * Retrieves connectionPassword
     * 
     */
    public String getConnectionPassword() {
        return this.connectionPassword;
    }

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

    /**
     * Retrieves connectionAddress
     * 
     */
    public String getConnectionAddress() {
        return this.connectionAddress;
    }

    /**
     * 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 JiraConnectorConnectionManager.ConnectionFactory(this), config);
    }

    public JiraConnectorLifecycleAdapter acquireConnection(JiraConnectorConnectionManager.ConnectionKey key)
        throws Exception
    {
        return ((JiraConnectorLifecycleAdapter) connectionPool.borrowObject(key));
    }

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

    public void destroyConnection(JiraConnectorConnectionManager.ConnectionKey key, JiraConnectorLifecycleAdapter 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 JiraConnectorConnectionManager connectionManager;

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

        public Object makeObject(Object key)
            throws Exception
        {
            if (!(key instanceof JiraConnectorConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            JiraConnectorLifecycleAdapter connector = new JiraConnectorLifecycleAdapter();
            if (connector instanceof Initialisable) {
                connector.initialise();
            }
            if (connector instanceof Startable) {
                connector.start();
            }
            return connector;
        }

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

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

        public void activateObject(Object key, Object obj)
            throws Exception
        {
            if (!(key instanceof JiraConnectorConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            if (!(obj instanceof JiraConnectorLifecycleAdapter)) {
                throw new RuntimeException("Invalid connector type");
            }
            try {
                if (!((JiraConnectorLifecycleAdapter) obj).validateConnection()) {
                    ((JiraConnectorLifecycleAdapter) obj).connect(((JiraConnectorConnectionManager.ConnectionKey) key).getConnectionUser(), ((JiraConnectorConnectionManager.ConnectionKey) key).getConnectionPassword(), ((JiraConnectorConnectionManager.ConnectionKey) key).getConnectionAddress());
                }
            } 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 connectionUser;
        /**
         * 
         */
        private String connectionPassword;
        /**
         * 
         */
        private String connectionAddress;

        public ConnectionKey(String connectionUser, String connectionPassword, String connectionAddress) {
            this.connectionUser = connectionUser;
            this.connectionPassword = connectionPassword;
            this.connectionAddress = connectionAddress;
        }

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

        /**
         * Retrieves connectionUser
         * 
         */
        public String getConnectionUser() {
            return this.connectionUser;
        }

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

        /**
         * Retrieves connectionPassword
         * 
         */
        public String getConnectionPassword() {
            return this.connectionPassword;
        }

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

        /**
         * Retrieves connectionAddress
         * 
         */
        public String getConnectionAddress() {
            return this.connectionAddress;
        }

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

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

    }

}
