/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2004-2007 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 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.discovery.internal;

import java.util.ArrayList;

import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXServiceURL;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.jonas.discovery.base.BaseDiscovery;
import org.ow2.jonas.discovery.base.DuplicateServerNameException;
import org.ow2.jonas.discovery.base.comm.DiscEvent;
import org.ow2.jonas.discovery.base.comm.DiscMessage;
import org.ow2.jonas.discovery.internal.client.DiscoveryClient;
import org.ow2.jonas.discovery.internal.client.DiscoveryClientMBean;
import org.ow2.jonas.discovery.internal.enroller.Enroller;
import org.ow2.jonas.discovery.internal.enroller.EnrollerMBean;
import org.ow2.jonas.discovery.internal.manager.DiscoveryManager;
import org.ow2.jonas.jmx.JmxService;
import org.ow2.jonas.lib.util.JonasObjectName;
import org.ow2.jonas.lib.util.Log;
import org.ow2.jonas.service.ServiceException;


/**
 * The discovery service creates and starts at least a {@link DiscoveryManager}
 * which is a MBean  that multi-casts <code>discovery information</code> to all
 * the servers who joined the  <code>discovery multicast group</code>.
 * <p>
 * The discovery service may also create a {@link Enroller} and a
 * {@link DiscoveryClient}, and in this case  the current server becomes
 * a <code>discovery server</code>.
 * </p>
 * <p>
 * <code>Discovery information</code> contains information allowing to
 * remotely manage a server in the group.
 * </p>
 * <p>
 * <code>Discovery multicast group</code> is a group of servers which can
 * be managed remotely by a discovery server.
 * </p>
 * <p>
 * <code>Discovery server</code> is the server in the group who detains
 * the discovery information concerning all the servers in the group.
 * </p>
 * @author Adriana Danes
 */
public class MulticastDiscoveryServiceImpl extends BaseDiscovery implements MulticastDiscoveryServiceImplMBean {

    /**
     * This attribute is injected
     * String form of the IP Address where multicast messages are sent.
     * @uml.property  name="listeningIp"
     */
    private String listeningIp = null;

    /**
     * This attribute is injected
     * The port to listen to for multicast messages.
     * @uml.property  name="listeningPort"
     */
    private int listeningPort;

    /**
     * This attribute is injected
     * The port to listen to for responses to greeting message.
     * @uml.property  name="greetingListeningPort"
     */
    private int greetingListeningPort;

    /**
     * This attribute is injected
     * The port where discovery events are sent.
     * @uml.property  name="sourcePort"
     */
    private int sourcePort;

    /**
     * The discovery manager.
     */
    private DiscoveryManager dm = null;

    /**
     *  Handles {@link DiscEvent} for master discovery.
     */
    private Enroller enroller = null;

    /**
     * handles {@link DiscMessage} for master or slave discovery.
     */
    private DiscoveryClient dc;

    /**
     * Logger for this service.
     */
    private static Logger logger = Log.getLogger(Log.JONAS_DISCOVERY_PREFIX);

    /**
     * @return the multicast group IP address used by the discovery service
     */
    public String getDiscoveryAddress() {
        return listeningIp;
    }

    /**
     * @return the multicast group port number used by the discovery service
     */
    public String getDiscoveryPort() {
        return String.valueOf(listeningPort);
    }

    /**
     * @param address the multicast group IP address used by the discovery service.Used on injection
     */
    public void setMulticastAddress(final String address) {
        listeningIp = address;
    }

    /**
     * @param port multi-cast group port number. Used on injection
     */
    public void setMulticastPort(final int port) {
        this.listeningPort = port;
    }

    /**
     * Management operation allowing to make the current server become a
     * master if its not already.
     * @throws JMException a JMX exception occurred when trying to make current server a discovery master
     */
    public void startDiscoveryMaster() throws JMException {
        if (!getIsDiscoveryMaster()) {
            createEnroller(getDomainName());
            createDiscClient(getDomainName());
            setMaster(true);
        }
    }

    /**
     * Create the {@link EnrollerMBean} MBean.
     * @param domainName the domain name
     * @throws JMException
     */
    private void createEnroller(final String domainName) throws JMException {
        enroller = new Enroller(listeningPort, listeningIp);
        enroller.setTimeToLive(getTtl());
        getJmxService().registerMBean(enroller,
                                     JonasObjectName.discoveryEnroller(domainName));
    }

