/**
 * JaDOrT: JASMINe Deployment Orchestration Tool
 * Copyright (C) 2008-2009 Bull S.A.S.
 * Copyright (C) 2008-2009 France Telecom R&D
 * Contact: jasmine@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: TopologyReader.java 4310 2009-07-07 14:41:06Z chahinem $
 * --------------------------------------------------------------------------
 */
package org.ow2.jasmine.jadort.service.implementation;

import java.io.File;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.ow2.jasmine.deployme.generated.Configuration;
import org.ow2.jasmine.deployme.generated.Configuration.Protocols;
import org.ow2.jasmine.deployme.generated.Topology.Domains;
import org.ow2.jasmine.deployme.generated.Topology.Domains.Domain.ClusterDaemons;
import org.ow2.jasmine.deployme.generated.Topology.Domains.Domain.Servers;
import org.ow2.jasmine.jadort.api.JadortServiceException;
import org.ow2.jasmine.jadort.api.entities.topology.ConnectorBean;
import org.ow2.jasmine.jadort.api.entities.topology.GroupBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean;
import org.ow2.jasmine.jadort.api.entities.topology.TopologyBean;
import org.ow2.jasmine.jadort.api.entities.topology.VMBean;
import org.ow2.jasmine.jadort.api.entities.topology.WorkerBean;
import org.ow2.jasmine.jadort.api.entities.topology.ServerBean.Type;
import org.ow2.jasmine.jadort.service.topology.xml.Balancer;
import org.ow2.jasmine.jadort.service.topology.xml.Server;
import org.ow2.jasmine.jadort.service.topology.xml.Target;
import org.ow2.jasmine.jadort.service.topology.xml.URLConnector;
import org.ow2.jasmine.jadort.service.topology.xml.VM;
import org.ow2.jasmine.jadort.service.topology.xml.VMM;
import org.ow2.jasmine.jadort.service.topology.xml.Worker;
import org.ow2.jasmine.jadort.service.topology.xml.Topology.Group;
import org.xml.sax.SAXException;

/**
 * Helper class that reads topology files.
 * 
 * @author Malek Chahine
 * @author S. Ali Tokmen
 */
public class TopologyReader {

    public final static String XSD_PATH_JADORT = "XMLSchema/topology_JadortSpecific.xsd";

    public final static String XSD_PATH_DEPLOYME = "xml/topology.xsd";

