/*
 * This software is released under a licence similar to the Apache Software Licence.
 * See org.logicalcobwebs.proxool.package.html for details.
 * The latest version is available at http://proxool.sourceforge.net
 */
package org.logicalcobwebs.proxool.admin.jmx;

import org.logicalcobwebs.proxool.resources.ResourceNamesIF;
import org.logicalcobwebs.proxool.ProxoolConstants;
import org.logicalcobwebs.proxool.ConnectionPoolDefinitionIF;
import org.logicalcobwebs.proxool.ProxoolFacade;
import org.logicalcobwebs.proxool.ProxoolException;
import org.logicalcobwebs.proxool.ProxoolListenerIF;
import org.logicalcobwebs.proxool.ConfigurationListenerIF;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.DynamicMBean;
import javax.management.AttributeNotFoundException;
import javax.management.MBeanException;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;
import javax.management.Attribute;
import javax.management.InvalidAttributeValueException;
import javax.management.AttributeList;
import javax.management.MBeanParameterInfo;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationListener;
import javax.management.NotificationFilter;
import javax.management.ListenerNotFoundException;
import javax.management.Notification;
import javax.management.MBeanRegistration;
import javax.management.ObjectName;
import javax.management.MBeanServer;

import java.util.Iterator;
import java.util.ResourceBundle;
import java.util.Properties;
import java.util.StringTokenizer;
import java.text.MessageFormat;

/**
 * JMX DynamicMBean adapter for a Proxool connection pool.<br>
 * See the configuration documentation to learn
 * how to activate a pool for JMX. No programming is necessary to do this.
 * <p>
 * <b>Attributes</b>
 * <ul>
 * <li>alias</li>
 * <li>driverClass</li>
 * <li>driverUrl</li>
 * <li>driverProperties</li>
 * <li>fatalSqlException</li>
 * <li>houseKeepingSleeptime</li>
 * <li>houseKeepingTestSql</li>
 * <li>maximumActiveTime</li>
 * <li>maximumConnectionCount</li>
 * <li>maximumConnectionLifetime</li>
 * <li>minimumConnectionCount</li>
 * <li>maximumNewConnections</li>
 * <li>overloadWithoutRefusalLifetime</li>
 * <li>recentlyStartedThreshold</li>
 * <li>prototypeCount</li>
 * <li>trace</li>
 * <li>verbose</li>
 * </ul>
 * </p>
 * <p>
 * <b>Operations</b>
 * <ul>
 * <li>shutdown</li>
 * </ul>
 * </p>
 * <p>
 * <b>Notifications</b>
 * <ul>
 * <li>{@link #NOTIFICATION_TYPE_DEFINITION_UPDATED}</li>
 * </ul>
 * </p>
 * @version $Revision: 1.16 $, $Date: 2007/06/19 11:33:36 $
 * @author Christian Nedregaard (christian_nedregaard@email.com)
 * @author $Author: billhorsman $ (current maintainer)
 * @since Proxool 0.8
 */
