/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.management.reconfig;

import java.util.Hashtable;
import java.util.Properties;

import javax.management.JMException;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.management.javaee.J2EEManagedObject;
import org.ow2.jonas.lib.management.reconfig.actuator.PropertiesConfigurationActuator;
import org.ow2.jonas.lib.management.reconfig.actuator.XMLConfigurationActuator;
import org.ow2.jonas.lib.management.services.ReconfigNotifications;
import org.ow2.jonas.properties.ServerProperties;

/**
 * This MBean allows persistent reconfiguration of a JOnAS server, all together with its embedded services,
 * and possibly used resources like Data Sources and Mail Factories.
 * This class implements NotificationListener interface. The ReconfigManager adds itself as Listener to
 * the following JMX Notification types (JAVA types):
 * <ul>
 * <li>MBeanServerNotification</li>, sent by the MBean server on MBeans registration/un-registration
 * <li>Notification with type equal to ReconfigNotifications.RECONFIG_TYPE</li>
 * <li>Notification with type equal to ReconfigNotifications.SAVE_RECONFIG_TYPE</li>
 * </ul>
 * @author Adriana Danes
 *
 */
public class ReconfigManager extends J2EEManagedObject implements NotificationListener {
    /**
     * The value of the 'type' property in JonasObjectName of the services MBeans.
     */
    private static final String SERVICE_TYPE = "service";

    /**
     * The value of the j2eeType key for JTA Resources.
     */
    private static final String JTA_RESOURCE_TYPE = "JTAResource";

    /**
     * The value of the j2eeType key for Mail Resources.
     */
    private static final String MAIL_RESOURCE_TYPE = "JavaMailResource";

    /**
     * The value of the j2eeType key for JDBCDataSources.
     */
    private static final String JDBC_RESOURCE_TYPE = "JDBCDataSource";

    /**
     * Type of the security realm.
     */
    private static final String SECURITYREALM_FACTORY = "securityfactory";

    /**
     * Name of the security realm file.
     */
    private static final String SECURITYREALM_FILE = "jonas-realm.xml";

    /**
     * My ObjectName.
     */
    private ObjectName reconfigManagerOn = null;

    /**
     * JOnAS server configuration and environment properties.
     */
    private ServerProperties props = null;

    /**
     * Name of the file for log configuration.
     */
    private String logConfigFileName = null;
    /**
     * Name of the file for JOnAS services configuration.
     */
    private String serverConfigFileName = null;
    /**
     * Table of IConfigurationActuator objects.
     * We have a IConfigurationActuator object per each reconfigurable service + one for the server itself + one per resource
     */
    Hashtable reconfigurators = new Hashtable();

    /**
     * ReconfigManager constructor.
     * @param objectName Associated MBean's OBJECT_NAME
     * @param props JOnAS server configuration and environment properties
     */
    public ReconfigManager(final ObjectName objectName, final ServerProperties props) {
        super(objectName.toString());
        reconfigManagerOn = objectName;
        this.props = props;
    }

    /**
     * Allow for this MBean to receive REGISTRATION_NOTIFICATION/UNREGISTRATION_NOTIFICATION
     * emitted by the JMX server.
     * @throws JMException Could not register listener. The ReconfigManager is not working.
     */
    public void addMBeanServerDelegateListener() throws JMException {
        ObjectName delegate = new ObjectName("JMImplementation:type=MBeanServerDelegate");
        jmxService.getJmxServer().addNotificationListener(delegate, this, null, null);
    }
    /**
     * Treat the notifications emitted by those MBeans having the ReconfigManager added as listener.
     * This method determines the type of the notification and calls the specific treatment.
     * @param notification received notification
     * @param handback received hand-back object
     */
    public void handleNotification(final Notification notification, final Object handback) {
        String notificationType = notification.getType();
        if (notification instanceof MBeanServerNotification) {
            // This notification is sent by the jmx server.
            // It may be a REGISTRATION_NOTIFICATION or an UNREGISTRATION_NOTIFICATION
            handleRegUnregNotification((MBeanServerNotification) notification);
        } else if (notificationType.equals(ReconfigNotifications.RECONFIG_TYPE)) {
            // This notification is emitted by a resource being reconfigured
            handleReconfig(notification);
        } else if (notificationType.equals(ReconfigNotifications.SAVE_RECONFIG_TYPE)) {
            // This notification is emitted by a resource being persistently reconfigured
            handleSave(notification);
        }
    }