    /**
     * @see TopologyReader#loadSchemaAndFile(String, Class, File)
     * @param xmlTopoFile JaDOrT-specific topology XML file to load.
     */
    public static TopologyBean loadJadortSpecificTopology(final File xmlTopoFile) throws UnmarshalException,
        JadortServiceException {
        org.ow2.jasmine.jadort.service.topology.xml.Topology xmlTopology = TopologyReader.loadSchemaAndFile(
            TopologyReader.XSD_PATH_JADORT, org.ow2.jasmine.jadort.service.topology.xml.Topology.class, xmlTopoFile);

        TopologyBean topologyBean = new TopologyBean();

        Map<String, Worker> workerXMLs = new HashMap<String, Worker>();
        for (Worker workerXML : xmlTopology.getWorker()) {
            workerXMLs.put(workerXML.getName(), workerXML);
        }

        Map<String, VMM> vmmXMLs = new HashMap<String, VMM>();
        for (VMM vmmXML : xmlTopology.getVMM()) {
            vmmXMLs.put(vmmXML.getName(), vmmXML);
        }

        List<GroupBean> groups = new ArrayList<GroupBean>();
        for (Group groupXML : xmlTopology.getGroup()) {
            GroupBean groupBean = new GroupBean();
            groupBean.setName(groupXML.getName());
            groupBean.setClustered(groupXML.isClustered());
            groupBean.setConnected(true);

            List<ServerBean> servers = new ArrayList<ServerBean>();
            List<WorkerBean> workers = new ArrayList<WorkerBean>();
            boolean atLeastOneServerHasVM = false;
            String VMMName = "";
            for (Server serverXML : groupXML.getServer()) {
                ServerBean serverBean = new ServerBean();
                serverBean.setName(serverXML.getName());
                if (serverXML.getCapacity() != null) {
                    serverBean.setCapacity(serverXML.getCapacity());
                }
                serverBean.setType(ServerBean.Type.valueOf(serverXML.getType().name()));

                serverBean.setServerConnector(TopologyReader.createJMXConnectorBean(serverXML.getURLConnector()));
                if (serverXML.getManager() != null) {
                    serverBean.setManagerConnector(TopologyReader.createJMXConnectorBean(serverXML.getManager()
                        .getURLConnector()));
                }

                Target targetXML = serverXML.getTarget();
                ServerBean target = null;
                if (targetXML != null) {
                    target = new ServerBean();
                    target.setName(targetXML.getName());
                    target.setType(ServerBean.Type.valueOf(targetXML.getType().name()));

                    target.setServerConnector(TopologyReader.createJMXConnectorBean(targetXML.getURLConnector()));
                    if (targetXML.getManager() != null) {
                        target.setManagerConnector(TopologyReader.createJMXConnectorBean(targetXML.getManager()
                            .getURLConnector()));
                    }

                    serverBean.setTarget(target);
                }

                List<Balancer> balancerXMLs = serverXML.getBalancer();
                if (balancerXMLs != null) {
                    for (Balancer balancerXML : balancerXMLs) {
                        Worker workerXML = workerXMLs.get(balancerXML.getWorker());
                        if (workerXML == null) {
                            throw new JadortServiceException("There is no worker named \"" + balancerXML.getWorker()
                                + "\", please check your topology file.", null);
                        }
                        WorkerBean workerBean = new WorkerBean();
                        workerBean.setName(balancerXML.getServer());
                        workerBean.setWorkerName(workerXML.getName());
                        workerBean.setType(WorkerBean.Type.valueOf(workerXML.getType().name()));
                        workerBean.setServerName(balancerXML.getServer());
                        workerBean.setConnector(TopologyReader.createJMXConnectorBean(workerXML.getURLConnector()));
                        workerBean.setServer(serverBean);
                        workers.add(workerBean);
                    }
                }

                VM vmXML = serverXML.getVM();
                if (vmXML != null) {
                    atLeastOneServerHasVM = true;
                    VMM vmmXML = vmmXMLs.get(vmXML.getVmm());
                    if (vmmXML != null) {
                        VMBean vmBean = new VMBean();
                        vmBean.setName(vmXML.getName());
                        vmBean.setVmm(vmXML.getVmm());
                        vmBean.setConnector(TopologyReader.createJMXConnectorBean(vmmXML.getURLConnector()));
                        serverBean.setVm(vmBean);
                        VMMName = vmmXML.getName();
                    } else {
                        throw new JadortServiceException("VMM not defined" + "\", please check your topology file.", null);
                    }
                }
                servers.add(serverBean);
            }
            for (ServerBean server : servers) {
                if ((atLeastOneServerHasVM && server.getVm() == null) || (!atLeastOneServerHasVM && server.getVm() != null)) {
                    throw new JadortServiceException("All servers in a group must have a VM or no server has one"
                        + "\", please check your topology file.", null);
                }
                if (server.getVm() != null && !server.getVm().getVmm().equals(VMMName)) {
                    throw new JadortServiceException("All servers in a group must have the same VMM"
                        + "\", please check your topology file.", null);
                }
            }
            groupBean.setServers(servers);
            groupBean.setWorkers(workers);

            groups.add(groupBean);
        }
        topologyBean.setGroups(groups);
        return topologyBean;
    }