public class ConnectionPoolMBean implements DynamicMBean, MBeanRegistration, NotificationBroadcaster,
        ProxoolListenerIF, ConfigurationListenerIF {
    /**
     * Notification type emitted when the pool definition is updated.
     */
    public static final String NOTIFICATION_TYPE_DEFINITION_UPDATED = "proxool.definitionUpdated";


    private static final Log LOG = LogFactory.getLog(ConnectionPoolMBean.class);
    private static final String CLASS_NAME = ConnectionPoolMBean.class.getName();

    private static final String RECOURCE_NAME_MBEAN_POOL_DESCRIPTION = "mbean.pool.description";
    private static final String RECOURCE_NAME_MBEAN_NOTIFICATION_DESCRIPTION = "mbean.notification.description";
    private static final String RECOURCE_NAME_MBEAN_NOTIFICATION_DEF_UPDATED = "mbean.notification.defUpdated";

    private static final String OPERATION_NAME_SHUTDOWN = "shutdown";

    private static final ResourceBundle ATTRIBUTE_DESCRIPTIONS_RESOURCE = createAttributeDescriptionsResource();
    private static final ResourceBundle JMX_RESOURCE = createJMXResource();

    private static final MBeanNotificationInfo[] NOTIFICATION_INFOS = getNotificationInfos();

    private MBeanInfo mBeanInfo;
    private ConnectionPoolDefinitionIF poolDefinition;
    private Properties poolProperties;
    private long definitionUpdatedSequence;
    private NotificationBroadcasterSupport notificationHelper = new NotificationBroadcasterSupport();
    private boolean active;

    public ConnectionPoolMBean(String alias, Properties poolProperties)
            throws ProxoolException {
        this.poolDefinition = ProxoolFacade
                .getConnectionPoolDefinition(alias);
        this.poolProperties = poolProperties;
        this.mBeanInfo = getDynamicMBeanInfo(this.poolDefinition.getAlias());
        ProxoolFacade.addProxoolListener(this);
        ProxoolFacade.addConfigurationListener(alias, this);
    }

    /**
     * @see javax.management.DynamicMBean#getAttribute(java.lang.String)
     */
    public Object getAttribute(String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException {
        if (attributeName == null) {
            final String message = "Cannot invoke a getter of " + CLASS_NAME + " with null attribute name";
            LOG.error(message);
            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
                    message);
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("Getting attribute " + attributeName + ".");
        }
        return ((Attribute) getAttributes(new String[]{attributeName}).get(0)).getValue();
    }

    /**
     * @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute)
     */
    public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException,
            MBeanException, ReflectionException {
        if (attribute == null) {
            final String message = "Cannot invoke a setter of " + CLASS_NAME + " with null attribute";
            LOG.error(message);
            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"),
                    message);
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug("Setting attribute " + attribute.getName() + ".");
        }
        final AttributeList attributeList = new AttributeList();
        attributeList.add(attribute);
        setAttributes(attributeList);
    }

    /**
     * @see javax.management.DynamicMBean#getAttributes(java.lang.String[])
     */
    public AttributeList getAttributes(String[] attributeNames) {
        if (attributeNames == null) {
            final String message = "Cannot invoke a null getter of " + CLASS_NAME;
            LOG.error(message);
            throw new RuntimeOperationsException(new IllegalArgumentException("attributeNames[] cannot be null"),
                    message);
        }
        AttributeList resultList = new AttributeList();

        // if attributeNames is empty, return an empty result list
        if (attributeNames.length == 0) {
            return resultList;
        }

        // build the result attribute list
        for (int i = 0; i < attributeNames.length; i++) {
            try {
                if (equalsProperty(attributeNames[i], ProxoolConstants.ALIAS)) {
                    resultList.add(new Attribute(attributeNames[i],
                            this.poolDefinition.getAlias()));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.DRIVER_PROPERTIES)) {
                    resultList.add(new Attribute(attributeNames[i],
                            getDelegatePropertiesAsString(this.poolProperties)));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.DRIVER_URL)) {
                    resultList.add(new Attribute(attributeNames[i],
                            this.poolDefinition.getUrl()));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.FATAL_SQL_EXCEPTION)) {
                    resultList.add(new Attribute(attributeNames[i],
                            getValueOrEmpty(this.poolProperties.getProperty(ProxoolConstants.FATAL_SQL_EXCEPTION_PROPERTY))));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.HOUSE_KEEPING_SLEEP_TIME)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Long(this.poolDefinition.getHouseKeepingSleepTime())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.HOUSE_KEEPING_TEST_SQL)) {
                    resultList.add(new Attribute(attributeNames[i],
                            getValueOrEmpty(poolDefinition.getHouseKeepingTestSql())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.TEST_BEFORE_USE)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Boolean(this.poolDefinition.isTestBeforeUse())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.TEST_AFTER_USE)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Boolean(this.poolDefinition.isTestAfterUse())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.MAXIMUM_ACTIVE_TIME)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Long(this.poolDefinition.getMaximumActiveTime())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.MAXIMUM_CONNECTION_COUNT)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Integer(this.poolDefinition.getMaximumConnectionCount())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.MAXIMUM_CONNECTION_LIFETIME)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Long(this.poolDefinition.getMaximumConnectionLifetime())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.MAXIMUM_NEW_CONNECTIONS)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Integer(this.poolDefinition.getMaximumNewConnections())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.SIMULTANEOUS_BUILD_THROTTLE)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Integer(this.poolDefinition.getSimultaneousBuildThrottle())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.MINIMUM_CONNECTION_COUNT)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Integer(this.poolDefinition.getMinimumConnectionCount())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.OVERLOAD_WITHOUT_REFUSAL_LIFETIME)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Long(this.poolDefinition.getOverloadWithoutRefusalLifetime())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.PROTOTYPE_COUNT)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Integer(this.poolDefinition.getPrototypeCount())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.RECENTLY_STARTED_THRESHOLD)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Long(this.poolDefinition.getRecentlyStartedThreshold())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.STATISTICS)) {
                    resultList.add(new Attribute(attributeNames[i],
                            getValueOrEmpty(this.poolDefinition.getStatistics())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.STATISTICS_LOG_LEVEL)) {
                    resultList.add(new Attribute(attributeNames[i],
                            getValueOrEmpty(this.poolDefinition.getStatisticsLogLevel())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.TRACE)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Boolean(this.poolDefinition.isTrace())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.VERBOSE)) {
                    resultList.add(new Attribute(attributeNames[i],
                            new Boolean(this.poolDefinition.isVerbose())));
                } else if (equalsProperty(attributeNames[i], ProxoolConstants.FATAL_SQL_EXCEPTION_WRAPPER_CLASS)) {
                    resultList.add(new Attribute(attributeNames[i],
                            getValueOrEmpty(this.poolDefinition.getFatalSqlExceptionWrapper())));
                } else {
                    final String message = "Unknown attribute: " + attributeNames[i];
                    LOG.error(message);
                    throw new AttributeNotFoundException(message);
                }
            } catch (AttributeNotFoundException e) {
                throw new RuntimeOperationsException(new IllegalArgumentException(e.getMessage()));
            }
        }
        return resultList;
    }

    /**
     * @see javax.management.DynamicMBean#setAttributes(javax.management.AttributeList)
     */
    public AttributeList setAttributes(AttributeList attributes) {

        if (attributes == null) {
            final String message = "AttributeList attributes cannot be null";
            LOG.error(message);
            throw new RuntimeOperationsException(new IllegalArgumentException(message),
                    "Cannot invoke a setter of " + CLASS_NAME);
        }
        AttributeList resultList = new AttributeList();

        if (attributes.isEmpty()) {
            return resultList;
        }

        String name = null;
        Object value = null;
        final Properties newProperties = new Properties();
        Attribute attribute = null;
        for (Iterator i = attributes.iterator(); i.hasNext();) {
            attribute = (Attribute) i.next();
            try {
                name = attribute.getName();
                value = attribute.getValue();

                if (equalsProperty(name, ProxoolConstants.DRIVER_PROPERTIES)) {
                    if (!isEqualProperties(value.toString(), getDelegatePropertiesAsString(this.poolProperties))) {
                        checkAssignable(name, String.class, value);
                        setDelegateProperties(newProperties, value.toString());
                        resultList.add(new Attribute(name, value));
                    }
                } else if (equalsProperty(name, ProxoolConstants.DRIVER_URL)) {
                    checkAssignable(name, String.class, value);
                    if (notEmpty(value)) {
                        newProperties.setProperty(ProxoolConstants.DRIVER_URL_PROPERTY, value.toString());
                    } else {
                        newProperties.setProperty(ProxoolConstants.DRIVER_URL_PROPERTY, "");
                    }
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.FATAL_SQL_EXCEPTION)) {
                    if (!isEqualProperties(value.toString(),
                            this.poolProperties.getProperty(ProxoolConstants.FATAL_SQL_EXCEPTION_PROPERTY))) {
                        checkAssignable(name, String.class, value);
                        if (notEmpty(value)) {
                            newProperties.setProperty(ProxoolConstants.FATAL_SQL_EXCEPTION_PROPERTY, value.toString());
                        } else {
                            newProperties.setProperty(ProxoolConstants.FATAL_SQL_EXCEPTION_PROPERTY, "");
                        }
                        resultList.add(new Attribute(name, value));
                    }
                } else if (equalsProperty(name, ProxoolConstants.HOUSE_KEEPING_SLEEP_TIME)) {
                    setIntegerAttribute(name, ProxoolConstants.HOUSE_KEEPING_SLEEP_TIME_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_HOUSE_KEEPING_SLEEP_TIME, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.HOUSE_KEEPING_TEST_SQL)) {
                    checkAssignable(name, String.class, value);
                    if (notEmpty(value)) {
                        newProperties.setProperty(ProxoolConstants.HOUSE_KEEPING_TEST_SQL_PROPERTY, value.toString());
                    } else {
                        newProperties.setProperty(ProxoolConstants.HOUSE_KEEPING_TEST_SQL_PROPERTY, "");
                    }
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.TEST_BEFORE_USE)) {
                    checkAssignable(name, Boolean.class, value);
                    newProperties.setProperty(ProxoolConstants.TEST_BEFORE_USE_PROPERTY, value.toString());
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.TEST_AFTER_USE)) {
                    checkAssignable(name, Boolean.class, value);
                    newProperties.setProperty(ProxoolConstants.TEST_AFTER_USE_PROPERTY, value.toString());
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.MAXIMUM_ACTIVE_TIME)) {
                    setIntegerAttribute(name, ProxoolConstants.MAXIMUM_ACTIVE_TIME_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_MAXIMUM_ACTIVE_TIME, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.MAXIMUM_CONNECTION_COUNT)) {
                    setIntegerAttribute(name, ProxoolConstants.MAXIMUM_CONNECTION_COUNT_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_MAXIMUM_CONNECTION_COUNT, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.MAXIMUM_CONNECTION_LIFETIME)) {
                    setIntegerAttribute(name, ProxoolConstants.MAXIMUM_CONNECTION_LIFETIME_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_MAXIMUM_CONNECTION_LIFETIME, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.MAXIMUM_NEW_CONNECTIONS)) {
                    setIntegerAttribute(name, ProxoolConstants.MAXIMUM_NEW_CONNECTIONS_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_MAXIMUM_NEW_CONNECTIONS, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.SIMULTANEOUS_BUILD_THROTTLE)) {
                    setIntegerAttribute(name, ProxoolConstants.SIMULTANEOUS_BUILD_THROTTLE_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_SIMULTANEOUS_BUILD_THROTTLE, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.MINIMUM_CONNECTION_COUNT)) {
                    checkAssignable(name, Integer.class, value);
                    newProperties.setProperty(ProxoolConstants.MINIMUM_CONNECTION_COUNT_PROPERTY, value.toString());
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.OVERLOAD_WITHOUT_REFUSAL_LIFETIME)) {
                    setIntegerAttribute(name, ProxoolConstants.OVERLOAD_WITHOUT_REFUSAL_LIFETIME_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_OVERLOAD_WITHOUT_REFUSAL_THRESHOLD, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.PROTOTYPE_COUNT)) {
                    checkAssignable(name, Integer.class, value);
                    newProperties.setProperty(ProxoolConstants.PROTOTYPE_COUNT_PROPERTY, value.toString());
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.RECENTLY_STARTED_THRESHOLD)) {
                    setIntegerAttribute(name, ProxoolConstants.RECENTLY_STARTED_THRESHOLD_PROPERTY, value,
                            ConnectionPoolDefinitionIF.DEFAULT_RECENTLY_STARTED_THRESHOLD, newProperties, resultList);
                } else if (equalsProperty(name, ProxoolConstants.STATISTICS)) {
                    checkAssignable(name, String.class, value);
                    if (notEmpty(value)) {
                        newProperties.setProperty(ProxoolConstants.STATISTICS_PROPERTY, value.toString());
                    } else {
                        newProperties.setProperty(ProxoolConstants.STATISTICS_PROPERTY, "");
                    }
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.STATISTICS_LOG_LEVEL)) {
                    checkAssignable(name, String.class, value);
                    if (notEmpty(value)) {
                        newProperties.setProperty(ProxoolConstants.STATISTICS_LOG_LEVEL_PROPERTY, value.toString());
                    } else {
                        newProperties.setProperty(ProxoolConstants.STATISTICS_LOG_LEVEL_PROPERTY, "");
                    }
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.TRACE)) {
                    checkAssignable(name, Boolean.class, value);
                    newProperties.setProperty(ProxoolConstants.TRACE_PROPERTY, value.toString());
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.VERBOSE)) {
                    checkAssignable(name, Boolean.class, value);
                    newProperties.setProperty(ProxoolConstants.VERBOSE_PROPERTY, value.toString());
                    resultList.add(new Attribute(name, value));
                } else if (equalsProperty(name, ProxoolConstants.FATAL_SQL_EXCEPTION_WRAPPER_CLASS)) {
                    checkAssignable(name, Boolean.class, value);
                    newProperties.setProperty(ProxoolConstants.FATAL_SQL_EXCEPTION_WRAPPER_CLASS_PROPERTY, value.toString());
                    resultList.add(new Attribute(name, value));
                } else {
                    final String message = "Unknown attribute: " + name;
                    LOG.error(message);
                    throw new AttributeNotFoundException(message);
                }
            } catch (InvalidAttributeValueException e) {
                final String message = "Attribute value was illegal: " + e.getMessage();
                LOG.error(message);
                throw new RuntimeOperationsException(new RuntimeException(message));
            } catch (AttributeNotFoundException e) {
                throw new RuntimeOperationsException(new IllegalArgumentException(e.getMessage()));
            }
        }
        try {
            ProxoolFacade.updateConnectionPool(ProxoolConstants.PROPERTY_PREFIX + this.poolDefinition.getAlias(), newProperties);
        } catch (ProxoolException e) {
            LOG.error("Update of Proxool pool failed: ", e);
            throw new RuntimeOperationsException(new RuntimeException(e.getMessage()));
        }
        return resultList;
    }

    /**
     * @see javax.management.DynamicMBean#invoke(java.lang.String, java.lang.Object[], java.lang.String[])
     */
    public Object invoke(String operationName, Object params[], String signature[]) throws MBeanException, ReflectionException {
        if (operationName == null) {
            throw new RuntimeOperationsException(new IllegalArgumentException("Operation name cannot be null"), "Cannot invoke a null operation in " + CLASS_NAME);
        } else if (operationName.equals(OPERATION_NAME_SHUTDOWN)) {
            try {
                ProxoolFacade.removeConnectionPool(this.poolDefinition.getAlias());
            } catch (ProxoolException e) {
                LOG.error("Shutdown of pool " + this.poolDefinition.getAlias() + " failed.", e);
            }
            return null;
        } else {
            throw new ReflectionException(new NoSuchMethodException(operationName),
                    "Cannot find the operation " + operationName + ".");
        }
    }

    /**
     * @see javax.management.DynamicMBean#getMBeanInfo()
     */
    public MBeanInfo getMBeanInfo() {
        return mBeanInfo;
    }

    private MBeanInfo getDynamicMBeanInfo(String alias) {
        final MBeanAttributeInfo[] attributeInfos = new MBeanAttributeInfo[]{
            createProxoolAttribute(ProxoolConstants.ALIAS, String.class, false),
            createProxoolAttribute(ProxoolConstants.DRIVER_PROPERTIES, String.class),
            createProxoolAttribute(ProxoolConstants.DRIVER_URL, String.class),
            createProxoolAttribute(ProxoolConstants.FATAL_SQL_EXCEPTION, String.class),
            createProxoolAttribute(ProxoolConstants.HOUSE_KEEPING_SLEEP_TIME, Integer.class),
            createProxoolAttribute(ProxoolConstants.HOUSE_KEEPING_TEST_SQL, String.class),
            createProxoolAttribute(ProxoolConstants.TEST_BEFORE_USE, Boolean.class),
            createProxoolAttribute(ProxoolConstants.TEST_AFTER_USE, Boolean.class),
            createProxoolAttribute(ProxoolConstants.MAXIMUM_ACTIVE_TIME, Integer.class),
            createProxoolAttribute(ProxoolConstants.MAXIMUM_CONNECTION_COUNT, Integer.class),
            createProxoolAttribute(ProxoolConstants.MAXIMUM_CONNECTION_LIFETIME, Integer.class),
            createProxoolAttribute(ProxoolConstants.SIMULTANEOUS_BUILD_THROTTLE, Integer.class),
            createProxoolAttribute(ProxoolConstants.MINIMUM_CONNECTION_COUNT, Integer.class),
            createProxoolAttribute(ProxoolConstants.OVERLOAD_WITHOUT_REFUSAL_LIFETIME, Integer.class),
            createProxoolAttribute(ProxoolConstants.PROTOTYPE_COUNT, Integer.class),
            createProxoolAttribute(ProxoolConstants.RECENTLY_STARTED_THRESHOLD, Integer.class),
            createProxoolAttribute(ProxoolConstants.STATISTICS, String.class),
            createProxoolAttribute(ProxoolConstants.STATISTICS_LOG_LEVEL, String.class),
            createProxoolAttribute(ProxoolConstants.TRACE, Boolean.class),
            createProxoolAttribute(ProxoolConstants.VERBOSE, Boolean.class),
            createProxoolAttribute(ProxoolConstants.FATAL_SQL_EXCEPTION_WRAPPER_CLASS, String.class),
        };

        final MBeanConstructorInfo[] constructorInfos = new MBeanConstructorInfo[]{
            new MBeanConstructorInfo("ConnectionPoolMBean(): Construct a ConnectionPoolMBean object.", ConnectionPoolMBean.class.getConstructors()[0])
        };

        final MBeanOperationInfo[] operationInfos = new MBeanOperationInfo[]{
            new MBeanOperationInfo(OPERATION_NAME_SHUTDOWN, "Stop and dispose this connection pool.",
                    new MBeanParameterInfo[]{}, "void", MBeanOperationInfo.ACTION)
        };

        return new MBeanInfo(CLASS_NAME, MessageFormat.format(getJMXText(RECOURCE_NAME_MBEAN_POOL_DESCRIPTION),
                new Object[]{alias}), attributeInfos, constructorInfos, operationInfos, new MBeanNotificationInfo[0]);
    }

    private static String getAttributeDescription(String attributeName) {
        String description = "";
        if (ATTRIBUTE_DESCRIPTIONS_RESOURCE != null) {
            try {
                description = ATTRIBUTE_DESCRIPTIONS_RESOURCE.getString(attributeName);
            } catch (Exception e) {
                LOG.warn("Could not get description for attribute '" + attributeName + "' from resource " + ResourceNamesIF.ATTRIBUTE_DESCRIPTIONS + ".");
            }
        }
        return description;
    }

    private static String getJMXText(String key) {
        String value = "";
        if (JMX_RESOURCE != null) {
            try {
                value = JMX_RESOURCE.getString(key);
            } catch (Exception e) {
                LOG.warn("Could not get value for attribute '" + key + "' from resource " + ResourceNamesIF.JMX + ".");
            }
        }
        return value;
    }

    private static ResourceBundle createAttributeDescriptionsResource() {
        try {
            return ResourceBundle.getBundle(ResourceNamesIF.ATTRIBUTE_DESCRIPTIONS);
        } catch (Exception e) {
            LOG.error("Could not find resource " + ResourceNamesIF.ATTRIBUTE_DESCRIPTIONS, e);
        }
        return null;
    }

    private static ResourceBundle createJMXResource() {
        try {
            return ResourceBundle.getBundle(ResourceNamesIF.JMX);
        } catch (Exception e) {
            LOG.error("Could not find resource " + ResourceNamesIF.JMX, e);
        }
        return null;
    }

    private static MBeanAttributeInfo createProxoolAttribute(String attributeName, Class type) {
        return createProxoolAttribute(attributeName, type, true);
    }

    private static MBeanAttributeInfo createProxoolAttribute(String attributeName, Class type, boolean writable) {
        return new MBeanAttributeInfo(ProxoolJMXHelper.getValidIdentifier(attributeName), type.getName(),
                getAttributeDescription(attributeName), true, writable, false);
    }

    private void checkAssignable(String name, Class clazz, Object value) throws InvalidAttributeValueException {
        if (value == null) {
            if (!String.class.equals(clazz)) {
                throw(new InvalidAttributeValueException("Cannot set attribute " + name + " to null "
                        + " an instance of " + clazz.getName() + " expected."));
            }
        } else {
            Class valueClass = value.getClass();
            if (!clazz.isAssignableFrom(valueClass)) {
                throw(new InvalidAttributeValueException("Cannot set attribute " + name + " to a " + valueClass.getName()
                        + " instance, " + clazz.getName() + " expected."));
            }
        }
    }

    private boolean equalsProperty(String beanAttribute, String proxoolProperty) {
        return beanAttribute.equals(ProxoolJMXHelper.getValidIdentifier(proxoolProperty));
    }

    private void setDelegateProperties(Properties properties, String propertyString)
            throws InvalidAttributeValueException {
        if (propertyString == null || propertyString.trim().length() == 0) {
            return;
        }
        StringTokenizer tokenizer = new StringTokenizer(propertyString, ",");
        String keyValuePair = null;
        int equalsIndex = -1;
        while (tokenizer.hasMoreElements()) {
            keyValuePair = tokenizer.nextToken().trim();
            equalsIndex = keyValuePair.indexOf("=");
            if (equalsIndex != -1) {
                properties.put(keyValuePair.substring(0, equalsIndex).trim(),
                        keyValuePair.substring(equalsIndex + 1).trim());
            } else {
                throw new InvalidAttributeValueException("Could not find key/value delimiter '=' in property definition: '"
                        + keyValuePair + "'.");
            }
        }
    }

    private String getDelegatePropertiesAsString(Properties properties) {
        final StringBuffer result = new StringBuffer();
        Iterator keyIterator = properties.keySet().iterator();
        String key = null;
        boolean first = true;
        while (keyIterator.hasNext()) {
            key = (String) keyIterator.next();
            if (!key.startsWith(ProxoolConstants.PROPERTY_PREFIX)) {
                if (!first) {
                    result.append(", ");
                } else {
                    first = false;
                }
                result.append(key).append("=").append(properties.getProperty(key));
            }
        }
        return result.toString();
    }

    private boolean notEmpty(Object object) {
        return object != null && object.toString().trim().length() > 0;
    }

    private boolean notEmptyOrZero(Integer integer) {
        return integer != null && integer.intValue() > 0;
    }

    private String getValueOrEmpty(String property) {
        return property == null ? "" : property;
    }

    private void setIntegerAttribute(String attributeName, String propertyName, Object value, int defaultValue, Properties properties,
                                     AttributeList resultList) throws InvalidAttributeValueException {
        checkAssignable(attributeName, Integer.class, value);
        if (notEmptyOrZero((Integer) value)) {
            properties.setProperty(propertyName, value.toString());
            resultList.add(new Attribute(attributeName, value));
        } else {
            resultList.add(new Attribute(attributeName,
                    new Integer(defaultValue)));
        }
    }

    private boolean isEqualProperties(String property1, String property2) {
        if (property1 == null) {
            return property2 == null;
        } else if (property2 == null) {
            return property1 == null;
        } else {
            return property1.equals(property2);
        }
    }

    private static MBeanNotificationInfo[] getNotificationInfos() {
        return new MBeanNotificationInfo[]{
            new MBeanNotificationInfo(
                    new String[]{NOTIFICATION_TYPE_DEFINITION_UPDATED}, Notification.class.getName(), getJMXText(RECOURCE_NAME_MBEAN_NOTIFICATION_DESCRIPTION))
        };
    }

    // Listener methods

    /**
     * Not used.
     * @see org.logicalcobwebs.proxool.ProxoolListenerIF#onRegistration(org.logicalcobwebs.proxool.ConnectionPoolDefinitionIF, java.util.Properties)
     */
    public void onRegistration(ConnectionPoolDefinitionIF connectionPoolDefinition, Properties completeInfo) {
        // Not used.
    }

    /**
     * If the given alias equals this pools alias: Unregister this JMX bean.
     * @see org.logicalcobwebs.proxool.ProxoolListenerIF#onShutdown(java.lang.String)
     */
    public void onShutdown(String alias) {
        if (alias.equals(this.poolDefinition.getAlias())) {
            if (this.active) {
                this.active = false;
                ProxoolJMXHelper.unregisterPool(this.poolDefinition.getAlias(), this.poolProperties);
                LOG.info(this.poolDefinition.getAlias() + " MBean unregistered.");
            }
        }
    }

    /**
     * Update the attributes of this MBean and emit a {@link #NOTIFICATION_TYPE_DEFINITION_UPDATED} event.
     * @see org.logicalcobwebs.proxool.ConfigurationListenerIF#definitionUpdated(org.logicalcobwebs.proxool.ConnectionPoolDefinitionIF, java.util.Properties, java.util.Properties)
     */
    public void definitionUpdated(ConnectionPoolDefinitionIF connectionPoolDefinition, Properties completeInfo,
                                  Properties changedInfo) {
        this.poolDefinition = connectionPoolDefinition;
        this.poolProperties = completeInfo;
        this.notificationHelper.sendNotification(new Notification(NOTIFICATION_TYPE_DEFINITION_UPDATED, this,
                definitionUpdatedSequence++, System.currentTimeMillis(),
                getJMXText(RECOURCE_NAME_MBEAN_NOTIFICATION_DEF_UPDATED)));
    }

    /**
     * @see javax.management.NotificationBroadcaster#addNotificationListener(javax.management.NotificationListener, javax.management.NotificationFilter, java.lang.Object)
     */
    public void addNotificationListener(NotificationListener notificationListener, NotificationFilter notificationFilter,
                                        Object handBack) throws IllegalArgumentException {
        this.notificationHelper.addNotificationListener(notificationListener, notificationFilter, handBack);
    }

    /**
     * @see javax.management.NotificationBroadcaster#removeNotificationListener(javax.management.NotificationListener)
     */
    public void removeNotificationListener(NotificationListener notificationListener) throws ListenerNotFoundException {
        this.notificationHelper.removeNotificationListener(notificationListener);
    }

    /**
     * @see javax.management.NotificationBroadcaster#getNotificationInfo()
     */
    public MBeanNotificationInfo[] getNotificationInfo() {
        return NOTIFICATION_INFOS;
    }

    /**
     * @see javax.management.MBeanRegistration#preRegister(javax.management.MBeanServer, javax.management.ObjectName)
     */
    public ObjectName preRegister(MBeanServer mBeanServer, ObjectName objectName) throws Exception {
        if (objectName == null) {
            throw new ProxoolException("objectName was null, but we can not construct an MBean instance without knowing"
                    + " the pool alias.");
        }
        return objectName;
    }

    /**
     * @see javax.management.MBeanRegistration#postRegister(java.lang.Boolean)
     */
    public void postRegister(Boolean success) {
        if (success.booleanValue() == true) {
            this.active = true;
        }
    }

    /**
     * @see javax.management.MBeanRegistration#preDeregister()
     */
    public void preDeregister() throws Exception {
        this.active = false;
    }

    /**
     * @see javax.management.MBeanRegistration#postDeregister()
     */
    public void postDeregister() {
    }
}