    /**
     * Handle notification emitted by the JMX server.
     * @param notification received MBeanServerNotification to treat
     */
    private void handleRegUnregNotification(final MBeanServerNotification notification) {
        String notificationType = notification.getType();
        if (notificationType.equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
            handleRegistrationNotification(notification);
        } else if (notificationType.equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
            handleUnregistrationNotification(notification);
        }
    }

    /**
     * Handle UNREGISTRATION notification emitted by the JMX server.
     * Here we filter notifications and treat only registering MBeans associated
     * to reconfigurable resources. These are detected using their ObjectName:
     * the <code>type</code>, <code>j2eeType</code> and <code>name</code>
     * key properties are relevant.
     * @param notification received UNREGISTRATION MBeanServerNotification to treat
     */
    private void handleUnregistrationNotification(final MBeanServerNotification notification) {
        ObjectName registeringMbean = notification.getMBeanName();
        String registeringName = registeringMbean.getKeyProperty("name");
        if (registeringName != null && reconfigurators.containsKey(registeringName)) {
            // A configurationActuator was created for this MBean
            try {
                jmxService.getJmxServer().removeNotificationListener(registeringMbean, this);
            } catch (JMException me) {
                throw new ReconfigException("ReconfigManager can't remove notification listen because of exception: ", me);
            }
        }
    }
    /**
     * Handle REGISTRATION notification emitted by the JMX server.
     * Here we filter notifications and treat only registering MBeans associated
     * to reconfigurable resources. These are detected using their ObjectName:
     * the <code>type</code>, <code>j2eeType</code> and <code>name</code>
     * key properties are relevant.
     * @param notification received REGISTRATION MBeanServerNotification to treat
     */
    private void handleRegistrationNotification(final MBeanServerNotification notification) {
        // A configurationActuator object will be created if the registering MBean corresponds
        // to a reconfigurable resource.
        IConfigurationActuator configurationActuator = null;
        ObjectName registeringMbean = notification.getMBeanName();
        String registeringType = registeringMbean.getKeyProperty("type");
        String registeringJ2eeType = registeringMbean.getKeyProperty("j2eeType");
        if (registeringJ2eeType != null) {
            registeringType = registeringJ2eeType;
        }

        if (registeringType == null) {
            // Nothing to do
            return;
        }

        String registeringName = registeringMbean.getKeyProperty("name");

        if (registeringType.equals(SERVICE_TYPE) || registeringType.equals(JTA_RESOURCE_TYPE)) {
            // The registered MBean corresponds to a JOnAS service
            try {
                Properties registeringProps;
                String configFileName;
                if ("log".equals(registeringName)) {
                    // Treat registration of the JOnAS log system MBean
                    registeringProps = (Properties) jmxService.getJmxServer().getAttribute(registeringMbean, "props");
                    configFileName = logConfigFileName;
                } else {
                    // other JOnAS service
                    registeringProps = props.getConfigFileEnv();
                    configFileName = serverConfigFileName;
                }
                configurationActuator = new PropertiesConfigurationActuator(registeringName, configFileName, registeringProps);
            } catch (Exception e) {
                // Could not handle LogManager MBean's registration
                // TODO - treat exception
            }
        } else {
            // possibly a j2eeType
            String resourceName = null;
            try {
                if (registeringType.equals(MAIL_RESOURCE_TYPE)) {
                    // get the name of the Mail Factory
                    resourceName = (String) jmxService.getJmxServer().getAttribute(registeringMbean, "FactoryName");
                } else if (registeringType.equals(JDBC_RESOURCE_TYPE)) {
                    // get the name of the Datasource
                    resourceName = (String) jmxService.getJmxServer().getAttribute(registeringMbean, "name");
                } else if (registeringType.equals(SECURITYREALM_FACTORY)) {
                    JProp jprop = JProp.getInstance(SECURITYREALM_FILE);
                    String configFilename = jprop.getPropFile();
                    String txt = jprop.getConfigFileXml();
                    // Create a IConfigurationActuator for this resource
                    configurationActuator = new XMLConfigurationActuator(SECURITYREALM_FILE, configFilename, txt);
                }
                if (resourceName != null) {
                    JProp jprop = JProp.getInstance(resourceName);
                    String configFilename = jprop.getPropFile();
                    Properties registeringProps = JProp.getInstance(resourceName).getConfigFileEnv();
                    configurationActuator = new PropertiesConfigurationActuator(resourceName, configFilename, registeringProps);
                }
            } catch (Exception e) {
                // TODO - treat exception
            }
        }
        // If a configurationActuator was created, register the ReconfigManager MBean as listener of
        // notifications emitted by the reconfigurable resource.
        if (configurationActuator != null) {
            try {
                jmxService.getJmxServer().addNotificationListener(registeringMbean, this, null, null);
            } catch (JMException me) {
                throw new ReconfigException("ReconfigManager can't listen to Notifications because of exception: ", me);
            }
            reconfigurators.put(registeringName, configurationActuator);
        }
    }

