 /*
  * Jopr Management Platform
  * Copyright (C) 2005-2008 Red Hat, Inc.
  * All rights reserved.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License, version 2, as
  * published by the Free Software Foundation, and/or the GNU Lesser
  * General Public License, version 2.1, also as published by the Free
  * Software Foundation.
  *
  * This program 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 General Public License and the GNU Lesser General Public License
  * for more details.
  *
  * You should have received a copy of the GNU General Public License
  * and the GNU Lesser General Public License along with this program;
  * if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
package org.rhq.plugins.jbossas.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.ConfigurationUpdateStatus;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.resource.CreateResourceStatus;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.inventory.CreateResourceReport;
import org.rhq.core.pluginapi.util.SelectiveSkippingEntityResolver;

 /**
 * Common base class for MessagingConfig related stuff
 * @author Heiko W. Rupp
 */
public class AbstractMessagingConfigurationEditor {

    protected static final Log LOG = LogFactory.getLog(JBossMessagingConfigurationEditor.class);
    private static final String JNDI_NAME = "JNDIName";
    protected static final String DEPENDS = "depends";
    /**
     * Holds the type of JMS
     */
    protected String type;

    protected String code;
    /**
     * Root Element of the xml file. The component should be a child element of the root element
     */
    protected String rootElementString = "server";
    /**
     * Root Element of the XML File. Always check for NPE in your code. This is assigned in the main interface methods.
     * There are not getters or setters for this attribute.
     */
    protected Element root;
    /**
     * Configuration object for the resource. Always check for NPE in your code. Configuration object is assigned in the
     * update methods of the public interface, so there are no setters or getters needed The configuration is retrieved
     * directly from either the @see CreateResourceReport or
     *
     * @see org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport objects
     */
    protected Configuration config;
    protected CreateResourceReport createReport;
    protected ConfigurationUpdateReport updateReport;
    /**
     * File object of the XML file that will store the resource's configuration values. Always check for NPE in your
     * code. The File object is assigned in the public interface methods, so there are no setters or getters needed. The
     * File object is passed in through the APIs parameters
     */
    protected File deploymentFile;
    protected String securityConfig;
    private static final String MBEAN_NAME = "MBeanName";
    protected static final String ATTRIBUTE = "attribute";

    public AbstractMessagingConfigurationEditor() {
        super();
    }

