/* * 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.common.server.session; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; import java.util.Properties; 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.hibernate.SessionFactory; import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.mapping.Table; import org.hyperic.hibernate.dialect.HQDialect; import org.hyperic.hq.authz.server.session.AuthzSubject; import org.hyperic.hq.authz.shared.AuthzSubjectManager; import org.hyperic.hq.common.ApplicationException; import org.hyperic.hq.common.ConfigProperty; import org.hyperic.hq.common.SystemException; import org.hyperic.hq.common.shared.HQConstants; import org.hyperic.hq.common.shared.ServerConfigManager; import org.hyperic.hq.context.Bootstrap; import org.hyperic.hq.measurement.shared.MeasTabManagerUtil; import org.hyperic.util.ConfigPropertyException; import org.hyperic.util.StringUtil; import org.hyperic.util.jdbc.DBUtil; import org.hyperic.util.timer.StopWatch; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.LocalSessionFactoryBean; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * This class is responsible for setting/getting the server configuration */ @Service("serverConfigManager") public class ServerConfigManagerImpl implements ServerConfigManager { private static final String SQL_VACUUM = "VACUUM ANALYZE {0}"; private static final int DEFAULT_COST = 15; private final DBUtil dbUtil; private final AuthzSubjectManager authzSubjectManager; private static final String[] APPDEF_TABLES = { "EAM_PLATFORM", "EAM_SERVER", "EAM_SERVICE", "EAM_CONFIG_RESPONSE", "EAM_AGENT", "EAM_IP", "EAM_RESOURCE", "EAM_CPROP_KEY", "EAM_AUDIT", "EAM_AIQ_SERVER", "EAM_AIQ_PLATFORM", "EAM_RESOURCE_EDGE", "EAM_RES_GRP_RES_MAP" }; private static final String[] DATA_TABLES = { "EAM_MEASUREMENT_DATA_1D", "EAM_MEASUREMENT_DATA_6H", "EAM_MEASUREMENT_DATA_1H", "HQ_METRIC_DATA_COMPAT", "EAM_METRIC_PROB", "EAM_REQUEST_STAT", "EAM_ALERT_ACTION_LOG", "EAM_ALERT_CONDITION_LOG", "EAM_ALERT", "EAM_EVENT_LOG", "EAM_CPROP", "EAM_MEASUREMENT", "EAM_SRN", "HQ_AVAIL_DATA_RLE" }; public static final String LOG_CTX = "org.hyperic.hq.common.server.session.ServerConfigManagerImpl"; protected final Log log = LogFactory.getLog(LOG_CTX); private final ConfigPropertyDAO configPropertyDAO; private final ServerConfigAuditFactory serverConfigAuditFactory; private final ServerConfigCache serverConfigCache; @Autowired public ServerConfigManagerImpl(DBUtil dbUtil, AuthzSubjectManager authzSubjectManager, ConfigPropertyDAO configPropertyDAO, ServerConfigAuditFactory serverConfigAuditFactory, ServerConfigCache serverConfigCache) { this.dbUtil = dbUtil; this.authzSubjectManager = authzSubjectManager; this.configPropertyDAO = configPropertyDAO; this.serverConfigAuditFactory = serverConfigAuditFactory; this.serverConfigCache = serverConfigCache; } private void createChangeAudit(AuthzSubject subject, String key, String oldVal, String newVal) { if (key.equals(HQConstants.BaseURL)) { serverConfigAuditFactory.updateBaseURL(subject, newVal, oldVal); } else if (key.equals(HQConstants.EmailSender)) { serverConfigAuditFactory.updateFromEmail(subject, newVal, oldVal); } else if (key.equals(HQConstants.DataMaintenance)) { int oldHours = (int) (Long.parseLong(oldVal) / 60 / 60 / 1000); int newHours = (int) (Long.parseLong(newVal) / 60 / 60 / 1000); serverConfigAuditFactory.updateDBMaint(subject, newHours, oldHours); } else if (key.equals(HQConstants.DataPurgeRaw)) { int oldDays = (int) (Long.parseLong(oldVal) / 24 / 60 / 60 / 1000); int newDays = (int) (Long.parseLong(newVal) / 24 / 60 / 60 / 1000); serverConfigAuditFactory.updateDeleteDetailed(subject, newDays, oldDays); } else if (key.equals(HQConstants.AlertPurge)) { int oldPurge = (int) (Long.parseLong(oldVal) / 24 / 60 / 60 / 1000); int newPurge = (int) (Long.parseLong(newVal) / 24 / 60 / 60 / 1000); serverConfigAuditFactory.updateAlertPurgeInterval(subject, newPurge, oldPurge); } else if (key.equals(HQConstants.EventLogPurge)) { int oldPurge = (int) (Long.parseLong(oldVal) / 24 / 60 / 60 / 1000); int newPurge = (int) (Long.parseLong(newVal) / 24 / 60 / 60 / 1000); serverConfigAuditFactory.updateEventPurgeInterval(subject, newPurge, oldPurge); } else if (key.equals(HQConstants.AlertsEnabled)) { boolean oldEnabled = oldVal.equals("true"); boolean newEnabled = newVal.equals("true"); serverConfigAuditFactory.updateAlertsEnabled(subject, newEnabled, oldEnabled); } else if (key.equals(HQConstants.AlertNotificationsEnabled)) { boolean oldEnabled = oldVal.equals("true"); boolean newEnabled = newVal.equals("true"); serverConfigAuditFactory.updateAlertNotificationsEnabled(subject, newEnabled, oldEnabled); } else if (key.equals(HQConstants.HIERARCHICAL_ALERTING_ENABLED)) { boolean oldEnabled = oldVal.equals("true"); boolean newEnabled = newVal.equals("true"); serverConfigAuditFactory.updateHierarchicalAlertingEnabled(subject, newEnabled, oldEnabled); } } private void createChangeAudits(AuthzSubject subject, Collection<ConfigProperty> allProps, Properties newProps) { Properties oldProps = new Properties(); for (ConfigProperty prop : allProps) { String val = prop.getValue(); if (val == null) { val = prop.getDefaultValue(); } if (val == null) { val = ""; } oldProps.put(prop.getKey(), val); } for (Map.Entry<Object, Object> newEnt : newProps.entrySet()) { String newKey = (String) newEnt.getKey(); String newVal = (String) newEnt.getValue(); String oldVal = (String) oldProps.get(newKey); if (oldVal == null || !oldVal.equals(newVal)) { if (oldVal == null) { oldVal = ""; } createChangeAudit(subject, newKey, oldVal, newVal); } } } /** * Set the server configuration * * @throws ConfigPropertyException - if the props object is missing a key * that's currently in the database * */ @Transactional public void setConfig(AuthzSubject subject, Properties newProps) throws ApplicationException, ConfigPropertyException { setConfig(subject, null, newProps); } @Transactional public void deleteConfig(AuthzSubject subject, Set<String> toDelete) { Collection<ConfigProperty> allProps = serverConfigCache.getProps(null); for (ConfigProperty configProp : allProps) { // check if the props object has a key matching String key = configProp.getKey(); if (toDelete.contains(key)) { configPropertyDAO.remove(configProp); serverConfigCache.remove(key); } } } /** * Set the server Configuration * @param prefix The config prefix to use when setting properties. The * prefix is used for namespace protection and property scoping. * @param newProps The Properties to set. * @throws ConfigPropertyException - if the props object is missing a key * that's currently in the database * */ @Transactional public void setConfig(AuthzSubject subject, String prefix, Properties newProps) throws ApplicationException, ConfigPropertyException { Properties tempProps = new Properties(); tempProps.putAll(newProps); // get all properties Collection<ConfigProperty> allProps = serverConfigCache.getProps(prefix); createChangeAudits(subject, allProps, newProps); for (ConfigProperty configProp : allProps) { // check if the props object has a key matching String key = configProp.getKey(); if (newProps.containsKey(key)) { tempProps.remove(key); String propValue = (String) newProps.get(key); // delete null values from prefixed properties if (prefix != null && (propValue == null || propValue.equals("NULL"))) { configPropertyDAO.remove(configProp); serverConfigCache.remove(key); } else { // non-prefixed properties never get deleted. configProp.setValue(propValue); serverConfigCache.put(key, propValue); // Fix Bug 1285064/HQ-4793: Save the property in DB configPropertyDAO.save(configProp); if (HQConstants.HQGUID.equals(key)){ // Came from read only method so need to flush the session configPropertyDAO.flushSession(); if (log.isDebugEnabled()) log.debug("Saving HQ-GUID in DB [" + propValue + "]"); } } } else if (prefix == null) { // Bomb out if props are missing for non-prefixed properties throw new ConfigPropertyException("Updated configuration missing required key: " + key); } } // create properties that are still left in tempProps if (tempProps.size() > 0) { Enumeration propsToAdd = tempProps.propertyNames(); while (propsToAdd.hasMoreElements()) { String key = (String) propsToAdd.nextElement(); String propValue = tempProps.getProperty(key); // create the new property configPropertyDAO.create(prefix, key, propValue, propValue); serverConfigCache.put(key, propValue); } } } /** * Run an analyze command on all non metric tables. The metric tables are * handled seperately using analyzeHqMetricTables() so that only the tables * that have been modified are analyzed. * * @return The time taken in milliseconds to run the command. * */ @Transactional public long analyzeNonMetricTables() { HQDialect dialect = (HQDialect) ((SessionFactoryImplementor) Bootstrap.getBean(SessionFactory.class)) .getDialect(); long duration = 0; Connection conn = null; try { conn = dbUtil.getConnection(); for (Iterator i = Bootstrap.getBean(LocalSessionFactoryBean.class).getConfiguration().getTableMappings(); i.hasNext();) { Table t = (Table) i.next(); if (t.getName().toUpperCase().startsWith("EAM_MEASUREMENT_DATA") || t.getName().toUpperCase().startsWith("HQ_METRIC_DATA")) { continue; } String sql = dialect.getOptimizeStmt(t.getName(), 0); duration += doCommand(conn, sql, null); } } catch (SQLException e) { log.error("Error analyzing table", e); } finally { DBUtil.closeConnection(LOG_CTX, conn); } return duration; } /** * Run an analyze command on both the current measurement data slice and the * previous data slice if specified. * * @param analyzePrevMetricDataTable tells method to analyze previous metric * data table as well as the current. * @return The time taken in milliseconds to run the command. * */ @Transactional public long analyzeHqMetricTables(boolean analyzePrevMetricDataTable) { long systime = System.currentTimeMillis(); String currMetricDataTable = MeasTabManagerUtil.getMeasTabname(systime); long prevtime = MeasTabManagerUtil.getPrevMeasTabTime(systime); String prevMetricDataTable = MeasTabManagerUtil.getMeasTabname(prevtime); long duration = 0; HQDialect dialect = (HQDialect) ((SessionFactoryImplementor) Bootstrap.getBean(SessionFactory.class)) .getDialect(); Connection conn = null; try { String sql; conn = dbUtil.getConnection(); sql = dialect.getOptimizeStmt(currMetricDataTable, DEFAULT_COST); duration += doCommand(conn, sql, null); if (analyzePrevMetricDataTable) { sql = dialect.getOptimizeStmt(prevMetricDataTable, DEFAULT_COST); duration += doCommand(conn, sql, null); } } catch (SQLException e) { log.error("Error analyzing metric tables", e); throw new SystemException(e); } finally { DBUtil.closeConnection(LOG_CTX, conn); } return duration; } private long doCommand(Connection conn, String sql, String table) throws SQLException { Statement stmt = null; StopWatch watch = new StopWatch(); if (table == null) { table = ""; } sql = StringUtil.replace(sql, "{0}", table); if (log.isDebugEnabled()) { log.debug("Execute command: " + sql); } try { stmt = conn.createStatement(); stmt.execute(sql); return watch.getElapsed(); } finally { DBUtil.closeStatement(LOG_CTX, stmt); } } /** * Generate new GUID and set it in properties and DB * This method is called only once * * @param props server config properties * @return HQ-QUID * @author tgoldman */ @Transactional private synchronized String generateNewGUID(Properties props){ // Fix bug 1285064 / HQ-4793 String hqGUID; // Generate new GUID and set it to cache and DB if((hqGUID = GUIDGenerator.createGUID()) == null) { hqGUID = "unknown"; }else { props.setProperty(HQConstants.HQGUID, hqGUID); try { setConfig(authzSubjectManager.getOverlordPojo(), props); }catch(Exception e) { throw new SystemException(e); } } log.info("Generating a new HQ-GUID [" + hqGUID + "]"); return hqGUID; } /** * Save the HQ-QUID value in server configuration table * * @param guidValue HQ-GUID */ @Transactional private synchronized void saveGUID(String guidValue, ConfigProperty configProp){ // Fix Bug 1285064 / HQ-4793: Save the property in DB configProp.setValue(guidValue); configPropertyDAO.save(configProp); configPropertyDAO.flushSession(); if (log.isDebugEnabled()) log.debug("Saving the GUID in DB [" + guidValue + "]"); } /** * Get all the {@link ConfigProperty}s * */ @Transactional(readOnly = true) public Collection<ConfigProperty> getConfigProperties() { return configPropertyDAO.findAll(); } /** * Gets the GUID for this HQ server instance. The GUID is persistent for the * duration of an HQ install and is created upon the first call of this * method. If for some reason it can't be determined, 'unknown' will be * returned. * * */ @Transactional(readOnly = true) public String getGUID() { Properties p; String res; try { p = getConfig(); }catch(Exception e) { throw new SystemException(e); } res = p.getProperty(HQConstants.HQGUID); if(res == null || res.trim().length() == 0) { // Fix bug 1285064 / HQ-4793: generate new GUID and save it to cache and DB res = generateNewGUID(p); } if (log.isDebugEnabled()) log.debug("HQ-GUID value is [" + res + "]"); return res; } @Transactional(readOnly = true) public Properties getConfig() throws ConfigPropertyException { return serverConfigCache.getConfig(); } @Transactional(readOnly = true) public Properties getConfig(String prefix) throws ConfigPropertyException { return serverConfigCache.getConfig(prefix); } @Transactional(readOnly = true) public String getPropertyValue(String name) { return serverConfigCache.getProperty(name); } /** * * @return major part of the server version - x.x or x.x.x. * If pattern fails to match - returns the full server version. */ @Transactional(readOnly = true) public String getServerMajorVersion() { String serverVersion = serverConfigCache.getProperty(HQConstants.ServerVersion); return extractMajorVersion(serverVersion); } private String extractMajorVersion(String version) { String majorVersion = version; Pattern regex = Pattern.compile("^(\\d+)(.\\d+.\\d+)?"); Matcher regexMatcher = regex.matcher(version); if (regexMatcher.find() && (regexMatcher.groupCount() > 0)) { majorVersion = regexMatcher.group(1); // just the first digits before . // group(0) is the whole expression } return majorVersion; } }