/** 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.jboss.on.plugins.tomcat;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.on.plugins.tomcat.helper.CreateResourceHelper;
import org.mc4j.ems.connection.EmsConnection;
import org.mc4j.ems.connection.bean.EmsBean;
import org.mc4j.ems.connection.bean.attribute.EmsAttribute;
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.jmx.MBeanResourceDiscoveryComponent;

/**
 * Discovery component used to discover web applications.
 *
 * @author Jay Shaughnessy
 * @author Jason Dobies
 */
public class TomcatWarDiscoveryComponent extends MBeanResourceDiscoveryComponent<TomcatVHostComponent> {

    public static final String PLUGIN_CONFIG_NAME = "name";

    private static final List<String> EMS_ATTRIBUTE_DOC_BASE = Arrays.asList(new String[] { "docBase" });
    private static final List<String> EMS_ATTRIBUTE_PATH = Arrays.asList(new String[] { "path" });
    /** The name MBean attribute for each application is of the form "Tomcat WAR (//vHost/contextRoot)". */
    private static final Pattern PATTERN_NAME = Pattern.compile("//(.*)(/.*)");
    private static final String RT_LOG_FILE_NAME_SUFFIX = "_rt.log";

    private final Log log = LogFactory.getLog(this.getClass());

    public Set<DiscoveredResourceDetails> discoverResources(ResourceDiscoveryContext<TomcatVHostComponent> context) {
        // Parent will discover deployed applications through JMX
        Set<DiscoveredResourceDetails> resources = super.discoverResources(context);
        Set<DiscoveredResourceDetails> result = new HashSet<DiscoveredResourceDetails>();

        TomcatVHostComponent parentComponent = context.getParentResourceComponent();
        ApplicationServerComponent applicationServerComponent = (ApplicationServerComponent) parentComponent;
        String deployDirectoryPath = applicationServerComponent.getConfigurationPath().getPath();
        String parentHost = parentComponent.getName();
        Matcher m = PATTERN_NAME.matcher("");

        for (DiscoveredResourceDetails resource : resources) {
            resource.setResourceKey(CreateResourceHelper.getCanonicalName(resource.getResourceKey()));
            Configuration pluginConfiguration = resource.getPluginConfiguration();
            String name = pluginConfiguration.getSimpleValue(PLUGIN_CONFIG_NAME, "");
            m.reset(name);
            if (m.matches()) {
                String host = m.group(1);
                // skip entries that are not for this vHost
                if (!host.equalsIgnoreCase(parentHost)) {
                    continue;
                }

                // get some info from the MBean (it seems awkward I have to query for the bean I'm basically dealing with)
                EmsConnection connection = context.getParentResourceComponent().getEmsConnection();
                EmsBean warBean = connection.getBean(resource.getResourceKey());
                // this refresh is important in case EMS is caching a stale version of this object. It can happen if
                // a user deletes and then recreates the same object.
                List<EmsAttribute> contextRootAttribs = warBean.refreshAttributes(EMS_ATTRIBUTE_PATH);                
                String contextRoot = (String) contextRootAttribs.get(0).getValue();
                List<EmsAttribute> docBaseAttribs = warBean.refreshAttributes(EMS_ATTRIBUTE_DOC_BASE);
                String docBase = (String) docBaseAttribs.get(0).getValue();
                File docBaseFile = new File(docBase);
                String filename = (docBaseFile.isAbsolute()) ? docBase
                    : (deployDirectoryPath + File.separator + docBase);
                try {
                    filename = new File(filename).getCanonicalPath();
                } catch (IOException e) {
                    // leave path as is
                    log.warn("Unexpected discovered web application path: " + filename);
                }
                if ("".equals(contextRoot)) {
                    contextRoot = "/";
                }
                pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_VHOST, host));
                pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_CONTEXT_ROOT, contextRoot));
                pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_FILENAME, filename));
                pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_RESPONSE_TIME_LOG_FILE,
                    getResponseTimeLogFile(parentComponent.getCatalinaBase(), host, contextRoot)));
                resource.setResourceName(resource.getResourceName().replace("{contextRoot}",
                    (("/".equals(contextRoot)) ? docBase : contextRoot)));

                result.add(resource);
            } else {
                log.warn("Skipping discovered web application with unexpected name: " + name);
            }
        }

        // Find apps in the deploy directory that have not been deployed. This can happen if the vhost is
        // not autodeploying
        Set<DiscoveredResourceDetails> undeployedWarResources = discoverUndeployed(context, result);

        // 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.
        result.addAll(undeployedWarResources);

        return result;
    }

    /**
     * Discovers applications that are present in the docbase directory but are not deployed and
     * are thus not discoverable through JMX since they have no mbean.
     *
     * @param  context discovery context
     * @param  deployed the set of already deployed (discovered via JMX) apps,  used for filtering 
     *
     * @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> discoverUndeployed(ResourceDiscoveryContext<TomcatVHostComponent> context,
        Set<DiscoveredResourceDetails> deployed) {
        Configuration defaultConfiguration = context.getDefaultPluginConfiguration();

        // Find the location of the deploy directory
        TomcatVHostComponent vhost = context.getParentResourceComponent();
        File deployDirectory = vhost.getConfigurationPath();

        // Set up filter for application type
        FileFilter filter = new WebAppFileFilter();
        File[] files = deployDirectory.listFiles(filter);

        // can be null if we have a remote install dir specified for a remote server
        if ((null == files) || (0 == files.length)) {
            return new HashSet<DiscoveredResourceDetails>(0);
        }

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

        Set<DiscoveredResourceDetails> resources = new HashSet<DiscoveredResourceDetails>(files.length);
        for (File file : files) {
            // skip over anything in this directory that is not an actual web app
            if (!vhost.isWebApplication(file)) {
                continue;
            }

            String resourceName = defaultConfiguration
                .getSimple(MBeanResourceDiscoveryComponent.PROPERTY_NAME_TEMPLATE).getStringValue();
            String description = defaultConfiguration.getSimple(
                MBeanResourceDiscoveryComponent.PROPERTY_DESCRIPTION_TEMPLATE).getStringValue();
            String fileName = file.getName();
            String contextRoot = ((file.isDirectory() ? fileName : fileName.substring(0, fileName.length() - 4)));
            contextRoot = "ROOT".equals(contextRoot) ? "/" : "/" + contextRoot;
            resourceName = resourceName.replace("{contextRoot}", contextRoot);
            String name = "//" + vhostName + contextRoot;
            String resourceKey = determineResourceKey(defaultConfiguration, name);
            String objectName = resourceKey;

            DiscoveredResourceDetails resource = new DiscoveredResourceDetails(resourceType, resourceKey, resourceName,
                "", description, null, null);

            // If this app has been deployed skip it
            if (deployed.contains(resource)) {
                continue;
            }

            Configuration pluginConfiguration = resource.getPluginConfiguration();

            pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_NAME, name));
            pluginConfiguration
                .put(new PropertySimple(MBeanResourceDiscoveryComponent.PROPERTY_OBJECT_NAME, objectName));
            pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_VHOST, vhost.getName()));
            pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_CONTEXT_ROOT, contextRoot));
            try {
                pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_FILENAME, file
                    .getCanonicalPath()));
            } catch (IOException e) {
                pluginConfiguration
                    .put(new PropertySimple(TomcatWarComponent.PROPERTY_FILENAME, file.getAbsolutePath()));
            }
            pluginConfiguration.put(new PropertySimple(TomcatWarComponent.PROPERTY_RESPONSE_TIME_LOG_FILE,
                getResponseTimeLogFile(vhost.getCatalinaBase(), vhost.getName(), contextRoot)));

            resources.add(resource);
        }

        return resources;
    }

    /**
     * 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  name name of the web app
     *
     * @return resource key to use for the indicated application file
     */
    private String determineResourceKey(Configuration defaultConfiguration, String name) {
        String template = defaultConfiguration.getSimple(MBeanResourceDiscoveryComponent.PROPERTY_OBJECT_NAME)
            .getStringValue();
        String resourceKey = template.replaceAll("%name%", name);
        return CreateResourceHelper.getCanonicalName(resourceKey);
    }

    /**
     * The filename generated here must be the same as the filenames being generated by the Response Time
     * filter {see RHQ RtFilter} configured with the Tomcat Server.
     * @param installPath
     * @param vHost
     * @param contextRoot
     * @return
     */
    private String getResponseTimeLogFile(File installPath, String vHost, String contextRoot) {
        File logsDir = new File(installPath, "logs/rt");
        String rtLogFileName = null;
        if (isLocalhost(vHost)) {
            rtLogFileName = (isRoot(contextRoot) ? "ROOT" : contextRoot.substring(1)) + RT_LOG_FILE_NAME_SUFFIX;
        } else {
            rtLogFileName = vHost + (isRoot(contextRoot) ? "/ROOT" : contextRoot) + RT_LOG_FILE_NAME_SUFFIX;
        }
        rtLogFileName = rtLogFileName.replace('/', '_');
        File rtLogFile = new File(logsDir, rtLogFileName);
        String result;

        try {
            result = rtLogFile.getCanonicalPath();
        } catch (IOException e) {
            result = rtLogFile.getPath();
        }

        return result;
    }

    private boolean isLocalhost(String vHost) {
        return ("localhost".equals(vHost) || "127.0.0.1".equals(vHost));
    }

    private boolean isRoot(String contextRoot) {
        return ("/".equals(contextRoot));
    }

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

    /**
     * Filter used to find applications.
     */
    private static class WebAppFileFilter implements FileFilter {
        // Attributes  --------------------------------------------

        public WebAppFileFilter() {
        }

        public boolean accept(File pathname) {
            return pathname.isDirectory() || pathname.getPath().toLowerCase().endsWith(".war");
        }
    }
}