    /**
     * Create the {@link DiscoveryClientMBean} MBean.
     * @param domainName the domain name
     * @throws JMException
     */
    private void createDiscClient(final String domainName) throws JMException {
        dc = new DiscoveryClient(listeningPort, listeningIp, sourcePort);
        dc.setTimeToLive(getTtl());
        getJmxService().registerMBean(dc, JonasObjectName.discoveryClient(domainName));
    }
    /**
     * Start the discovery service.
     *
     * @throws ServiceException
     *             An error occurred when starting the service
     */
    @Override
    protected void doStart() throws ServiceException {

        JmxService jmx = getJmxService();

        // Create discovery manager
        setMaster(getServerProperties().isMaster());
        dm = new DiscoveryManager(getJonasServerName(),
                                  listeningPort,
                                  listeningIp,
                                  greetingListeningPort,
                                  getGreetingTimeout());
        String domainName = getDomainName();
        dm.setDomainName(domainName);
        dm.setJonasName(getJonasServerName());
        dm.setTimeToLive(getTtl());
        JMXServiceURL[] connectorServerURLs = jmx.getConnectorServerURLs();
        urlsList = new ArrayList();
        for (int i = 0; i < connectorServerURLs.length; i++) {
            // The connectorServerURLs may contain null
            // if the list of protocols in Carol contain
            // other protocols than the standard ones (JRMP, IIOP, CMI)
            if (connectorServerURLs[i] != null) {
                urlsList.add(connectorServerURLs[i].toString());
            }
        }
        String[] urls = new String[urlsList.size()];
        for (int i = 0; i < urls.length; i++) {
            urls[i] = (String) urlsList.get(i);
        }
        dm.setUrls(urls);

        // Register DiscoveryManager MBean
        try {
            jmx.registerMBean(dm,
                              JonasObjectName.discoveryManager(domainName));
        } catch (MalformedObjectNameException e1) {
            e1.printStackTrace();
            throw new ServiceException("Problem when starting the Discovery Service:",
                                       e1);
        }

        // Start discovery manager
        try {
            dm.start();
        } catch (DuplicateServerNameException e) {
            // Server with the same name already exists in domain.
            logger.log(BasicLevel.ERROR,
                       "Discovery manager failed to start due to a pre-existing"
                       + " server in the domain with the same name.", e);
            try {
                jmx.unregisterMBean(JonasObjectName.discoveryManager(domainName));
            } catch (MalformedObjectNameException e1) {
                e1.printStackTrace();
                throw new ServiceException(
                        "Problem when starting the Discovery Service:", e1);
            }
            // Discovery service had a problem.
            throw new ServiceException(
                    "Problem when starting the Discovery Service:", e);
        }

        if (getIsDiscoveryMaster()) {
            // Create enroller
            try {
                createEnroller(domainName);
            } catch (JMException e) {
                throw new ServiceException(
                        "Problem when starting the Discovery Service: ", e);
            }

            // Create the discovery client
            try {
                createDiscClient(domainName);
            } catch (JMException e) {
                throw new ServiceException(
                        "Problem when starting the Discovery Service: ", e);
            }
        }
        // Create and register the service MBean
        jmx.registerMBean(this, JonasObjectName.discoveryService(domainName));
        logger.log(BasicLevel.INFO, "Multicast discovery started successfully");

    }

    /**
     * Stop the discovery service.
     */
    @Override
    protected void doStop() throws ServiceException {
        // Unregister the service MBean
        JmxService jmxService = getJmxService();
        if (jmxService != null) {
            jmxService.unregisterMBean(JonasObjectName.discoveryService(getDomainName()));
        }
        logger.log(BasicLevel.INFO, "Multicast discovery stopped successfully");
    }

    /**
     * Create a 'fake' DiscEvent object containing info to establish a JMX connection with a new server
     * in the domain.
     * @param serverName the OBJECT_NAME of the server MBean corresponding to the server to connect
     * @param domainName the JOnAS server's domain name
     * @param connectorURLs the urls of the connector server of the server to connect
     * @param state the state of the server (RUNNING in case the DiscEvent is used to add a server; could be STOPPING
     * if the DiscEvent is used to remove a server)
     * @return a new DiscEvent object
     */
    public DiscEvent getDiscEvent(final String serverName,
                                  final String domainName,
                                  final String[] connectorURLs,
                                  final String state) {
        String sourceAddress = null;
        int sourcePort = 0;
        String serverId = null;
        DiscEvent fakeMessage = new DiscEvent(sourceAddress,
                                              sourcePort,
                                              serverName,
                                              domainName,
                                              serverId,
                                              connectorURLs);
        fakeMessage.setState(state);
        return fakeMessage;
    }
    /**
     * @return the discovery protocol version number
     */
    public String getDiscoveryProtocolVersion() {
        return DiscMessage.DISCOVERY_PROTOCOL_VERSION;
    }

