/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.enterprise.management.support;

import com.sun.appserv.management.base.AMXDebug;
import com.sun.appserv.management.monitor.LoadBalancerMonitor;  
import com.sun.appserv.management.monitor.LoadBalancerClusterMonitor;
import com.sun.appserv.management.monitor.LoadBalancerServerMonitor;
import com.sun.appserv.management.monitor.LoadBalancerApplicationMonitor;
import com.sun.appserv.management.monitor.LoadBalancerContextRootMonitor;
import com.sun.appserv.management.config.DeployedItemRefConfig;
import com.sun.appserv.management.config.ClusterRefConfig;
import com.sun.appserv.management.config.ClusterConfig;
import com.sun.appserv.management.config.ServerRefConfig;
import com.sun.appserv.management.config.ServerConfig;
import com.sun.appserv.management.config.StandaloneServerConfig;
import com.sun.appserv.management.config.LoadBalancerConfig;
import com.sun.appserv.management.config.LBConfig;
import com.sun.appserv.management.ext.lb.LoadBalancer;
import com.sun.appserv.management.monitor.MonitoringRoot;
import com.sun.appserv.management.DomainRoot;

import java.util.Map;
import java.util.HashMap;
import java.io.IOException;
import javax.management.AttributeChangeNotification;

import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import com.sun.appserv.management.base.XTypes;
import static com.sun.appserv.management.base.XTypes.LOAD_BALANCER;
import static com.sun.appserv.management.base.XTypes.LOAD_BALANCER_CONFIG;
import static com.sun.appserv.management.base.XTypes.LOAD_BALANCER_MONITOR;
import static com.sun.appserv.management.base.XTypes.LB_CONFIG;

import com.sun.appserv.management.base.AMX;
import static com.sun.appserv.management.base.AMX.J2EE_TYPE_KEY;
import static com.sun.appserv.management.base.AMX.NAME_KEY;
import static com.sun.appserv.management.base.AMX.JMX_DOMAIN;

import com.sun.appserv.management.base.Util;
import com.sun.appserv.management.client.ProxyFactory;
import com.sun.enterprise.management.monitor.LoadBalancerMonitorImpl;
import com.sun.enterprise.management.ext.lb.LoadBalancerImpl;

/**
 * A class that triggers forming LB monitoring tree whenever a new <load-balancer>
 * gets registered. The registration logic 
 * 1. register a runtime LoadBalancer MBean 
 * 2. register a listener to listen on the attribute monitoring-enabled in the 
 *    <lb-config> referenced by this <load-balancer>
 * 3. create the monitoring tree starting with 
 *    a. the root LoadBalancerMonitor
 *    b. LoadBalancerClusterMonitor(s) under root LoadBalancerMonitor
 *    c. LoadBalancerServerMonitor(s) under each LoadBalancerClusterMonitor
 *    d. LoadBalancerApplicationMonitor(s) under each LoadBalancerServerMonitor
 *    e. LoadBalancerContextRootMonitor(s) under each LoadBalancerApplicationMonitor
 * @see LoadBalancerClusterRefRegistrationListener
 * @see LoadBalancerApplicationRefRegistrationListener
 * @see LoadBalancerRegistrationListener
 */
public class LoadBalancerRegistrationListener extends LBBaseMBeanRegistrationListener {
    
    final static String MONITORING_ENABLED = "MonitoringEnabled";

    public LoadBalancerRegistrationListener(MBeanServer mBeanServer)
        throws InstanceNotFoundException, IOException {
        super(mBeanServer);
    }

    LoadBalancer registerLoadBalancer(String name) {
        //create load balancer runtime mbean
        DomainRoot domainRoot = 
            (DomainRoot) ProxyFactory.getInstance(getConn()).getDomainRoot();
        ObjectName domainRootObjName = Util.getObjectName(domainRoot);
        
        ObjectName loadBalancerObjName = 
            objectNames.buildContaineeObjectName( domainRootObjName, 
                domainRoot.getFullType(), LOAD_BALANCER, name);

        LoadBalancerImpl loadBalancerImpl = new LoadBalancerImpl(getConn());
        try {
            ((MBeanServer)getConn()).registerMBean(
                loadBalancerImpl, loadBalancerObjName);
        } catch( JMException ex ) {
            logWarning(
                "LoadBalancerRegistrationListener registerLoadBalancer " +
                "failed. Exception = ", ex);	
        }
        
        return ProxyFactory.getInstance(
            getConn()).getProxy(loadBalancerObjName, LoadBalancer.class);        
    }
    
