/**
 * 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.jk;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.objectweb.util.monolog.api.BasicLevel;
import org.ow2.jonas.lib.bootstrap.JProp;
import org.ow2.jonas.lib.management.domain.cluster.BaseCluster;
import org.ow2.jonas.lib.management.domain.cluster.ClusterMember;
import org.ow2.jonas.lib.management.domain.proxy.server.ServerProxy;
import org.ow2.jonas.lib.util.JonasObjectName;

/**
 * JkCluster are used for Web level load balancing
 * @author Adriana Danes
 * @author Philippe Durieux
 */
public class JkCluster extends BaseCluster implements JkClusterMBean {

    /**
     * The type of Cluster, that is part of the MBean ObjectName
     */
    private String type = "JkCluster";

    /**
     * This properties are listed in "worker.properties" config file.
     */
    private JProp jprop = null;

    /**
     * The Load Balancer Worker.
     * Its name is given to the Cluster.
     */
    private String lbWorker = null;

    /**
     * These servers are actually the jonas servers that compose the Cluster.
     * Built from "workers.properties".
     */
    private ArrayList balancedWorkers = new ArrayList();

    /**
     * Web level loadbalancing cluster constructor
     * Lookup for configuration file in JONAS_BASE/conf
     * This is the "workers.properties" file from TOMCAT.
     * @param cf ClusterFactory
     * @throws JMException could not create MBean instance
     */
    public JkCluster(final JkClusterFactory cf) throws JMException {
        super(cf);

        // Read the "Worker.properties" configuration file.
        try {
            jprop = JProp.getInstance("workers.properties");
        } catch (RuntimeException e) {
            logger.log(BasicLevel.DEBUG, "Cannot find workers.properties:" + e);
            throw e;
        }

        // Get the lbWorker defined in the config file.
        getLbWorker();
        if (lbWorker == null) {
            logger.log(BasicLevel.WARN, "No Load Balancer worker in workers.properties");
            return;
        }

        // Read the list of Workers defined in the workers.properties file
        getBalancedWorkerList();


        // Give a name to the Cluster and register it as a MBean
        setName(lbWorker);
        cf.getMBeanServer().registerMBean(this, objectName);
    }

    @Override
    public ClusterMember createClusterMember(final String svname, final ServerProxy proxy) {
        return new JkClusterMember(svname, proxy);
    }

    // --------------------------------------------------------------------------
    // Other public methods
    // --------------------------------------------------------------------------

    /**
     * @return The String type to be put in the ObjectName
     */
    @Override
    public String getType() {
        return type;
    }

    /**
     * Add a Worker to the list of the JkCluster
     * It should be known in workers.properties
     * Make link between the member and the ServerProxy.
     * @param workerName the worker name
     * @param workerPort the worker port
     * @param proxy The ServerProxy related object.
     * @return True if correctly added in the List.
     */
    public boolean addWorker(final String workerName, final int workerPort, final ServerProxy proxy) {

        // Check if a the detected worker corresponds to a worker
        // defined in the configuration file
        // If not print a Warning and exit.
        boolean found = false;
        for (Iterator it = balancedWorkers.iterator(); it.hasNext();) {
            String aWorkerName = (String) it.next();
            if (workerName.equals(aWorkerName)) {
                found = true;
                // it corresponds to a defined worker if the port value
                // is also corresponding
                String portString = (String) getWorkerProp(workerName, "port");
                Integer portValue = new Integer(portString);
                if (portValue != null && portValue.intValue() != workerPort) {
                    logger.log(BasicLevel.WARN, "Bad port number for: " + workerName);
                }
                break;
            }
        }
        if (!found) {
            logger.log(BasicLevel.WARN, "Cannot find this worker in workers.properties:" + workerName);
            return false;
        }

        // Create the JkClusterMember object (= a worker)
        JkClusterMember worker = new JkClusterMember(workerName, proxy);
        worker.setPort(workerPort);

        // Set configuration parameters (from workers.properties)
        String host = (String) getWorkerProp(workerName, "host");
        if (host != null) {
            worker.setHost(host);
        }
        String type = (String) getWorkerProp(workerName, "type");
        if (type != null) {
            worker.setType(type);
        }
        String lbFactorString = (String) getWorkerProp(workerName, "lbfactor");
        Integer lbFactor = new Integer(lbFactorString);
        if (lbFactor != null) {
            worker.setLbfactor(lbFactor.intValue());
        }

        // Add this member if not already there
        boolean added = addMember(worker);
        if (added) {
            // Build the ObjectName and register MBean
            try {
                ObjectName on = JonasObjectName.clusterMember(domainName, workerName, getType(), name);
                worker.setObjectName(on);
                MBeanServer mbeanServer = jmx.getJmxServer();
                if (mbeanServer.isRegistered(on)) {
                    mbeanServer.unregisterMBean(on);
                }
                mbeanServer.registerMBean(worker, on);
            } catch (JMException e) {
                logger.log(BasicLevel.WARN, "Cannot register Worker " + workerName + ": " + e);
            }
        }
        return added;
    }

    // --------------------------------------------------------------------------
    // private methods
    // --------------------------------------------------------------------------

    /**
     * Get the loadbalancer worker defined in worker.properties.
     * Found by its type that is equals to "lb"
     */
    private void getLbWorker() {
        // Get the worker list in order to find the balancer worker
        String valueList = jprop.getValue("worker.list");
        StringTokenizer st = new StringTokenizer(valueList, ",");
        while (st.hasMoreTokens()) {
            String aWorker = st.nextToken();
            // check if this is a load balancer worker
            String key = "worker." + aWorker + ".type";
            String type = jprop.getValue(key);
            if ("lb".equals(type)) {
                lbWorker = aWorker;
                break;
            }
        }
    }

    /**
     * Get the balance worker list defined in worker.properties
     * into balancedWorkers attribute.
     */
    private void getBalancedWorkerList() {
        // Get the balanced workers
        String key = "worker." + lbWorker + ".balance_workers";
        String valueList = jprop.getValue(key);
        StringTokenizer st = new StringTokenizer(valueList, ",");
        while (st.hasMoreTokens()) {
            String aWorker = st.nextToken();
            // Sanity check: control the type.
            key = "worker." + aWorker + ".type";
            String type = jprop.getValue(key);
            if ("ajp13".equals(type)) {
                balancedWorkers.add(aWorker);
            } else {
                logger.log(BasicLevel.WARN, "Bad worker type: " + type);
            }
        }
    }
    /**
     * Get a worker directive value
     * @param workerName the worker name
     * @param directive the worker directive
     * @return the worker directive value
     */
    private Object getWorkerProp(final String workerName, final String directive) {
        Object valueObj = null;
        String key = "worker." + workerName + "." + directive;
        if (jprop != null) {
            valueObj = jprop.getValue(key);
        }
        return valueObj;
    }
    // ----- Management methods ----
    /**
     * @return The list of the load balancer workers' name
     */
    public String[] getBalancedWorkers() {
        String[] result = new String[balancedWorkers.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = (String) balancedWorkers.get(i);
        }
        return result;
    }
    /**
     * @return true if stcky session
     */
    public boolean isStickySession() {
        String key = "worker." + lbWorker + ".sticky_session";
        return new Boolean(jprop.getValue(key)).booleanValue();
    }

}
