/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id: EARModuleMBean.java 20637 2010-12-06 16:47:31Z benoitf $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ear.internal.mbean;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.io.StringReader;
import java.io.StringWriter;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.RuntimeOperationsException;
import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.ow2.jonas.ear.internal.EARModule;
import org.ow2.jonas.lib.bootstrap.LoaderManager;
import org.ow2.jonas.lib.management.javaee.J2EEDeployedObjectMBean;
import org.ow2.jonas.lib.management.javaee.J2eeObjectName;
import org.ow2.util.url.URLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * This class is managing the object for JSR77 J2EEApplication.
 * @author Florent BENOIT
 */
public class EARModuleMBean extends J2EEDeployedObjectMBean<EARModule> {

    /**
     * Modules of this application.
     */
    private String[] modules = null;

    /**
     * Public default constructor.
     * @throws MBeanException if this Object can't be created
     */
    public EARModuleMBean() throws MBeanException {
        super();
    }

    /**
     * @return Returns the XML Deployment Descriptors of the J2EEApplication Module.
     */
    @Override
    public String getDeploymentDescriptor() {
        String dd = null;
        try {
            dd = ((EARModule) getManagedResource()).getDeploymentDescriptor();
        } catch (InstanceNotFoundException e) {
            throw new IllegalStateException("Cannot get the managed resource of the EARModuleMBean", e);
        } catch (RuntimeOperationsException e) {
            throw new IllegalStateException("Cannot get the managed resource of the EARModuleMBean", e);
        } catch (MBeanException e) {
            throw new IllegalStateException("Cannot get the managed resource of the EARModuleMBean", e);
        } catch (InvalidTargetObjectTypeException e) {
            throw new IllegalStateException("Cannot get the managed resource of the EARModuleMBean", e);
        }
        return dd;
    }

    /**
     * Build the Modules of this EAR.
     */
    private void initModules() {
        // List of ObjectNames corresponding to J2EEModules
        List<String> modulesList = new ArrayList<String>();

        // Get EAR ObjectName
        ObjectName objectName = null;
        try {
            objectName = ObjectName.getInstance(getObjectName());
        } catch (MalformedObjectNameException e) {
            throw new IllegalStateException("Cannot get ObjectName on '" + getObjectName() + "'", e);
        } catch (NullPointerException e) {
            throw new IllegalStateException("Cannot get ObjectName on '" + getObjectName() + "'", e);
        }

        // Parse Name
        String domain = objectName.getDomain();
        String serverName = objectName.getKeyProperty(J2EESERVER_KEY);

        String appName = objectName.getKeyProperty("name");
        ObjectName modulesOn = null;

        // Add EJBModules
        modulesOn = J2eeObjectName.getEJBModules(domain, serverName, appName);
        addModuleObjectNames(modulesList, modulesOn);

        // Add WebModules
        modulesOn = J2eeObjectName.getWebModules(domain, serverName, appName);
        addModuleObjectNames(modulesList, modulesOn);

        // Add ResourceAdapterModules
        modulesOn = J2eeObjectName.getResourceAdapterModules(domain, serverName, appName);
        addModuleObjectNames(modulesList, modulesOn);

        // Add AppClientModules
        modulesOn = J2eeObjectName.getAppClientModules(domain, serverName, appName);
        addModuleObjectNames(modulesList, modulesOn);

        // set modules
        this.modules = modulesList.toArray(new String[modulesList.size()]);
    }

    /**
     * Add to a destination list ObjectNames corresponding to J2EEModules
     * ObjectName patterns found in the local MBeanServer.
     * @param modulesList destination list
     * @param objectName J2EEModules ObjectName patterns
     */
    @SuppressWarnings("unchecked")
    private void addModuleObjectNames(final List<String> modulesList, final ObjectName objectName) {
        Iterator<ObjectName> itNames = getRegistry().getMBeanServer().queryNames(objectName, null).iterator();
        while (itNames.hasNext()) {
            modulesList.add(itNames.next().toString());
        }
    }

    /**
     * @return the Application Name.
     */
    public String getName() {
        return getManagedComponent().getEARDeployable().getModuleName();
    }

    /**
     * @return the URL of this Application
     */
    public URL getEarUrl() {
        try {
            URL earURL = getManagedComponent().getEARDeployable().getOriginalDeployable().getArchive().getURL();
            if ("file".equals(earURL.getProtocol())) {
                // If file, make sure it is escaped correctly
                File file = URLUtils.urlToFile(earURL);
                earURL = file.toURL();
            }
            return earURL;
        } catch (Exception e) {
            throw new IllegalArgumentException("Cannot get URL from deployable '" + getManagedComponent().getEARDeployable()
                    + "'.", e);
        }
    }