    /**
     * @see TopologyReader#loadSchemaAndFile(String, Class, File)
     * @param xmlTopoFile JASMINe Deploy ME topology XML file to load.
     */
    public static TopologyBean loadDeployMETopology(final File xmlTopoFile) throws UnmarshalException, JadortServiceException {
        org.ow2.jasmine.deployme.generated.Topology xmlTopology = TopologyReader.loadSchemaAndFile(
            TopologyReader.XSD_PATH_DEPLOYME, org.ow2.jasmine.deployme.generated.Topology.class, xmlTopoFile);

        TopologyBean topologyBean = new TopologyBean();

        List<GroupBean> groups = new ArrayList<GroupBean>();
        for (Domains.Domain domain : xmlTopology.getDomains().getDomain()) {
            Configuration conf = domain.getConfiguration();
            String defaultHost = null;
            int defaultPort = 0;
            Protocols protocols = null;

            if (conf != null) {
                if (conf.getGlobalJonas() != null) {
                    defaultHost = conf.getGlobalJonas().getHost();
                }
                protocols = conf.getProtocols();
            }
            if (defaultHost == null) {
                defaultHost = "localhost";
            }
            String defaultProtocol = null;
            if (protocols != null) {
                defaultProtocol = protocols.getProtocolsList();
                BigInteger jrmpPort = protocols.getJrmpPort();
                if (jrmpPort != null) {
                    defaultPort = jrmpPort.intValue();
                }
            }
            if (defaultProtocol != null) {
                // Take the first protocol of the list
                defaultProtocol = defaultProtocol.split(",")[0];
            } else {
                defaultProtocol = "jrmp";
            }

            GroupBean groupBean = new GroupBean();
            groupBean.setName(domain.getName());

            boolean clustered = false;
            try {
                if (conf.getActiveServices().getWeb().isHttpReplicationActivated()) {
                    clustered = true;
                } else {
                    clustered = false;
                }
            } catch (NullPointerException ignored) {
                // If NPE, then there's no HttpReplicationConf
            }

            List<ServerBean> servers = new ArrayList<ServerBean>();
            for (Servers.Server server : domain.getServers().getServer()) {
                conf = server.getConfiguration();
                String host = null;
                if (conf != null) {
                    if (conf.getGlobalJonas() != null) {
                        host = conf.getGlobalJonas().getHost();
                    }
                    protocols = conf.getProtocols();
                }
                if (host == null) {
                    host = defaultHost;
                }
                String name = server.getName();
                int port = defaultPort;
                String protocol = null;
                if (protocols != null) {
                    protocol = protocols.getProtocolsList();
                    BigInteger jrmpPort = protocols.getJrmpPort();
                    if (jrmpPort != null) {
                        port = jrmpPort.intValue();
                    }
                }
                if (protocol != null) {
                    // Take the first protocol of the list
                    protocol = protocol.split(",")[0];
                } else {
                    protocol = defaultProtocol;
                }
                String transport;
                if (protocol.equals("irmi")) {
                    transport = "rmi";
                    if (port == 0) {
                        port = 1098;
                    }
                } else if (protocol.equals("jrmp")) {
                    transport = "rmi";
                    if (port == 0) {
                        port = 1099;
                    }
                } else if (protocol.equals("iiop")) {
                    transport = "iiop";
                    if (port == 0) {
                        port = 2001;
                    }
                } else {
                    throw new JadortServiceException("Unknown protocol \"" + protocol + "\" for server \"" + name + "\".", null);
                }

                String jmxURL = "service:jmx:" + transport + "://" + host + "/jndi/" + transport + "://" + host + ":" + port
                    + "/" + protocol + "connector_" + name;

                ConnectorBean connector = new ConnectorBean();
                connector.setConnectorUrl(jmxURL);
                ServerBean serverBean = new ServerBean();
                serverBean.setName(name);
                serverBean.setServerConnector(connector);
                if (JadortServiceStatefulBean.DUMMY) {
                    serverBean.setType(Type.DUMMY);
                } else {
                    serverBean.setType(Type.JONAS);
                }
                servers.add(serverBean);

                try {
                    if (conf.getActiveServices().getWeb().isHttpReplicationActivated()) {
                        clustered = true;
                    } else {
                        clustered = false;
                    }
                } catch (NullPointerException ignored) {
                    // If NPE, then there's no HttpReplicationConf
                }
            }
            if (domain.getClusterDaemons() != null) {
                for (ClusterDaemons.ClusterDaemon cd : domain.getClusterDaemons().getClusterDaemon()) {
                    int nb = 1;
                    String prefix = cd.getServerNamePrefix();
                    for (String serverName : cd.getServers().getServerName()) {
                        boolean found = false;
                        for (ServerBean s : servers) {
                            if (s.getName().equals(serverName)) {
                                s.setName(prefix + nb);
                                s.getServerConnector().setConnectorUrl(
                                    s.getServerConnector().getConnectorUrl().replace("connector_" + serverName,
                                        "connector_" + s.getName()));

                                nb++;
                                String protocol = cd.getClusterDaemonProtocol();
                                if (protocol != null) {
                                    // Take the first protocol of the list
                                    protocol = protocol.split(",")[0];
                                } else {
                                    protocol = defaultProtocol;
                                }
                                int port = defaultPort;
                                Integer portInteger = cd.getClusterDaemonPort();
                                if (portInteger != null) {
                                    port = portInteger.intValue();
                                }
                                String transport;
                                if (protocol.equals("irmi")) {
                                    transport = "rmi";
                                    if (port == 0) {
                                        port = 1098;
                                    }
                                } else if (protocol.equals("jrmp")) {
                                    transport = "rmi";
                                    if (port == 0) {
                                        port = 1099;
                                    }
                                } else if (protocol.equals("iiop")) {
                                    transport = "iiop";
                                    if (port == 0) {
                                        port = 2001;
                                    }
                                } else {
                                    throw new JadortServiceException("Unknown protocol \"" + protocol
                                        + "\" for cluster daemon \"" + cd.getName() + "\".", null);
                                }

                                String serverURL = s.getServerConnector().getConnectorUrl();
                                int hostStart = serverURL.indexOf("://") + 3;
                                String host = serverURL.substring(hostStart, serverURL.indexOf('/', hostStart));
                                String jmxURL = "service:jmx:" + transport + "://" + host + "/jndi/" + transport + "://" + host
                                    + ":" + port + "/" + protocol + "connector_" + cd.getName();
                                ConnectorBean manager = new ConnectorBean();
                                manager.setConnectorUrl(jmxURL);
                                s.setManagerConnector(manager);

                                found = true;
                                break;
                            }
                        }

                        if (!found) {
                            throw new JadortServiceException("Cannot find server \"" + serverName + "\" for cluster daemon \""
                                + cd.getName() + "\".", null);
                        }
                    }
                }
            }
            groupBean.setServers(servers);
            groupBean.setClustered(clustered);

            groups.add(groupBean);
        }
        topologyBean.setGroups(groups);
        return topologyBean;
    }

