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

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.mc4j.ems.connection.EmsConnection;

import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pluginapi.inventory.ApplicationServerComponent;
import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
import org.rhq.plugins.jbossas.util.DeploymentUtility;
import org.rhq.plugins.jmx.JMXComponent;
import org.rhq.plugins.jmx.MBeanResourceDiscoveryComponent;

/**
* Discovery component used to discover both enterprise and web applications.
*
* @author Jason Dobies
*/
public class ApplicationDiscoveryComponent extends MBeanResourceDiscoveryComponent<JMXComponent<?>> {
    // ResourceDiscoveryComponent Implementation  --------------------------------------------

    public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext<JMXComponent<?>> context) {
        // Parent will discover deployed applications through JMX
        Set<DiscoveredResourceDetails> jmxResources = super.discoverResources(context);

        // JMX resources don't set the filename property, so munge them before going on
        JMXComponent parentComponent = context.getParentResourceComponent();
        ApplicationServerComponent applicationServerComponent = (ApplicationServerComponent) parentComponent;
        EmsConnection emsConnection = parentComponent.getEmsConnection();
        String deployDirectoryPath = generateDeployDirectory(applicationServerComponent.getConfigurationPath()
            .getPath());

        List<String> earNames = new ArrayList<String>();
        for (DiscoveredResourceDetails jmxResource : jmxResources) {
            earNames.add(jmxResource.getResourceName());
        }
        Map<String, String> pathMap = DeploymentUtility.getEarDeploymentPath(emsConnection, earNames);

        for (DiscoveredResourceDetails jmxResource : jmxResources) {
            Configuration pluginConfiguration = jmxResource.getPluginConfiguration();
            String path;
            if (pathMap.containsKey(jmxResource.getResourceName()))
                path = pathMap.get(jmxResource.getResourceName());
            else
                path = deployDirectoryPath + jmxResource.getResourceName(); // Fallback, just in case
            pluginConfiguration.put(new PropertySimple("filename", path));
        }

        // Find all deployed but unstarted applications
        Set<DiscoveredResourceDetails> fileSystemResources = discoverFileSystem(context);

        // Merge. The addAll operation will only add items that are not already present, so resources discovered
        // by JMX will be used instead of those found by the file system scan.
        jmxResources.addAll(fileSystemResources);

        return jmxResources;
    }

    // Private  --------------------------------------------

    /**
     * Discovers applications that are deployed but did not start and are thus not discoverable through JMX.
     *
     * @param  context discovery context, will be used to determine what type of application (EAR, WAR) is being found
     *
     * @return set of all applications discovered on the file system; this should include at least some of the
     *         applications discovered through JMX as well
     */
    private Set<DiscoveredResourceDetails> discoverFileSystem(ResourceDiscoveryContext<JMXComponent<?>> context) {
        Configuration defaultConfiguration = context.getDefaultPluginConfiguration();

        // Find the location of the deploy directory
        JMXComponent parentComponent = context.getParentResourceComponent();
        ApplicationServerComponent applicationServerComponent = (ApplicationServerComponent) parentComponent;

        String deployDirectoryPath = generateDeployDirectory(applicationServerComponent.getConfigurationPath()
            .getPath());
        File deployDirectory = new File(deployDirectoryPath);

        // Set up filter for application type
        String extension = defaultConfiguration.getSimple("extension").getStringValue();
        FilenameFilter filter = new ApplicationFileFilter(extension);
        File[] files = deployDirectory.listFiles(filter);

        // For each file found, create a resource details instance for it
        ResourceType resourceType = context.getResourceType();

        Set<DiscoveredResourceDetails> resources = new HashSet<DiscoveredResourceDetails>(files.length);
        for (File file : files) {
            String resourceKey = determineResourceKey(defaultConfiguration, file.getName());
            String objectName = resourceKey;
            String resourceName = file.getName();
            String description = defaultConfiguration.getSimple("descriptionTemplate").getStringValue();

            DiscoveredResourceDetails resource = new DiscoveredResourceDetails(resourceType, resourceKey, resourceName,
                "", description, null, null);
            Configuration resourcePluginConfiguration = resource.getPluginConfiguration();
            resourcePluginConfiguration.put(new PropertySimple("name", resourceName));
            resourcePluginConfiguration.put(new PropertySimple("objectName", objectName));
            resourcePluginConfiguration.put(new PropertySimple("filename", deployDirectoryPath + resourceName));

            resources.add(resource);
        }

        return resources;
    }

    // Private  --------------------------------------------

    /**
     * Generates the string representing the deployment directory for this AS instance. This may have to change in the
     * future to take into account WARs nested inside of EARs.
     *
     * @param  profilePath path to the AS profile directory (i.e. /opt/jboss/server/default)
     *
     * @return path to the deployment directory
     */
    private String generateDeployDirectory(String profilePath) {
        return profilePath + File.separator + "deploy" + File.separator;
    }

    /**
     * Creates the appropriate resource key. The resource key is generated by substituting the file name into the plugin
     * descriptor provided JMX object name template. This will only be run for applications that are not currently
     * deployed and accessible through JMX. This method mimics the resource key created by the super class handling of
     * JMX discovered applications.
     *
     * @param  defaultConfiguration default plugin configuration for the application's resource type
     * @param  fileName             file name of the application found (e.g. rhq.ear)
     *
     * @return resource key to use for the indicated application file
     */
    private String determineResourceKey(Configuration defaultConfiguration, String fileName) {
        String template = defaultConfiguration.getSimple("objectName").getStringValue();
        String resourceKey = template.replaceAll("%name%", fileName);
        return resourceKey;
    }

    // Inner Classes  --------------------------------------------

    /**
     * Filter used to find applications.
     */
    private static class ApplicationFileFilter implements FilenameFilter {
        // Attributes  --------------------------------------------

        private String applicationExtension;

        // Constructors  --------------------------------------------

        private ApplicationFileFilter(String applicationExtension) {
            this.applicationExtension = applicationExtension;
        }

        // FilenameFilter Implementation  --------------------------------------------

        public boolean accept(File dir, String name) {
            // TODO: If on Windows, this check should be case-insensitive.
            boolean result = name.endsWith(applicationExtension);
            return result;
        }
    }
}