    /**
     * Return the J2EEModules used by this J2EEApplication (jar, war, rar).
     * @return A array of OBJECT_NAMEs corresponding to these modules.
     */
    public String[] getModules() {
        if (modules == null) {
            initModules();
        }
        return modules;
    }


    /**
     * Gets data about loading a given class.
     * @param className the class name
     * @return a string description for the given class that needs to be loaded
     */
    public String loadClass(final String className) throws javax.management.modelmbean.InvalidTargetObjectTypeException, javax.management.InstanceNotFoundException, javax.management.MBeanException  {

        // Get ClassLoader
        ClassLoader systemClassLoader = null;
        try {
            systemClassLoader = LoaderManager.getInstance().getExternalLoader();
        } catch (Exception e) {
            throw new IllegalStateException("Unable to get LoaderManager", e);
        }


        // Create XML document...

        // Create builder with factory
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = null;
        try {
            builder = factory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new IllegalStateException("Cannot build document builder", e);
        }

        // Create Document
        Document document = builder.newDocument();

        // Append root element
        Element classElement = document.createElement("class");
        document.appendChild(classElement);

        // name
        classElement.setAttribute("name", className);


        boolean classNotFound = false;
        String error = null;
        ClassLoader earClassLoader = ((EARModule) getManagedResource()).getClassLoader();
        Class<?> clazz = null;
        try {
            clazz = earClassLoader.loadClass(className);
        } catch (ClassNotFoundException e) {
            error = e.toString();
            classNotFound = true;
        } catch (Error e) {
            classNotFound = true;
            error = e.toString();
        }

        // class not found ? (add error content if NotFound)
        classElement.setAttribute("classNotFound", Boolean.toString(classNotFound));
        if (classNotFound) {
            Element errorElement = document.createElement("error");
            classElement.appendChild(errorElement);
            Text errorText = document.createTextNode(error);
            errorElement.appendChild(errorText);
        } else {
            // Class found ! Add details (if any)

            // Search if the classes was loaded from the module, from the application or from the system
            String type = "Application";
            ClassLoader classClassLoader = clazz.getClassLoader();
            ClassLoader cl = earClassLoader;
            boolean found = false;
            while (cl != null && !found) {

                // ClassLoader is equals to the classloader that has loaded the class
                if (cl.equals(classClassLoader)) {
                    found = true;
                }

                if (systemClassLoader.equals(cl)) {
                    type = "System";
                }


                cl = cl.getParent();
            }

            // Add where the class has been found
           classElement.setAttribute("where", type);
           

           // ClassLoader info (if any)
           if (classClassLoader != null) {
               Element classLoaderElement = document.createElement("class-loader");
               classElement.appendChild(classLoaderElement);
               classLoaderElement.setAttribute("name", classClassLoader.getClass().getName());
               Text classLoaderText = document.createTextNode(classClassLoader.toString());
               classLoaderElement.appendChild(classLoaderText);
            }
        }

        StringWriter stringWriter = new StringWriter();
        StreamResult streamResult = new StreamResult(stringWriter);

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer;
        try {
            transformer = transformerFactory.newTransformer();
        } catch (TransformerConfigurationException e) {
            throw new IllegalStateException("Unable to get a new transformer", e);
        }

        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

        // transform OUTPUT
        try {
            transformer.transform(new DOMSource(document), streamResult);
        } catch (TransformerException e) {
            throw new IllegalStateException("Unable to transform the document", e);
        }

        return stringWriter.toString();
    }

    /**
     * Gets the filters of classloader.
     * @return the filters for the war classloader
     */
    public String[] getClassLoaderFilters() throws javax.management.modelmbean.InvalidTargetObjectTypeException, javax.management.InstanceNotFoundException, javax.management.MBeanException  {
        List<String> filters = ((EARModule) getManagedResource()).getFilteringClassLoader().getFilters();
        return filters.toArray(new String[filters.size()]);
    }

    /**
     * Gets all the URL to the given resource.
     * @param resourceName the name of the resource
     * @return the list of url, if any
     */
    public URL[] getResources(final String resourceName) throws javax.management.modelmbean.InvalidTargetObjectTypeException, javax.management.InstanceNotFoundException, javax.management.MBeanException  {
        ClassLoader classLoader = ((EARModule) getManagedResource()).getClassLoader();

        Enumeration<URL> urls = null;
        try {
            urls = classLoader.getResources(resourceName);
        } catch (IOException e) {
            throw new IllegalStateException("Unable to get the resource '" + resourceName + "'.", e);
        }

        List<URL> urlsList = new ArrayList<URL>();
        while (urls.hasMoreElements()) {
            urlsList.add(urls.nextElement());
        }

        return urlsList.toArray(new URL[urlsList.size()]);
    }


}