/*
 Revision history:
 $Log: ConnectionPoolMBean.java,v $
 Revision 1.16  2007/06/19 11:33:36  billhorsman
 Changed time (millisecond) properties from int to long: maximumConnectionLifetime, houseKeepingSleepTime, recentlyStartedThreshold, overloadWithoutRefusalLifetime, maximumActiveTime

 Revision 1.15  2006/01/18 14:39:55  billhorsman
 Unbundled Jakarta's Commons Logging.

 Revision 1.14  2003/10/20 07:37:07  chr32
 Bettered handling of empty values. Now not setting attributes that has not changed.

 Revision 1.13  2003/09/30 18:38:27  billhorsman
 New properties

 Revision 1.12  2003/09/29 17:48:08  billhorsman
 New fatal-sql-exception-wrapper-class allows you to define what exception is used as a wrapper. This means that you
 can make it a RuntimeException if you need to.

 Revision 1.11  2003/09/14 21:29:31  chr32
 Added support for wrap-fatal-sql-exceptions,statistics and statistics-log-level properties.

 Revision 1.10  2003/09/10 22:21:04  chr32
 Removing > jdk 1.2 dependencies.

 Revision 1.9  2003/05/06 23:15:55  chr32
 Moving JMX classes back in from sandbox.

 Revision 1.1  2003/03/07 16:35:17  billhorsman
 moved jmx stuff into sandbox until it is tested

 Revision 1.7  2003/03/05 23:28:56  billhorsman
 deprecated maximum-new-connections property in favour of
 more descriptive simultaneous-build-throttle

 Revision 1.6  2003/03/03 11:11:59  billhorsman
 fixed licence

 Revision 1.5  2003/02/26 19:04:30  chr32
 Added active/inactive state check.

 Revision 1.4  2003/02/26 16:37:48  billhorsman
 fixed spelling in ConfigurationListenerIF

 Revision 1.3  2003/02/25 16:50:31  chr32
 Added JMX notification and doc.

 Revision 1.2  2003/02/24 18:01:57  chr32
 1st working version.

 Revision 1.1  2003/02/24 01:14:17  chr32
 Init rev (unfinished).

*/