    /**
     * Treat reconfiguration operation by calling the specific method on the associated {@link IConfigurationActuator}.
     * @param notification received ReconfigNotifications.RECONFIG_TYPE notification
     */
    private void handleReconfig(final Notification notification) {
        // The MBean sending this notification has an associated IConfigurationActuator object
        // created by the handleRegistrationNotification() method.
        // Get the IConfigurationActuator's name from the 'message' field
        String name = notification.getMessage();
        // get the sequence number
        long sequence = notification.getSequenceNumber();
         // get the reconfigured property (or properties) sent as a UserData within the notification
        IConfigurationData  data = (IConfigurationData) notification.getUserData();
        if (data instanceof PropertiesConfigurationData) {
            PropertiesConfigurationData prop = (PropertiesConfigurationData) data;
            handlePropsReconfig(name, sequence, prop);
        } else {
            XMLConfigurationData xmlData = (XMLConfigurationData) data;
            handleXmlReconfig(name, sequence, xmlData);
        }
    }

    /**
     * Treat reconfiguration operation by calling the specific method on the associated {@link IConfigurationActuator}.
     * @param name {@link IConfigurationActuator} name
     * @param sequence sequence number of the reconfiguration notification to treat
     * @param prop reconfigured property (or properties)
     * @exception ReconfigException ..
     */
    private void handlePropsReconfig(final String name, final long sequence, final PropertiesConfigurationData prop)
    throws ReconfigException {
        PropertiesConfigurationActuator actuator = (PropertiesConfigurationActuator) reconfigurators.get(name);
        if (actuator == null) {
            throw new ReconfigException("Can't find IConfigurationActuator associated to service or resource " + name);
        } else {
           if (prop.getPropValue() != null) {
                if (prop.replaceProp()) {
                    actuator.updateConfig(prop.getPropName(), prop.getPropValue(), sequence);
                } else {
                    actuator.updateConfig(prop.getPropName(), prop.getPropValue(), prop.addProp(), sequence);
                }
            } else {
                actuator.updateConfig(prop.getProps(), sequence);
            }
        }
    }

    /**
     * Treat reconfiguration operation by calling the specific method on the associated {@link IConfigurationActuator}.
     * @param name {@link IConfigurationActuator} name
     * @param sequence sequence number of the reconfiguration notification to treat
     * @param data reconfigured xml
     * @exception ReconfigException ..
     */
    private void handleXmlReconfig(final String name, final long sequence, final XMLConfigurationData data)
    throws ReconfigException {
        XMLConfigurationActuator actuator = (XMLConfigurationActuator) reconfigurators.get(name);
        if (actuator == null) {
            throw new ReconfigException("Can't find IConfigurationActuator associated to service or resource " + name);
        } else {
             actuator.updateConfig(data.getXml(), sequence);
        }
    }
    /**
     * Treat save operation by calling the specific method on the associated {@link IConfigurationActuator}.
     * @param notification received ReconfigNotifications.SAVE_RECONFIG_TYPE notification
     * @exception ReconfigException ..
     */
    private void handleSave(final Notification notification) {
        // Get the IConfigurationActuator's name from the 'message' field
        String name = notification.getMessage();
        // get the sequence number
        long sequence = notification.getSequenceNumber();
        IConfigurationActuator actuator = (IConfigurationActuator) reconfigurators.get(name);
        if (actuator == null) {
            throw new ReconfigException("Can't find IConfigurationActuator associated to service or resource " + name);
        } else {
            actuator.saveConfig(sequence);
        }
    }
    /**
     * ref to the Jmx Service.
     */
    private JmxService jmxService = null;

    /**
     * @param jmxService the jmxService to set
     */
    public void setJmxService(final JmxService jmxService) {
        this.jmxService = jmxService;
    }

    /**
     * @param logConfigFileName file name for the log configuration.
     */
    public void setLogConfigFileName(final String logConfigFileName) {
        this.logConfigFileName = logConfigFileName;
    }

    public void setServerConfigFileName(final String serverConfigFileName) {
        this.serverConfigFileName = serverConfigFileName;
    }
}
