/*
* NOTE: This copyright does *not* cover user programs that use HQ program
* services by normal system calls through the application program interfaces
* provided as part of the Hyperic Plug-in Development Kit or the Hyperic Client
* Development Kit - this is merely considered normal use of the program, and
* does *not* fall under the heading of "derived work". Copyright (C)
* [2004-2008], Hyperic, Inc. This file is part of HQ. HQ is free software; you
* can redistribute it and/or modify it under the terms version 2 of the GNU
* General Public License 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 for more details.
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place, Suite 330, Boston, MA 02111-1307 USA.
*/
package org.hyperic.hq.plugin.tomcat;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.ServerResource;
import org.hyperic.hq.product.SigarMeasurementPlugin;
import org.hyperic.hq.product.Win32ControlPlugin;
import org.hyperic.hq.product.jmx.MxServerDetector;
import org.hyperic.hq.product.jmx.MxUtil;
import org.hyperic.hq.product.pluginxml.PluginData;
import org.hyperic.sigar.win32.RegistryKey;
import org.hyperic.sigar.win32.Win32Exception;
import org.hyperic.util.config.ConfigResponse;
public class TomcatServerDetector
extends MxServerDetector
{
private static final String TOMCAT_PARAMS_KEY = "\\Parameters\\Java";
private static final String TOMCAT_SERVICE_KEY = "SOFTWARE\\Apache Software Foundation\\Procrun 2.0";
private static final String PTQL_QUERY_WIN32 = "Pid.Service.eq=%service_name%";
private static final String CATALINA_BASE_PROP = "-Dcatalina.base=";
private static final String TOMCAT_DEFAULT_URL = "service:jmx:rmi:///jndi/rmi://localhost:6969/jmxrmi";
private static final String PTQL_CONFIG_OPTION = SigarMeasurementPlugin.PTQL_CONFIG;
private static final String CATALINA_HOME_PROP = "-Dcatalina.home=";
private static final String TOMCAT_VERSION = "tomcatVersion";
private Log log = LogFactory.getLog(TomcatServerDetector.class);
private ServerResource getServerResource(String win32Service, List options) throws PluginException {
if (!isWin32ServiceRunning(win32Service)) {
log.debug(win32Service + " is not running, skipping.");
return null;
}
String path;
String[] args = (String[]) options.toArray(new String[0]);
String catalinaBase = getCatalinaBase(args);
if (catalinaBase == null) {
// no catalina base found
log.error("No Catalina Base found for service " + win32Service + ". Skipping..");
return null;
} else {
File catalinaBaseDir = new File(catalinaBase);
if (catalinaBaseDir.exists()) {
log.debug("Successfully detected Catalina Base for service: " + catalinaBase + " options=" + options);
path = catalinaBaseDir.getAbsolutePath();
} else {
log.error("Resolved catalina base " + catalinaBase +
" is not a valid directory. Skipping Tomcat service " + win32Service);
return null;
}
}
// catalina.jar file location
String catalinaJarURL = "/lib/catalina.jar";
// Check the expected tomcat version
String expectedVersion = getTypeProperty(TOMCAT_VERSION);
if(expectedVersion == null){
expectedVersion=getTypeInfo().getVersion();
}
// Tomcat 5.5: in tomcat 5.5 the lib directory located under server directory
// Add server directory to url for Tomcat 5.5
if ("1.0".equals(expectedVersion)) {
catalinaJarURL = "/server" + catalinaJarURL;
}
if (log.isDebugEnabled()) log.debug("Catalina jar url [" + catalinaBase + catalinaJarURL + "]");
if(!isCorrectVersion(catalinaBase + catalinaJarURL)){
return null;
}
ServerResource server = createServerResource(path);
// Set PTQL query
ConfigResponse config = new ConfigResponse();
config.setValue(MxUtil.PROP_JMX_URL, TOMCAT_DEFAULT_URL);
for (int i = 0; i < args.length; i++) {
if (configureMxURL(config, args[i])) {
break;
}
}
config.setValue(Win32ControlPlugin.PROP_SERVICENAME, win32Service);
config.setValue(PTQL_CONFIG_OPTION, PTQL_QUERY_WIN32);
server.setName(server.getName() + " " + win32Service);
server.setProductConfig(config);
server.setMeasurementConfig();
return server;
}
private String[] getServicesFromRegistry() {
RegistryKey key = null;
String[] services = null;
try {
key = RegistryKey.LocalMachine.openSubKey(TOMCAT_SERVICE_KEY);
services = key.getSubKeyNames();
} catch (Win32Exception e) {
// no tomcat services installed
} finally {
if (key != null) {
key.close();
}
}
return services;
}
/**
* Helper method to discover Tomcat server paths using the Windows registry
*/
private Map getServerRegistryMap() {
Map serverMap = new HashMap();
String[] services = getServicesFromRegistry();
// return empty map if no windows services are found
if (services == null) {
return serverMap;
}
for (int i = 0; i < services.length; i++) {
log.debug("Detected Tomcat service " + services[i]);
List options = new ArrayList();
RegistryKey key = null;
try {
key = RegistryKey.LocalMachine.openSubKey(TOMCAT_SERVICE_KEY + "\\" + services[i] + TOMCAT_PARAMS_KEY);
key.getMultiStringValue("Options", options);
} catch (Win32Exception e) {
log.error("Failed to find Java parameters for Tomcat service " + services[i]);
// skip current service
continue;
} finally {
if (key != null) {
key.close();
}
}
serverMap.put(services[i], options);
}
return serverMap;
}
@Override
protected boolean isInstallTypeVersion(MxProcess process) {
final String[] processArgs = process.getArgs();
String catalinaHome = getCatalinaHome(processArgs);
String catalinaBase = getCatalinaBase(processArgs);
String bootstrapJar = getBootstrapJar(processArgs);
boolean correctVersion=false;
if (bootstrapJar != null) {
// new style using bootstarp.jar meta-inf
correctVersion = isCorrectVersion(bootstrapJar);
} else {
// old style
//check catalina base first - we are using it for the process query, so it must be present
correctVersion = isInstallTypeVersion(catalinaBase);
if (!correctVersion) {
//check catalina home for version file
if (catalinaHome == null) {
getLog().warn("Unable to determine Tomcat version of possible Tomcat process with install path: "
+ process.getInstallPath()
+ ". Could not find value of catalina.home in process system properties. This process will be skipped.");
return false;
}
correctVersion = isInstallTypeVersion(catalinaHome);
}
}
if (!correctVersion) {
return false;
}
//Make sure this isn't a tc Server (if plugin present)
Iterator keys = PluginData.getGlobalProperties().keySet().iterator();
String extend_server = null;
while (keys.hasNext()) {
String key = (String) keys.next();
if (key.toUpperCase().endsWith(".EXTENDS")) {
String val = (String) PluginData.getGlobalProperties().get(key);
Pattern p = Pattern.compile(val);
Matcher m = p.matcher(getTypeInfo().getName());
boolean find = m.find();
getLog().debug("[isInstallTypeVersion] " + key + "=" + val + " (" + getTypeInfo().getName() + ") m.find()=" + find);
if (find) {
extend_server = key.substring(0, key.lastIndexOf("."));
final String tcServerVersionFile = getTypeProperty(extend_server, "VERSION_FILE");
if (tcServerVersionFile != null) {
File homeVersionFile = new File(catalinaHome, tcServerVersionFile);
File baseVersionFile = new File(catalinaBase, tcServerVersionFile);
if ((homeVersionFile.exists() || baseVersionFile.exists()) && findVersionFile(new File(catalinaBase), Pattern.compile("hq-common.*\\.jar")) == null) {
//This is a tc Server that is not the HQ server
getLog().debug("[isInstallTypeVersion] '" + getTypeInfo().getName() + " [" + process.getInstallPath() + "]' is a '" + extend_server + "'");
return false;
} else {
getLog().debug("[isInstallTypeVersion] '" + getTypeInfo().getName() + " [" + process.getInstallPath() + "]' is not a '" + extend_server + "'");
}
}
}
}
}
return true;
}
protected String getCatalinaHome(String[] args) {
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith(CATALINA_HOME_PROP)) {
return args[i].substring(CATALINA_HOME_PROP.length());
}
}
return null;
}
protected String getProcQuery(String path) {
String query = super.getProcessQuery();
if (path != null) {
query += path;
}
return query;
}
/**
* Auto scan
*/
public List getServerResources(ConfigResponse platformConfig) throws PluginException {
List servers = super.getServerResources(platformConfig);
// if we are on windows, take a look at the registry for autodiscovery
if (isWin32()) {
Map registryMap = getServerRegistryMap();
// convert registry options to server value types
for (Iterator it = registryMap.keySet().iterator(); it.hasNext();) {
String serviceName = (String) it.next();
List options = (List) registryMap.get(serviceName);
ServerResource server = getServerResource(serviceName, options);
if (server != null) {
servers.add(server);
}
}
}
//set control config for all servers
if (servers != null ){
for (Object server: servers) {
((ServerResource)server).setControlConfig();
}
}
return servers;
}
private String getBootstrapJar(String[] args) {
String res = null;
for (int i = 0; (i < args.length) && (res == null); i++) {
if (args[i].equalsIgnoreCase("-classpath")) {
String[] cp = args[i + 1].split(File.pathSeparator);
for (int c = 0; (c < cp.length) && (res == null); c++) {
if(cp[c].endsWith("bootstrap.jar")){
res = cp[c];
}
}
}
}
log.debug("[getBootstrapJar] res='"+res+"'");
return res;
}
private String getCatalinaBase(String[] args) {
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith(CATALINA_BASE_PROP)) {
return args[i].substring(CATALINA_BASE_PROP.length());
}
}
return null;
}
@Override
protected void setProductConfig(ServerResource server, ConfigResponse config, long pid) {
populateListeningPorts(pid, config,true);
super.setProductConfig(server, config);
}
@Override
protected ServerResource getServerResource(MxProcess process) {
ServerResource server = super.getServerResource(process);
String catalinaBase = server.getInstallPath();
File hq = findVersionFile(new File(catalinaBase), Pattern.compile("hq-common.*\\.jar"));
if (hq != null) {
server.setName(getPlatformName()+" Hyperic - Apache Tomcat " + getTypeInfo().getVersion());
server.setIdentifier("HQ Tomcat");
}
return server;
}
/**
* We want this ServerDetector going first to win the battle for monitoring HQ Server in an EE env
* TODO more elegant way to do this?
*/
@Override
public int getScanOrder() {
return 0;
}
private boolean isCorrectVersion(String versionJar) {
boolean correctVersion = false;
try {
JarFile jarFile = new JarFile(versionJar);
log.debug("[isInstallTypeVersion] versionJar='" + jarFile.getName() + "'");
Attributes attributes = jarFile.getManifest().getMainAttributes();
jarFile.close();
String tomcatVersion = attributes.getValue("Specification-Version");
// Tomcat 5.5: check for Manifest-Version
if (tomcatVersion == null){
tomcatVersion=attributes.getValue("Manifest-Version");
}
String expectedVersion = getTypeProperty(TOMCAT_VERSION);
if(expectedVersion==null){
expectedVersion=getTypeInfo().getVersion();
}
if (log.isDebugEnabled()){
log.debug("tomcatVersion=[" + tomcatVersion + "] expected version [" + expectedVersion + "]");
}
correctVersion = (tomcatVersion != null) ? tomcatVersion.equals(expectedVersion) : false;
} catch (IOException e) {
// Always occurred for the other supported Tomcat versions.
log.debug("Error getting Tomcat version [" + e + "]", e);
}
return correctVersion;
}
private void populateListeningPorts(long pid, ConfigResponse productConfig, boolean b) {
try {
Class du = Class.forName("org.hyperic.hq.product.DetectionUtil");
Method plp = du.getMethod("populateListeningPorts", long.class, ConfigResponse.class, boolean.class);
plp.invoke(null, pid, productConfig, b);
} catch (ClassNotFoundException ex) {
log.debug("[populateListeningPorts] Class 'DetectionUtil' not found", ex);
} catch (NoSuchMethodException ex) {
log.debug("[populateListeningPorts] Method 'populateListeningPorts' not found", ex);
} catch (Exception ex) {
log.debug("[populateListeningPorts] Problem with Method 'populateListeningPorts'", ex);
}
}
}