/*
* 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, 2005, 2006], 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.sqlquery;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.product.JDBCMeasurementPlugin;
import org.hyperic.hq.product.LogTrackPlugin;
import org.hyperic.hq.product.Metric;
import org.hyperic.hq.product.MetricInvalidException;
import org.hyperic.hq.product.MetricNotFoundException;
import org.hyperic.hq.product.MetricUnreachableException;
import org.hyperic.hq.product.MetricValue;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.PluginManager;
import org.hyperic.hq.product.TypeInfo;
import org.hyperic.util.StringUtil;
import org.hyperic.util.config.ConfigOption;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.config.ConfigSchema;
import org.hyperic.util.config.EnumerationConfigOption;
public class SQLQueryMeasurementPlugin
extends JDBCMeasurementPlugin {
private final Log _log = LogFactory.getLog(SQLQueryMeasurementPlugin.class);
private static final String DOMAIN = "sql";
private static final String PROP_DRIVER = "jdbcDriver";
private static final String EXEC_TIME_ATTR = "QueryExecTime";
private static final String PROP_ORACLE_USER = "user";
private static final String PROP_ORACLE_PASSWORD = "password";
private static final String PROP_ORACLE_READ_TIMEOUT = "oracle.jdbc.ReadTimeout";
private static final int TIMEOUT_VALUE = 60000;
private static final String ORACLE_JDBC_URL = "jdbc:oracle";
private static final String MYSQL_JDBC_URL = "jdbc:mysql";
private String config;
private boolean isProxy = false;
private static Map loadedDrivers = new HashMap();
private static Map availableDrivers;
public void init(PluginManager manager)
throws PluginException {
super.init(manager);
if (getName().equals(DOMAIN)) {
this.isProxy = true;
this.config =
":" + getProperties().getProperty(PROP_TEMPLATE_CONFIG);
}
else if (!getManager().isRegistered(DOMAIN)) {
getLog().info("Registered proxy for domain: " + DOMAIN);
getManager().createPlugin(DOMAIN, this);
}
}
public String translate(String template, ConfigResponse config) {
if (this.isProxy) {
if (!template.endsWith(this.config)) {
template += this.config;
}
}
return super.translate(template, config);
}
//<config> defined in plugin.xml..
//overriding jdbcDriver w/ *.class from plugin.properties
public ConfigSchema getConfigSchema(TypeInfo info, ConfigResponse config) {
ConfigSchema schema = super.getConfigSchema(info, config);
ConfigOption defopt =
schema.getOption(SQLQueryMeasurementPlugin.PROP_DRIVER);
EnumerationConfigOption option =
new EnumerationConfigOption(defopt.getName(),
defopt.getDescription(),
defopt.getDefault(),
getDriverNames());
schema.addOption(option);
return schema;
}
private Map getAvailableDrivers() {
if (availableDrivers != null) {
return availableDrivers;
}
Map drivers = new HashMap();
for (Iterator it = getProperties().entrySet().iterator();
it.hasNext();)
{
Map driver;
Map.Entry entry = (Map.Entry)it.next();
String key = (String)entry.getKey();
String val = (String)entry.getValue();
int ix = key.indexOf('.');
if (ix == -1) {
continue;
}
String name = key.substring(0, ix);
String prop = key.substring(ix+1);
driver = (Map)drivers.get(name);
if (driver == null) {
driver = new HashMap();
drivers.put(name, driver);
}
driver.put(prop, val);
}
availableDrivers = new HashMap(drivers.size());
for (Iterator it = drivers.values().iterator();
it.hasNext();)
{
Map driver = (Map)it.next();
String name = (String)driver.get(PROP_DRIVER);
availableDrivers.put(name, driver);
}
return availableDrivers;
}
private String[] getDriverNames() {
Map drivers = getAvailableDrivers();
String[] names =
(String[])drivers.keySet().toArray(new String[0]);
Arrays.sort(names);
return names;
}
private String encodeHTML(String val) {
val = StringUtil.replace(val, "<", "<");
val = StringUtil.replace(val, ">", ">");
return val;
}
private void driverItem(StringBuffer help,
String key, String val) {
help.append("<p><b>").append(key).append("</b><br>");
help.append(val).append("</p>\n");
}
private String href(String url) {
return "<a href=\"" + url + "\">" + url + "</a>";
}
private String included(Map driver) {
if ("true".equals(driver.get("included"))) {
return
"<img src=\"/images/icon_available_green.gif\"> " +
"This driver is included with HQ";
}
else {
return
"<img src=\"/images/icon_available_yellow.gif\"> " +
"This driver is not included with HQ. Copy required files to the " +
"agent pdk/lib/jdbc/ directory";
}
}
public String getHelp(TypeInfo info, Map props) {
StringBuffer help = new StringBuffer();
Map drivers = getAvailableDrivers();
String[] names = getDriverNames();
for (int i=0; i<names.length; i++) {
String name = names[i];
Map driver = (Map)drivers.get(name);
String driverName = (String)driver.get(PROP_DRIVER);
if (driverName == null) {
continue;
}
String title = (String)driver.get("name");
help.append("<hr><p>\n");
help.append("<h3>").append(title).append("</h3>\n");
help.append(included(driver));
driverItem(help, "URL Format",
encodeHTML((String)driver.get(PROP_URL)));
driverItem(help, "Required File(s)",
(String)driver.get("files"));
driverItem(help, "Driver Class",
driverName);
driverItem(help, "More information and download",
href((String)driver.get("download")));
help.append("</p>\n");
}
return help.toString();
}
protected void getDriver()
throws ClassNotFoundException {}
private static synchronized boolean loadDriver(String driver) {
if (loadedDrivers.get(driver) != null) {
return true;
}
try {
loadedDrivers.put(driver, Class.forName(driver));
return true;
} catch (ClassNotFoundException e) {
// Ignore, will fail in getConnection()
return false;
}
}
private static boolean loadDriver(Properties props) {
return loadDriver(props.getProperty(PROP_DRIVER));
}
static Connection getConnection(Properties props)
throws SQLException {
loadDriver(props);
String
url = props.getProperty(PROP_URL),
user = props.getProperty(PROP_USER),
pass = props.getProperty(PROP_PASSWORD);
return getTimedOutConnection(url, user, pass);
}
protected Connection getConnection(String url,
String user,
String password)
throws SQLException {
Connection conn = getTimedOutConnection(url, user, password);
if(_log.isDebugEnabled() && conn != null){
_log.debug("Get connection for SQLPlugin, URL:" + url +" User: " + user);
}
return conn;
}
static Connection getTimedOutConnection(String url, String user, String password) throws SQLException {
// set the timeout property in a DB-specific way
// currently only Oracle and MySQL support timeout parameters in the JDBC connection
if (isOracleUrl(url)) {
// pass in timeout property for Oracle
Properties info = getOracleJDBCConnectionProperties(user, password);
return DriverManager.getConnection(url, info);
}
else if (isMySqlUrl(url)) {
// pass in timeout in URL for MySQL
url = getMySqlUrl(url);
}
return DriverManager.getConnection(url, user, password);
}
static boolean isOracleUrl(String url) {
return url != null && url.toLowerCase().startsWith(ORACLE_JDBC_URL);
}
static boolean isMySqlUrl(String url) {
return url != null && url.toLowerCase().startsWith(MYSQL_JDBC_URL);
}
// convenience method used to set timeout properties for MySql
// JDBC connection by appending a url timeout parameter
private static String getMySqlUrl(String url) {
if (url == null) {
return url;
}
else if (url.indexOf('?') > 0) {
return url + "&socketTimeout=" + TIMEOUT_VALUE + "&connectTimeout="
+ TIMEOUT_VALUE;
}
else {
return url + "?socketTimeout=" + TIMEOUT_VALUE + "&connectTimeout="
+ TIMEOUT_VALUE;
}
}
/**
* Utility method that returns an instance of Properties containing the
* given user and password keys along with the default timeout. The
* Properties instance returned can be passed in as the info argument to
* DriverManager.getConnection(url, info) when acquiring Oracle connections
*
* @param user the username for the JDBC connection
* @param password the password for the JDBC connection
* @return an instance of Properties containing the user and password JDBC
* Connection properties
*/
public static Properties getOracleJDBCConnectionProperties(String user,
String password) {
Properties info = new Properties();
if (user != null) {
info.put(PROP_ORACLE_USER, user);
}
if (password != null) {
info.put(PROP_ORACLE_PASSWORD, password);
}
info.put(PROP_ORACLE_READ_TIMEOUT, new Integer(TIMEOUT_VALUE));
return info;
}
protected String getDefaultURL() {
return "";
}
protected void initQueries() {}
protected String getQuery(Metric metric)
{
//The ObjectProperties portion is pulled directly from the plugin
//without being parsed since the query may include a ','.
String query = Metric.decode(metric.getObjectPropString());
loadDriver(metric.getProperties());
return query;
}
public MetricValue getValue(Metric metric)
throws PluginException,
MetricUnreachableException,
MetricInvalidException,
MetricNotFoundException {
final String attr = metric.getAttributeName();
MetricValue val;
Properties props = metric.getProperties();
if (!loadDriver(props)) {
String name = props.getProperty(PROP_DRIVER);
Map driver =
(Map)getAvailableDrivers().get(name);
if (driver != null) {
String msg =
"Failed to load " + PROP_DRIVER + "=" + name +
" (missing " + driver.get("files") + "?)";
throw new PluginException(msg);
}
}
long startTime = System.currentTimeMillis();
double d;
if (!attr.replaceAll("\\s+", "").equalsIgnoreCase("queryexectime")
&& !metric.isAvail()) {
d = super.getQueryValue(metric, true);
if (_sqlLog != null) {
getManager().reportEvent(
metric, now(), LogTrackPlugin.LOGLEVEL_ANY, attr, _sqlLog);
_sqlLog = null;
}
} else {
d = super.getQueryValue(metric, false);
}
val = new MetricValue(d, System.currentTimeMillis());
// Not exact, but close enough
long totalTime = System.currentTimeMillis() - startTime;
if (attr.equals(EXEC_TIME_ATTR)) {
return new MetricValue(totalTime);
} else {
return val;
}
}
private final long now() {
return System.currentTimeMillis();
}
}