    /**
     * Verifies an XML file against an XSD and instantiates it using a given
     * class.
     * 
     * @param xsdPath XSD file path.
     * @param rootClass Root class used for instantiating JAXB.
     * @param xmlFile XML to load.
     * @return XML loaded using JAXB and the rootClass.
     * @throws JadortServiceException If loading fails.
     * @throws UnmarshalException If parsing fails (file doesn't correspond to
     *         XSD).
     */
    @SuppressWarnings("unchecked")
    protected static <T> T loadSchemaAndFile(final String xsdPath, final Class<T> rootClass, final File xmlFile)
        throws JadortServiceException, UnmarshalException {
        try {
            JAXBContext jc = JAXBContext.newInstance(rootClass.getPackage().getName());
            Unmarshaller unMarshaller = jc.createUnmarshaller();

            URL xsdURL = TopologyReader.class.getClassLoader().getResource(xsdPath);
            SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
            Schema schema = schemaFactory.newSchema(xsdURL);
            unMarshaller.setSchema(schema);

            return (T) unMarshaller.unmarshal(xmlFile);
        } catch (JAXBException e) {
            if (e instanceof UnmarshalException) {
                UnmarshalException ue = (UnmarshalException) e;
                if (ue.getLinkedException() != null) {
                    throw new UnmarshalException(ue.getLinkedException().getMessage(), e);
                } else {
                    throw ue;
                }
            } else {
                throw new JadortServiceException("Error creating the topology parser: " + e.getMessage(), e);
            }
        } catch (SAXException e) {
            throw new JadortServiceException("Error creating the topology parser: " + e.getMessage(), e);
        }
    }

    /**
     * @param urlConnector URLConnector for which to create a JMXConnector bean.
     * @return JMXConnector bean for the given urlConnector.
     */
    protected static ConnectorBean createJMXConnectorBean(final URLConnector urlConnector) {
        ConnectorBean jmxConnectorBean = new ConnectorBean();

        jmxConnectorBean.setConnectorUrl(urlConnector.getURL());
        if (urlConnector.getUsername() != null && urlConnector.getPassword() != null) {
            jmxConnectorBean.setUsername(urlConnector.getUsername());
            jmxConnectorBean.setPassword(urlConnector.getPassword());
        }

        return jmxConnectorBean;
    }
}
