/** Jopr Management Platform * Copyright (C) 2005-2012 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"); } } }