/*
* 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.product;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.util.jdbc.DBUtil;
/**
* JDBCQueryCache is a simple caching mechanism to be used with
* JDBCMeasurementPlugins.
*
* example:
*
* mysql> select Value, Default from sys_table;
* +-----------------------------------+----------+---------+
* | Variable_name | Value | Default |
* +-----------------------------------+----------+---------+
* | DB_Max_Memory | 8192 | 1024 |
* | DB_Max_Connections | 400 | 10 |
* ...
* ...
*
* <code>
* String query = "select Value, Default from sys_table";
* JDBCQueryCache cache = new JDBCQueryCache(query, "Variable_name", 5000);
* Double val = Double.valueOf(cache.get("DB_Max_Memory", "Value").toString());
* val = Double.valueOf(cache.get("DB_Max_Connections", "Default").toString());
* </code>
*/
public class JDBCQueryCache {
private static final String _logCtx = JDBCQueryCache.class.getName();
private final Log _log = LogFactory.getLog(_logCtx);
private final Map _cache = new HashMap();
private final String _query, _queryKey;
private long _last = -1l;
private final long _cacheTimeout;
public JDBCQueryCache(String query, String queryKey, long cacheTimeout) {
_query = query;
_queryKey = queryKey;
_cacheTimeout = cacheTimeout;
}
/**
* Explicitly clears any cached value
*/
public void clearCache() {
_cache.clear();
}
/**
* Explicitly sets the expire time of the cache to expireTime. Cache will
* not repopulate until System.currentTimeMillis() <= expireTime.
*/
public void setExpireTime(long expireTime) {
_last = expireTime - _cacheTimeout;
}
/**
* @return Object representation of the *only* row and column value
* or null if it does not exist
* @throws JDBCQueryCacheException if there are 0 or > 1 rows in the cache.
*/
public Object getOnlyRow(Connection conn, String column)
throws SQLException, JDBCQueryCacheException {
final long now = System.currentTimeMillis();
if (_cache.isEmpty() || (now - _cacheTimeout) > _last) {
repopulateCache(conn);
}
final Set keys = _cache.keySet();
if (keys.size() > 1) {
throw new JDBCQueryCacheException(
"cache contains more than one row");
} else if (keys.size() <= 0) {
throw new JDBCQueryCacheException(
"cache does not contain any results");
}
List list = null;
for (final Iterator it = _cache.entrySet().iterator(); it.hasNext();) {
final Map.Entry entry = (Map.Entry)it.next();
list = (List)entry.getValue();
break;
}
if (list == null) {
return null;
}
for (final Iterator it = list.iterator(); it.hasNext();) {
final NameValuePair pair = (NameValuePair) it.next();
if (pair.getName().equals(column)) {
return pair.getValue();
}
}
throw new JDBCQueryCacheException(
"column " + column + " not found.");
}
/**
* @return Object representation of the row key/column value or null if it
* does not exist
*/
public Object get(Connection conn, String key, String column)
throws JDBCQueryCacheException, SQLException {
long now = System.currentTimeMillis();
if (_cache.isEmpty() || (now - _cacheTimeout) > _last) {
repopulateCache(conn);
}
List list = (List) _cache.get(key);
if (list == null) {
return null;
}
for (Iterator it = list.iterator(); it.hasNext();) {
NameValuePair pair = (NameValuePair) it.next();
if (pair.getName().equalsIgnoreCase(column)) {
return pair.getValue();
}
}
throw new JDBCQueryCacheException("key " + key + ", column "
+ column + " not found.");
}
private void repopulateCache(Connection conn)
throws SQLException, JDBCQueryCacheException
{
Statement stmt = null;
ResultSet rs = null;
if (_log.isDebugEnabled()) {
final String msg = "re-populating JDBCQueryCache for " + _query +
" with queryKey of " + _queryKey;
_log.debug(msg);
}
try {
_cache.clear();
stmt = conn.createStatement();
rs = stmt.executeQuery(_query);
final List columns = getQueryColumns(rs);
final boolean debug = _log.isDebugEnabled();
while (rs.next()) {
int i = 1;
final List vals = new ArrayList();
String key = null;
for (final Iterator it = columns.iterator(); it.hasNext(); i++) {
final String column = (String) it.next();
if (column.equalsIgnoreCase(_queryKey)) {
key = rs.getString(i);
} else {
String tmp = rs.getString(i);
tmp = (rs.wasNull()) ? null : tmp;
if (debug) {
_log.debug("adding nameValuePair="
+ column + "/" + tmp);
}
vals.add(new NameValuePair(column, tmp));
}
}
if (key == null) {
throw new JDBCQueryCacheException("queryKey, " + _queryKey
+ " was not represented in the query");
}
if (debug) _log.debug("adding key=" + key);
_cache.put(key, vals);
}
} catch (SQLException e) {
throw e;
} finally {
DBUtil.closeJDBCObjects(_logCtx, null, stmt, rs);
// even if a failure occurs we want to set the _last update time.
// this will alleviate the situation where there are a bunch
// of subsequent failures. These failures could potentially hold
// up the ScheduleThread
_last = System.currentTimeMillis();
}
}
private List getQueryColumns(ResultSet rs) throws SQLException {
ResultSetMetaData rsmd = rs.getMetaData();
int size = rsmd.getColumnCount();
List rtn = new ArrayList(size);
for (int i = 1; i <= size; i++) {
rtn.add(rsmd.getColumnLabel(i));
}
return rtn;
}
private class NameValuePair {
private final String _name;
private final Object _value;
public NameValuePair(String name, Object value) {
_name = name;
_value = value;
}
public String getName() {
return _name;
}
public Object getValue() {
return _value;
}
}
}