/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2006 Bull S.A.
 * 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.lib.management.domain.cluster.tomcat;

import java.util.Collection;
import java.util.HashMap;
import java.util.Set;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.JMException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.ow2.jonas.lib.management.domain.DomainMonitor;
import org.ow2.jonas.lib.management.domain.cluster.BaseCluster;
import org.ow2.jonas.lib.management.domain.cluster.ClusterFactory;
import org.ow2.jonas.lib.management.domain.proxy.server.ServerProxy;

import org.objectweb.util.monolog.api.BasicLevel;

/**
 * Factory for clusters used by Tomact for Session Replication
 * These Clusters are built dynamically, when a new server is discovered
 * as being part of a cluster of this type.
 * @author durieuxp
 */
public class TomcatClusterFactory extends ClusterFactory {

    /**
     * List of TomcatCluster objects
     * There may be more than 1 TomcatCluster in the domain.
     * key = clusterName, value = TomcatCluster
     */
    private HashMap myclusters = new HashMap();

    /**
     * Constructor
     */
    public TomcatClusterFactory(final DomainMonitor dm) {
        super(dm);
    }

    /**
     * Look for a cluster by its name
     * @param name fo the cluster
     * @return cluster or null if not found
     */
    @Override
	public BaseCluster getCluster(final String name) {
        return (BaseCluster) myclusters.get(name);
    }

    /**
     * A new server has been discovered.
     * In case this server is recognized, it is added in a Cluster.
     * If not, nothing is done.
     * @param proxy The new ServerProxy
     * @return True if recognized as a tomcat server.
     */
    @Override
	public boolean notifyServer(final ServerProxy proxy) {

        String serverName = proxy.getServerName();
        logger.log(BasicLevel.DEBUG, serverName);

        // Determine if a Tomcat Cluster MBean exists in the server
        // referenced by this proxy.
        // It should have been created by Tomcat in each server where the
        // tomcat HTTP replication has been defined (in server.xml)
        ObjectName clusterOns;
        try {
            // CatalinaObjectName
            clusterOns = ObjectName.getInstance(domainName + ":type=Cluster,*");
        } catch (MalformedObjectNameException e2) {
            logger.log(BasicLevel.WARN, e2);
            return false;
        }
        Set clusterOnSet = proxy.queryNames(clusterOns);
        if (clusterOnSet == null) {
            logger.log(BasicLevel.DEBUG, "Cannot reach " + serverName);
            return false;
        }
        if (clusterOnSet.isEmpty()) {
            logger.log(BasicLevel.DEBUG, "No Tomcat Cluster declared");
            return false;
        }

        // A Server can be part of only 1 TomcatCluster : Take the first and only one.
        ObjectName clusterOn = (ObjectName) clusterOnSet.iterator().next();
        // CatalinaObjectName = "<domain>:type=Cluster,host=<hostname>"
        String hostName = clusterOn.getKeyProperty("host");
        String clusterName = (String) proxy.getAttribute(clusterOn, "clusterName");
        logger.log(BasicLevel.DEBUG, "Found Tomcat cluster: " + hostName + "," + clusterName);

        // Get cluster config info from the ClusterMembership MBean
        // It should be registered on the related server.

        ObjectName cmon = null;
        try {
            // CatalinaObjectName
            cmon = ObjectName.getInstance(domainName + ":type=ClusterMembership,host=" + hostName);
        } catch (MalformedObjectNameException e1) {
            logger.log(BasicLevel.WARN, e1);
            return false;
        }
        if (!proxy.isRegistered(cmon)) {
            logger.log(BasicLevel.ERROR, "CatalinaClusterMembership not registered:" + cmon);
            return false;
        }
        String mcastAddr = null;
        int mcastPort = 0;
        long mcastDropTime = 0;
        long mcastFrequency = 0;
        int mcastSoTimeout = 0;
        String[] attNames = new String[] {"mcastAddr", "mcastPort",
                "mcastDropTime", "mcastFrequency", "mcastTTL",
                "mcastBindAddress", "mcastClusterDomain", "mcastSoTimeout"};
        AttributeList attList = proxy.getAttributes(cmon, attNames);
        for (int i = 0; i < attList.size(); i++) {
            Attribute att = (Attribute) attList.get(i);
            String attName = att.getName();
            Object attValue = att.getValue();
            if ("mcastAddr".equals(attName)) {
                mcastAddr = (String) attValue;
            }
            if ("mcastPort".equals(attName)) {
                mcastPort = ((Integer) attValue).intValue();
            }
            if ("mcastDropTime".equals(attName)) {
                mcastDropTime = ((Long) attValue).longValue();
            }
            if ("mcastFrequency".equals(attName)) {
                mcastFrequency = ((Long) attValue).longValue();
            }
            if ("mcastSoTimeout".equals(attName)) {
                mcastSoTimeout = ((Integer) attValue).intValue();
            }
            // etc.
        }

        // Retrieve the TomcatCluster. Create it if necessary.
        TomcatCluster cluster = (TomcatCluster) myclusters.get(clusterName);
        if (cluster == null) {
            ObjectName clon = null;
            try {
                cluster = new TomcatCluster(this);
                cluster.setHost(hostName);
                clon = cluster.setName(clusterName);
            } catch (JMException e) {
                logger.log(BasicLevel.ERROR, "Cannot create tomcat SessionCluster:" + e);
                return false;
            }
            // Set config info
            cluster.setMcastAddr(mcastAddr);
            cluster.setMcastPort(mcastPort);
            cluster.setMcastDropTime(mcastDropTime);
            cluster.setMcastFrequency(mcastFrequency);
            cluster.setMcastSocketTimeout(mcastSoTimeout);

            // Register the MBean if not done
            if (!mbeanServer.isRegistered(clon)) {
                try {
                    // A MBean is registered for each Cluster
                    mbeanServer.registerMBean(cluster, clon);
                } catch (Exception e) {
                    logger.log(BasicLevel.ERROR, "Cannot register tomcat cluster:" + e);
                    return false;
                }
            }
            myclusters.put(clusterName, cluster);
        } else {
            // check configuration
            boolean ok = true;
            String currentMcastAddr = cluster.getMcastAddr();
            if (!currentMcastAddr.equals(mcastAddr)) {
                logger.log(BasicLevel.ERROR, "Tomcat cluster " + clusterName
                        + " configuration error: found mcastAddr " + mcastAddr
                        + ", got mcastAddr " + currentMcastAddr);
                ok = false;
            }
            int currentMcastPort = cluster.getMcastPort();
            if (currentMcastPort != mcastPort) {
                logger.log(BasicLevel.ERROR, "Tomcat cluster " + clusterName
                        + " configuration error: found mcastPort " + mcastPort
                        + ", got mcastPort " + currentMcastPort);
                ok = false;
            }
            // Policy has to be defined for missconfigured cluster members
            // if (!ok) {}
        }

        // add a server to the cluster
        return cluster.addTomcatServer(serverName, proxy);
    }

    @Override
	public Collection getClusterList() {
        return myclusters.values();
    }

    /**
     * Update dynamic info for all the ha clusters.
     */
    @Override
	public void getMonitoringInfo() {
        // Not implemented.
    }
}