    /**
     * @return Returns the discovery Time to live
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getDiscoveryTtl()
     */
    public String getDiscoveryTtl() {
        return String.valueOf(getTtl());
    }


    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getDiscoveryClient()
     */
    public ObjectName getDiscoveryClient() {
        return null;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getDiscoveryManager()
     */
    public ObjectName getDiscoveryManager() throws MalformedObjectNameException {
        return JonasObjectName.discoveryManager(getDomainName());
    }


    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getEnroller()
     */
    public ObjectName getEnroller() throws MalformedObjectNameException {
        return JonasObjectName.discoveryEnroller(getJmxService().getDomainName());
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#getListeningIp()
     */
    @Override
    public String getListeningIp() {
        return listeningIp;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#getListeningPort()
     */
    @Override
    public int getListeningPort() {
        return listeningPort;
    }


    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getSourcePort()
     */
    public int getSourcePort() {
        return sourcePort;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#setListeningIp(java.lang.String)
     */
    @Override
    public void setListeningIp(final String listeningIp) {
        this.listeningIp = listeningIp;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#setListeningPort(int)
     */
    @Override
    public void setListeningPort(final int listeningPort) {
        this.listeningPort = listeningPort;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.DiscoveryServiceImplMBean#setSourcePort(int)
     */
    public void setSourcePort(final int sourcePort) {
        logger.log(BasicLevel.DEBUG, "Setting source port \n");
        this.sourcePort = sourcePort;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#setUrlsList(java.util.ArrayList)
     */
    @Override
    public void setUrlsList(final ArrayList urlsList) {
        logger.log(BasicLevel.DEBUG, "Setting urls \n");
        this.urlsList = urlsList;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getGreetingPort()
     */
    public int getGreetingPort() {
        logger.log(BasicLevel.DEBUG, "Getting greeting listening port \n");
        return greetingListeningPort;
    }

    /**
     * @param port greeting port.
     */
    public void setGreetingPort(final int port) {
        logger.log(BasicLevel.DEBUG, "Setting greeting port \n");
        this.greetingListeningPort = port;
    }
    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.base.BaseDiscovery#getUrlsList()
     */
    @Override
    public ArrayList getUrlsList() {
        logger.log(BasicLevel.DEBUG, "Setting urls \n");
        return urlsList;
    }

    public String getJonasName() {
        logger.log(BasicLevel.DEBUG, "Getting name \n");
        return dm.getJonasName();
    }

    public MBeanServer getMbeanServer() {
        logger.log(BasicLevel.DEBUG, "Getting MBean server \n");
        return getJmxService().getJmxServer();
    }

    public ObjectName getMyOn() {
        logger.log(BasicLevel.DEBUG, "Getting ObjectName \n");
        return JonasObjectName.discoveryService(getJmxService().getDomainName());
    }

    public String getServerId() {
        logger.log(BasicLevel.DEBUG, "Getting server ID \n");
        return getJmxService().getJonasServerName();
    }

    public String[] getUrls() {
        logger.log(BasicLevel.DEBUG, "Getting urls \n");
        return (String[]) urlsList.toArray();
    }

    public void setDomainName(final String domainName) {
        logger.log(BasicLevel.DEBUG, "Setting domain name \n");
        dm.setDomainName(domainName);

    }

    public void setJonasName(final String jonasName) {
        logger.log(BasicLevel.DEBUG, "Setting name \n");
        dm.setJonasName(jonasName);

    }

    public void setMbeanServer(final MBeanServer mbeanServer) {
        // TODO implement this
        logger.log(BasicLevel.DEBUG, "Setting MBean server: not yet implemented \n");

    }

    public void setMyOn(final ObjectName myOn) {
        // TODO implement this
        logger.log(BasicLevel.DEBUG, "Setting ObjectName: not yet implemented \n");
    }

    public void setServerId(final String serverId) {
        logger.log(BasicLevel.DEBUG, "Setting  server ID: not yet implemented \n");
    }

    public void setUrls(final String[] urls) {
        logger.log(BasicLevel.DEBUG, "Setting server urls \n");
        urlsList = toArrayList(urls);
    }

    /**
     * Build and Arraylist from String[] containing jmx urls.
     * @param urls
     * @return
     * @throws NullPointerException
     */
    @SuppressWarnings("unchecked")
    private ArrayList toArrayList(final String[] urls) throws NullPointerException {
        ArrayList<String> ret = new ArrayList();
        try {
            for (int i = 0; i < urls.length; i++) {
                ret.add(urls[i]);
            }
        } catch (NullPointerException e) {
            throw new NullPointerException("NullPointerException thrown in JgroupsDiscoveryServiceImpl. Urls list must not be null in toArrayList method \n"+e);
        }
        return ret;
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getMulticastAddress()
     */
    public String getMulticastAddress() {
        return getListeningIp();
    }

    /* (non-Javadoc)
     * @see org.ow2.jonas.discovery.internal.MulticastDiscoveryServiceImplMBean#getMulticastPort()
     */
    public String getMulticastPort() {
        return new Integer(listeningPort).toString();
    }

}