    protected void mbeanRegistered(final ObjectName objectName) {
        try {
            if (LOAD_BALANCER_CONFIG.equals(Util.getJ2EEType(objectName))) {
                LoadBalancerConfig loadBalancerConfig =  
                    ProxyFactory.getInstance(getConn()).getProxy(objectName, LoadBalancerConfig.class);
                String name = loadBalancerConfig.getName();
                String loadBalancerConfigName = loadBalancerConfig.getName();
                LoadBalancer loadBalancer = 
                    registerLoadBalancer(loadBalancerConfig.getName());
                //Add a listener for the monitoring-enabled attribute of the 
                //referenced lb config mbean and store the reference to this 
                //listener for removal when this config is unregistered
                String lbConfigName = loadBalancerConfig.getLbConfigName();
                LBConfig lbConfig = 
                    getDomainConfig().getLBConfigMap().get(lbConfigName);
                
                addLBConfigListener(name, lbConfig);
                
                if (lbConfig.getMonitoringEnabled()) 
                    enableMonitoring(name, lbConfig);
            }
	} catch (Exception ex) {
            ex.printStackTrace();
            logWarning(
                "LoadBalancerRegistrationListener mbeanRegistered " +
                    "failed. Exception = ", ex);	
        }
    }
    
    private LoadBalancerMonitor registerLoadBalancerMonitor(String name) {
        
        //create root load balancer monitor
        MonitoringRoot monitoringRoot = (MonitoringRoot) ProxyFactory
            .getInstance(getConn()).getDomainRoot().getMonitoringRoot();
        ObjectName monitoringRootObjName = Util.getObjectName(monitoringRoot);
        
        ObjectName loadBalancerMonitorObjName = 
            objectNames.buildContaineeObjectName(monitoringRootObjName, 
                monitoringRoot.getFullType(), LOAD_BALANCER_MONITOR, name);

        LoadBalancerMonitorImpl loadBalancerMonitorImpl = 
            new LoadBalancerMonitorImpl();
        try {
            ((MBeanServer)getConn()).registerMBean(
                loadBalancerMonitorImpl, loadBalancerMonitorObjName);
        } catch( JMException ex ) {
            logWarning(
                "LoadBalancerRegistrationListener registerLoadBalancerMonitor " +
                    "failed. Exception = ", ex);	
        }
        
        return ProxyFactory.getInstance(
                    getConn()).getProxy(loadBalancerMonitorObjName,LoadBalancerMonitor.class );
    }
    
    void enableMonitoring(String name, LBConfig lbConfig) throws Exception {

        //Step1: Create the root empty LoadBalancerMonitor
       LoadBalancerMonitor lbm = registerLoadBalancerMonitor(name);
        
        //Step2: Create the default clusters LoadBalancerClusterMonitors and stick
        //in the monitoring mbeans corresponding to the standalone server 
        //instances - menaing server name and cluster name is same -
        //in these pseudo LoadBalancerClusterMonitors
       
        //get <server-ref> elements in this lbConfig
        Map<String, ServerRefConfig> serverRefConfigMap = 
            lbConfig.getServerRefConfigMap();
        
        if (!serverRefConfigMap.isEmpty()) {
            //get ALL standalone <server> elements
            Map<String,StandaloneServerConfig> ssConfigMap = 
                getDomainConfig().getStandaloneServerConfigMap();
            Map<String, ServerConfig> serverConfigMap = new HashMap<String, ServerConfig>();
            //doing the following to enable reuse of the 
            //registerLoadBalancerServerMonitorTree base class method
            for (String key : ssConfigMap.keySet()) 
                serverConfigMap.put(key, (ServerConfig)ssConfigMap.get(key));

            for (String serverName : serverRefConfigMap.keySet()) {
                LoadBalancerClusterMonitor lbcm =
                    registerLoadBalancerClusterMonitor(lbm, serverName);
                registerLoadBalancerServerMonitorTree(
                    lbcm, serverConfigMap, serverName);
            }
        } 
     
        //Step3: Create the LoadBalancerClusterMonitor and children tree based 
        //on the <cluster-ref> elements referred to by this lb-config 
        //there may be no cluster-ref elements. 
        
        //Beware both serverRefConfigMap and clusterRefConfigMap CANNOT be non-empty
        //DTD says <lb-config> = (<cluster-ref>* | <server-ref>*, property*)
        
        //get <cluster-ref> elements in this lbConfig
        Map<String, ClusterRefConfig> clusterRefConfigMap = 
            lbConfig.getClusterRefConfigMap();

        if (!clusterRefConfigMap.isEmpty()) 
            for (String clusterName : clusterRefConfigMap.keySet()) 
                registerLoadBalancerClusterMonitorTree(lbm, clusterName);
    }