    public void deleteComponent(File deploymentFile, String name) {
        this.deploymentFile = deploymentFile;
        Document doc;
        if (deploymentFile.exists()) {
            try {
                SAXBuilder builder = new SAXBuilder();
                SelectiveSkippingEntityResolver entityResolver = SelectiveSkippingEntityResolver.getDtdAndXsdSkippingInstance();
                builder.setEntityResolver(entityResolver);

                doc = builder.build(deploymentFile);
                root = doc.getRootElement();

                if (root != null) {
                    if (!root.getName().equals(rootElementString)) {
                        throw new RuntimeException(rootElementString + " file format exception on [" + deploymentFile
                            + "], expected [" + rootElementString + "] element but found [" + root.getName() + "]");
                    }

                    Element datasourceElement = findComponentElement(name);
                    root.removeContent(datasourceElement);
                }

                updateFile(doc);
            } catch (JDOMException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Takes the child elements of the componentElement and loops through them to find the particular property via the
     * looped elements attribute and creates the PropertySimple objects to attach to the @see Configuration object
     *
     * @param elementName      String representing the type of Element to get.
     * @param attributeName    What is the property name to find within the children
     * @param componentElement Element representing this Topic or Queue
     */
    protected void bindSimplePropertiesToXML(String elementName, String attributeName, Element componentElement) {
        List<Element> children = componentElement.getChildren(elementName);
        for (Element element : children) {
            Attribute attribute = element.getAttribute(attributeName);
            String propertyName = null;

            if (attribute != null) {
                propertyName = attribute.getValue();
            }

            String elementValue = element.getValue();

            if ((propertyName != null) && (elementValue != null)) {
                PropertySimple dependProperty = new PropertySimple(propertyName, elementValue);
                config.put(dependProperty);
            }
        }
    }

    protected void bindSimplePropertiesToConfiguration(String[] properties, String elementName, String attributeName,
        Element componentElement) {
        for (String simpleAttribute : properties) {
            PropertySimple property = config.getSimple(simpleAttribute);
            if (property != null) {
                String value = property.getStringValue();
                if (value != null) {
                    if (elementName.equals(DEPENDS)) {
                        try {
                            ObjectName.getInstance(value);
                        } catch (MalformedObjectNameException e) {
                            LOG
                                .error("Dependency to "
                                    + value
                                    + " does not exist or is not deployed. Because of this invalid dependency, this Topic/Queue will not be deployed.");
                            property.setErrorMessage("'" + value + "' is not a valid JMX object name.");
                            if (updateReport != null) {
                                updateReport.setStatus(ConfigurationUpdateStatus.FAILURE);
                            }

                            if (createReport != null) {
                                createReport.setStatus(CreateResourceStatus.FAILURE);
                            }
                        }
                    }

                    Element element = new Element(elementName);
                    Attribute attribute = new Attribute(attributeName, simpleAttribute);
                    element.setAttribute(attribute);
                    element.setText(value);
                    componentElement.addContent(element);
                }
            }
        }
    }

    /**
     * Finds the main Element object that holds all the configuration for the particular component. For example in JMS
     * the tag will be <mbean> with a name attribute for the particular Topic or Queue name
     *
     * @param  name String Topic or Queue name
     *
     * @return The Element object that holds the entire configuration.
     */
    protected Element findComponentElement(String name) {
        for (Object child : root.getChildren()) {
            Element childElement = (Element) child;

            String tagValue = childElement.getName();

            if (tagValue.equals("mbean")) {
                String mBeanName = childElement.getAttribute("name").getValue();
                Element jndiElement = childElement.getChild(JNDI_NAME);
                if (jndiElement != null) {
                    if (name.equals(jndiElement.getValue())) {
                        return childElement;
                    }
                } else {
                    String nameWithinMBean = type + ",name=" + name;
                    if (nameWithinMBean.equals(mBeanName)) {
                        return childElement;
                    }
                }
            }
        }

        return null;
    }

    /**
     * Gets the Element for the component that holds the Security Confs for Roles.
     *
     * @param  componentElement components main Element
     *
     * @return SecurityConf Element
     */
    protected Element findSecurityConf(Element componentElement) {
        List<Element> attributeElements = componentElement.getChildren("attribute");
        if (attributeElements != null) {
            for (Element attributeElement : attributeElements) {
                Attribute attributeName = attributeElement.getAttribute("name");
                if (attributeName.getValue().equals(securityConfig)) {
                    return attributeElement;
                }
            }
        }

        return null;
    }

    private void checkDependencies(String value, Property property) {
        try {
            ObjectName name = ObjectName.getInstance(value);
        } catch (MalformedObjectNameException e) {
            LOG
                .error("Dependency to "
                    + value
                    + " does not exist or is not deployed. because of this dependency this Topic/Queue will not deploy either.");
            property.setErrorMessage(value + " is not a valid value.");
            if (updateReport != null) {
                updateReport.setStatus(ConfigurationUpdateStatus.FAILURE);
            }

            if (createReport != null) {
                createReport.setStatus(CreateResourceStatus.FAILURE);
            }
        }
    }

    protected void loadNameProperty(Element mbeanElement) {
        String name = getNameFromMBeanElement(mbeanElement);
        PropertySimple dependProperty = new PropertySimple("MBeanName", name);
        config.put(dependProperty);
    }

    protected String getNameProperty() {
        String name = "";
        PropertySimple property = config.getSimple(MBEAN_NAME);
        if (property != null) {
            name = property.getStringValue();
        }

        return name;
    }

    protected void updateIfNameChanged(Element mbeanElement) {
        String nameInXML = getNameFromMBeanElement(mbeanElement);
        String nameInConfiguration = getNameProperty();
        if (!nameInXML.equals(nameInConfiguration)) {
            addNameAttributeToMBeanElement(mbeanElement, nameInConfiguration);
        }
    }

    protected void addNameAttributeToMBeanElement(Element mbeanElement, String name) {
        Attribute nameAttribute = new Attribute("name", "");
        nameAttribute.setValue(type + ",name=" + name);
        mbeanElement.removeAttribute("name"); // If it exists we need to remove it
        mbeanElement.setAttribute(nameAttribute);
    }

    private String getNameFromMBeanElement(Element mbeanElement) {
        Attribute name = mbeanElement.getAttribute("name");
        String attributeValue = name.getValue();
        String[] piecesOfAttribute = attributeValue.split(",name=");
        String valueInAttribute = piecesOfAttribute[1];

        return valueInAttribute;
    }

    /**
     * Updates the file.
     *
     * @param  doc JDom Document object for the file
     *
     * @throws org.jdom.JDOMException If JDOM cannot process the Document object
     * @throws java.io.IOException   If there is an exception when dealing with the File object.
     */
    protected void updateFile(Document doc) throws JDOMException, IOException {
        if (!isSetToFailure()) {
            FileOutputStream fos = new FileOutputStream(deploymentFile);
            XMLOutputter outp = new XMLOutputter(Format.getPrettyFormat());
            outp.output(doc, fos);
            fos.flush();
            fos.close();
        }
    }

    /**
     * Used to determine if we should be updating the actual file if there is a failure.
     *
     * @return boolean whether the update or create report is set to Failure status.
     */
    private boolean isSetToFailure() {
        boolean isFailure = false;
        if (updateReport != null) {
            isFailure = (updateReport.getStatus().equals(ConfigurationUpdateStatus.FAILURE));
        }

        if (createReport != null) {
            isFailure = (createReport.getStatus().equals(CreateResourceStatus.FAILURE));
        }

        return isFailure;
    }

    /**
     * Getter method
     *
     * @return String root Element name. i.e. "datasources"
     */
    public String getRootElementString() {
        return rootElementString;
    }

    /**
     * Setter method
     *
     * @param rootElement root element name. i.e. "datasources"
     */
    public void setRootElementString(String rootElement) {
        this.rootElementString = rootElement;
    }

    /**
     * Getter method
     *
     * @return String component type. i.e. "local-tx-datasource"
     */
    public String getType() {
        return type;
    }

    /**
     * Setter method
     *
     * @param type component type. i.e. "local-tx-datasource"
     */
    public void setType(String type) {
        this.type = type;
    }

    public Configuration loadConfiguration(File file, String name) {
        if (file==null)
            return null;

        deploymentFile = file;
        try {
            SAXBuilder builder = new SAXBuilder();
            SelectiveSkippingEntityResolver entityResolver = SelectiveSkippingEntityResolver.getDtdAndXsdSkippingInstance();
            builder.setEntityResolver(entityResolver);

            Document doc = builder.build(deploymentFile);

            // Get the root element
            root = doc.getRootElement();

            if (!root.getName().equals(rootElementString)) {
                return null;
            }

            Element componentElement = findComponentElement(name);

            if (componentElement == null) {
                return null;
            }

            config = new Configuration();

            bindSimplePropertiesToXML(DEPENDS, "optional-attribute-name", componentElement);
            bindSimplePropertiesToXML(ATTRIBUTE, "name", componentElement);
            loadNameProperty(componentElement);

            //Got to get the elements
            Element securityConf = findSecurityConf(componentElement);
            if (securityConf != null) {
                Element security = securityConf.getChild("security");
                List<Element> roles = security.getChildren();
                PropertyList rolesList = new PropertyList(securityConfig);
                for (Element role : roles) {
                    PropertyMap map = new PropertyMap("role");
                    List<Attribute> attributes = role.getAttributes();
                    for (Attribute attribute : attributes) {
                        PropertySimple property = new PropertySimple(attribute.getName(), attribute.getValue());
                        map.put(property);
                    }

                    rolesList.add(map);
                }

                config.put(rolesList);
            }

            return config;
        } catch (IOException e) {
            LOG.error("IOException when trying to read xml file for type " + type + " component " + name, e);
        } catch (JDOMException e) {
            LOG.error("Unable to convert resource into xml file elements", e);
        }

        return null;
    }

}