    void disableMonitoring(String loadBalancerName) throws Exception {
        
       LoadBalancerMonitor lbm = 
           getLBDeregistrationUtil((MBeanServer)getConn())
           .fetchLBMonitoringRoot(loadBalancerName);

       Map<String,LoadBalancerClusterMonitor> lbcmMap =
           lbm.getLoadBalancerClusterMonitorMap();

       for (LoadBalancerClusterMonitor lbcm : lbcmMap.values()) {
           //loadBalancerServerMonitorMap will have only one element for every
           //standalone server as it is is wrapped up in a default cluster
           Map<String,LoadBalancerServerMonitor> lbsmMap =
                lbcm.getLoadBalancerServerMonitorMap();

           for (LoadBalancerServerMonitor lbsm : lbsmMap.values()) {

               Map<String,LoadBalancerApplicationMonitor> 
                   lbamMap = lbsm.getLoadBalancerApplicationMonitorMap();

               for (LoadBalancerApplicationMonitor lbam : lbamMap.values()) {
                   Map<String,LoadBalancerContextRootMonitor> 
                        lbcrmMap = lbam.getLoadBalancerContextRootMonitorMap();
                   for (LoadBalancerContextRootMonitor lbcrm : lbcrmMap.values()) {
                        ObjectName lbcrmObjName = Util.getObjectName(lbcrm);
                        ((MBeanServer)getConn()).unregisterMBean(lbcrmObjName );
                    }

                    ObjectName lbamObjName = Util.getObjectName(lbam);
                    ((MBeanServer)getConn()).unregisterMBean(lbamObjName);
                }

                ObjectName loadBalancerServerMonitorObjName = 
                    Util.getObjectName(lbsm);
                ((MBeanServer)getConn())
                    .unregisterMBean(loadBalancerServerMonitorObjName);
            }

            ObjectName lbcmObjName = Util.getObjectName(lbcm);
                ((MBeanServer)getConn()).unregisterMBean(lbcmObjName);
       }

       ObjectName lbmObjName = Util.getObjectName(lbm);
       ((MBeanServer)getConn()).unregisterMBean(lbmObjName);
    }
    
    void addLBConfigListener(String loadBalancerName, LBConfig lbConfig) {
        LBConfigListener mLBConfigListener = 
            new LBConfigListener(loadBalancerName);
        getLBDeregistrationUtil((MBeanServer)getConn())
            .addLBConfigListener(loadBalancerName, mLBConfigListener, lbConfig);
    }
    
    /**
     * A class that listens on the attribute monitoring-enabled in the 
     * <lb-config> element. There is one instance of this listener per 
     * <load-balancer>. Hence if multiple <load-balancer>s reference a 
     * <lb-config>, there are multiple listeners for the same <lb-config>
     * This Map<load-balancer-name, LBConfigListener> is stored in 
     * LBDeregistrationUtil. LoadBalancerConfig.postDeregister removes 
     * the listener corresponding to 
     * @see com.sun.enterprise.management.support.LBDeregistrationUtil
     *
     */    
    private final class LBConfigListener implements NotificationListener {

        String mLoadBalancerConfigName;
        
        public LBConfigListener(String name) {
            mLoadBalancerConfigName = name;
        }
        
        public void handleNotification(
            final Notification notif, final Object handback) {
            try {
                if (notif.getType().equals(
                    AttributeChangeNotification.ATTRIBUTE_CHANGE)) {
                    
                    AttributeChangeNotification attrChangeNotif = 
                        (AttributeChangeNotification) notif;
                    
                    if (MONITORING_ENABLED.equals(
                            attrChangeNotif.getAttributeName())) {
                     
                        if ((Boolean)attrChangeNotif.getNewValue() != 
                                (Boolean)attrChangeNotif.getOldValue()) {
                    
                            if (attrChangeNotif.getNewValue().equals(Boolean.TRUE)) {
                                ObjectName lbConfigObjName = 
                                    (ObjectName) notif.getSource();
                                final LBConfig lbConfig =
                                    ProxyFactory.getInstance(getConn()).getProxy(lbConfigObjName, LBConfig.class);
                                enableMonitoring(mLoadBalancerConfigName, lbConfig);
                            } else disableMonitoring(mLoadBalancerConfigName);
                            
                        }

                    }
                }
            } catch(Exception ex) {
                logWarning(
                    "LBBootstrapUtil$LBConfigListener" +
                    " handleNotification failed. Exception = ", ex);	
            }
        }
    }        
}
