/* * RHQ Management Platform * Copyright (C) 2005-2008 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 as published by * the Free Software Foundation version 2 of the License. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.server.system; import java.lang.management.ManagementFactory; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.text.DateFormat; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.TimeZone; import javax.ejb.ConcurrencyManagement; import javax.ejb.ConcurrencyManagementType; import javax.ejb.EJB; import javax.ejb.Singleton; import javax.ejb.Timeout; import javax.ejb.Timer; import javax.ejb.TimerConfig; import javax.ejb.TimerService; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.management.MBeanServerInvocationHandler; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.db.DatabaseType; import org.rhq.core.db.DatabaseTypeFactory; import org.rhq.core.domain.auth.Subject; import org.rhq.core.domain.authz.Permission; import org.rhq.core.domain.common.ProductInfo; import org.rhq.core.domain.common.ServerDetails; import org.rhq.core.domain.common.ServerDetails.Detail; import org.rhq.core.domain.common.SystemConfiguration; import org.rhq.core.domain.common.composite.SystemSetting; import org.rhq.core.domain.common.composite.SystemSettings; import org.rhq.core.domain.configuration.PropertySimple; import org.rhq.core.domain.configuration.definition.PropertySimpleType; import org.rhq.core.domain.server.PersistenceUtility; import org.rhq.core.util.StopWatch; import org.rhq.core.util.obfuscation.PicketBoxObfuscator; import org.rhq.enterprise.server.RHQConstants; import org.rhq.enterprise.server.auth.SubjectManagerLocal; import org.rhq.enterprise.server.authz.RequiredPermission; import org.rhq.enterprise.server.cloud.instance.ServerManagerLocal; import org.rhq.enterprise.server.core.CoreServerMBean; import org.rhq.enterprise.server.core.CustomJaasDeploymentServiceMBean; import org.rhq.enterprise.server.plugin.pc.MasterServerPluginContainer; import org.rhq.enterprise.server.plugin.pc.ServerPluginEnvironment; import org.rhq.enterprise.server.plugin.pc.drift.DriftServerPluginContainer; import org.rhq.enterprise.server.plugin.pc.drift.DriftServerPluginManager; import org.rhq.enterprise.server.util.LookupUtil; import org.rhq.enterprise.server.util.SystemDatabaseInformation; @Singleton @ConcurrencyManagement(ConcurrencyManagementType.BEAN) public class SystemManagerBean implements SystemManagerLocal, SystemManagerRemote { private final String SQL_VACUUM = "VACUUM ANALYZE {0}"; private final String SQL_ANALYZE = "ANALYZE"; private final String SQL_REINDEX = "REINDEX TABLE {0}"; private final String SQL_REBUILD = "ALTER INDEX {0} REBUILD"; private final String[] TABLES_TO_VACUUM = { "RHQ_RESOURCE", "RHQ_CONFIG", "RHQ_CONFIG_PROPERTY", "RHQ_AGENT" }; private final String[] TABLES_TO_REINDEX = { "RHQ_MEASUREMENT_DATA_TRAIT", "RHQ_CALLTIME_DATA_KEY", "RHQ_CALLTIME_DATA_VALUE", "RHQ_AVAILABILITY" }; private final String[] ORA_INDEXES_TO_REBUILD = { "RHQ_MEAS_BASELINE_CTIME_IDX", "RHQ_MEAS_DATA_TRAIT_ID_TIME_PK" }; private static final Log LOG = LogFactory.getLog(SystemManagerBean.class); @PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME) private EntityManager entityManager; @javax.annotation.Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME) private DataSource dataSource; @javax.annotation.Resource private TimerService timerService; @EJB private SystemManagerLocal systemManager; @EJB private ServerManagerLocal serverManager; @EJB //@IgnoreDependency private SubjectManagerLocal subjectManager; @EJB //@IgnoreDependency private SystemInfoManagerLocal systemInfoManager; private SystemSettings cachedSystemSettings = null; private SystemSettings cachedObfuscatedSystemSettings = null; @Override public void scheduleConfigCacheReloader() { // each time the webapp is reloaded, we don't want to create duplicate jobs Collection<Timer> timers = timerService.getTimers(); for (Timer existingTimer : timers) { LOG.debug("Found timer - attempting to cancel: " + existingTimer.toString()); try { existingTimer.cancel(); } catch (Exception e) { LOG.warn("Failed in attempting to cancel timer: " + existingTimer.toString()); } } // timer that will trigger every 60 seconds timerService.createIntervalTimer(60000L, 60000L, new TimerConfig(null, false)); } @Timeout public void reloadConfigCache(Timer timer) { try { // reload the cache because it's fast and we'd need to make a db round-trip just to check if it's // stale. if the cache was stale then after the reload also perform any system reconfiguration // that may be necessary given changes. String oldLastUpdate = getCachedSettings().get(SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME); systemManager.loadSystemConfigurationCacheInNewTx(); String newLastUpdate = getCachedSettings().get(SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME); if (!safeEquals(oldLastUpdate, newLastUpdate)) { systemManager.reconfigureSystem(subjectManager.getOverlord()); } } catch (Throwable t) { LOG.error("Failed to reload the system config cache - will try again later. Cause: " + t); } } @Override @Deprecated @RequiredPermission(Permission.MANAGE_SETTINGS) public Properties getSystemConfiguration(Subject subject) { Properties copy = new Properties(); SystemSettings settings = getUnmaskedSystemSettings(true); for (Map.Entry<SystemSetting, String> e : settings.entrySet()) { //transform the value back to the database format, because that's //what this method always returned. //Leave the password fields as they are though, because now (as of 4.10) //the passwords are stored obfuscated, but are kept clear in memory. //The legacy behavior was to store the values in clear text, too, //so the expected output of this method is to have passwords in clear. if (e.getKey().isPublic()) { String value = e.getValue(); if (e.getKey().getType() != PropertySimpleType.PASSWORD) { value = transformSystemConfigurationPropertyToDb(e.getKey(), e.getValue(), e.getValue()); } copy.put(e.getKey().getInternalName(), value); } } return copy; } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public SystemSettings getSystemSettings(Subject subject) { SystemSettings ret = new SystemSettings(); SystemSettings unmasked = getUnmaskedSystemSettings(true); for (Map.Entry<SystemSetting, String> entry : unmasked.entrySet()) { if (entry.getKey().isPublic()) { if (entry.getKey().getType() == PropertySimpleType.PASSWORD) { entry.setValue(PropertySimple.MASKED_VALUE); } ret.put(entry.getKey(), entry.getValue()); } } return ret; } @Override public SystemSettings getUnmaskedSystemSettings(boolean includePrivateSettings) { SystemSettings ret = getCachedSettings(); return includePrivateSettings ? ret : removePrivateSettings(ret); } @Override public void deobfuscate(SystemSettings systemSettings) { for (Map.Entry<SystemSetting, String> entry : systemSettings.entrySet()) { String value = entry.getValue(); if (value != null && entry.getKey().getType() == PropertySimpleType.PASSWORD) { entry.setValue(PicketBoxObfuscator.decode(value)); } } } @Override public SystemSettings getObfuscatedSystemSettings(boolean includePrivateSettings) { SystemSettings ret = getCachedObfuscatedSettings(); return includePrivateSettings ? ret : removePrivateSettings(ret); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public void setSystemSettings(Subject subject, SystemSettings settings) { setAnySystemSettings(removePrivateSettings(settings), false, false); } @Override public void setAnySystemSetting(SystemSetting setting, String value) { if (SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME == setting) { return; } SystemSettings settings = getUnmaskedSystemSettings(true); settings.put(setting, value); setAnySystemSettings(settings, true, true); } private SystemSettings removePrivateSettings(SystemSettings settings) { SystemSettings cleansed = new SystemSettings(settings); for (SystemSetting s : SystemSetting.values()) { if (!s.isPublic()) { cleansed.remove(s); } } return cleansed; } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public void setStorageClusterSettings(Subject subject, SystemSettings settings) { for (SystemSetting setting : settings.keySet()) { if (!isStorageSetting(setting)) { throw new IllegalArgumentException(setting + " cannot be updated through this method. This method " + "only allows updating of storage cluster settings."); } } setAnySystemSettings(settings, false, true); } @Override public void setAnySystemSettings(SystemSettings settings, boolean skipValidation, boolean ignoreReadOnly) { // first, we need to get the current settings so we'll know if we need to persist or merge the new ones @SuppressWarnings("unchecked") List<SystemConfiguration> configs = entityManager.createNamedQuery(SystemConfiguration.QUERY_FIND_ALL) .getResultList(); Map<String, SystemConfiguration> existingConfigMap = new HashMap<String, SystemConfiguration>(); for (SystemConfiguration config : configs) { existingConfigMap.put(config.getPropertyKey(), config); } boolean changed = false; SystemConfiguration lastUpdateTime = existingConfigMap.get(SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME .getInternalName()); // verify each new setting and persist them to the database // note that if a new setting is the same as the old one, we do nothing - leave the old entity as is for (Map.Entry<SystemSetting, String> e : settings.entrySet()) { SystemSetting prop = e.getKey(); String value = e.getValue(); if (!skipValidation) { verifyNewSystemConfigurationProperty(prop, value, settings); } SystemConfiguration existingConfig = existingConfigMap.get(prop.getInternalName()); if (e.getKey() == SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME) { //we don't let the user persist their own last system config update time //in any manner lastUpdateTime = existingConfig; } else if (existingConfig == null) { value = transformSystemConfigurationPropertyToDb(prop, value, null); existingConfig = new SystemConfiguration(prop.getInternalName(), value); entityManager.persist(existingConfig); changed = true; existingConfigMap.put(existingConfig.getPropertyKey(), existingConfig); } else { //make sure we compare the new value with a database-agnostic value //it is important to compare in the database-agnostic format instead //of database specific because the conversion isn't reflective. //Some legacy code somewhere is (or just used to) store booleans as "0"s and "1"s //even though they are stored as strings and thus don't suffer from Oracle's //lack of support for boolean data type. If we encounter such values, we convert //them to "false"/"true" and store that value to database. This is a one way operation //and therefore we need to compare the database-agnostic (i.e. "true"/"false") and //not database specific. // //More importantly though, we store the password fields obfuscated, so we need to compare apples with //apples here. String existingValue = transformSystemConfigurationPropertyFromDb(prop, existingConfig.getPropertyValue(), true); //we need to unmask the new value so that we can compare for changes. only after that can we transform //it into the DB format. value = unmask(prop, value, existingValue); //also for oracle, treat null and empty string as the same. if ((isEmpty(existingValue) && !isEmpty(value)) || (null != existingValue && !existingValue.equals(value))) { //SystemSetting#isReadOnly should be a superset of the "fReadOnly" field in the database //but let's just be super paranoid here... if ((prop.isReadOnly() || (existingConfig.getFreadOnly() != null && existingConfig.getFreadOnly() .booleanValue())) && !(isStorageSetting(prop) || ignoreReadOnly)) { throw new IllegalArgumentException("The setting [" + prop.getInternalName() + "] is read-only - you cannot change its current value! Current value is [" + existingConfig.getPropertyValue() + "] while the new value was [" + value + "]."); } //transform to the database-specific format value = transformSystemConfigurationPropertyToDb(prop, value, existingValue); existingConfig.setPropertyValue(value); entityManager.merge(existingConfig); changed = true; } } } if (changed) { if (lastUpdateTime == null) { lastUpdateTime = new SystemConfiguration( SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME.getInternalName(), Long.toString(System .currentTimeMillis())); lastUpdateTime.setFreadOnly(SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME.isReadOnly()); entityManager.persist(lastUpdateTime); } else { lastUpdateTime.setPropertyValue(Long.toString(System.currentTimeMillis())); entityManager.merge(lastUpdateTime); } existingConfigMap.put(SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME.getInternalName(), lastUpdateTime); fillCache(existingConfigMap.values()); } } private static boolean isEmpty(String string) { return null == string || string.trim().isEmpty(); } private boolean isStorageSetting(SystemSetting setting) { switch (setting) { case STORAGE_CQL_PORT: case STORAGE_GOSSIP_PORT: case STORAGE_AUTOMATIC_DEPLOYMENT: case STORAGE_PASSWORD: case STORAGE_USERNAME: case STORAGE_REGULAR_SNAPSHOTS: case STORAGE_REGULAR_SNAPSHOTS_SCHEDULE: case STORAGE_REGULAR_SNAPSHOTS_RETENTION: case STORAGE_REGULAR_SNAPSHOTS_RETENTION_COUNT: case STORAGE_REGULAR_SNAPSHOTS_DELETION: case STORAGE_REGULAR_SNAPSHOTS_DELETION_LOCATION: return true; default: return false; } } private Map<String, String> toMap(Properties props) { HashMap<String, String> map = new HashMap<String, String>(props.size()); for (Map.Entry<Object, Object> entry : props.entrySet()) { map.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())); } return map; } private void transformToSystemSettingsFormat(Map<String, String> map) { for (Map.Entry<String, String> e : map.entrySet()) { SystemSetting prop = SystemSetting.getByInternalName(e.getKey()); if (prop != null) { //this is a legacy method that supplies values in the DB-specific format. //we therefore have to transform the values as if they came from the database. String value = transformSystemConfigurationPropertyFromDb(prop, e.getValue(), false); e.setValue(value); } } } private Map<String, String> getDriftServerPlugins() { DriftServerPluginManager pluginMgr = getDriftServerPluginManager(); Map<String, String> plugins = new HashMap<String, String>(); if (pluginMgr != null) { for (ServerPluginEnvironment env : pluginMgr.getPluginEnvironments()) { plugins.put(env.getPluginKey().getPluginName(), env.getPluginDescriptor().getDisplayName()); } } return plugins; } private DriftServerPluginManager getDriftServerPluginManager() { MasterServerPluginContainer masterPC = LookupUtil.getServerPluginService().getMasterPluginContainer(); if (masterPC == null) { LOG.warn(MasterServerPluginContainer.class.getSimpleName() + " is not started yet"); return null; } DriftServerPluginContainer pc = masterPC.getPluginContainerByClass(DriftServerPluginContainer.class); if (pc == null) { LOG.warn(DriftServerPluginContainer.class + " has not been loaded by the " + masterPC.getClass() + " yet"); return null; } return (DriftServerPluginManager) pc.getPluginManager(); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void loadSystemConfigurationCacheInNewTx() { // this is used by our timer, so any exceptions coming out here doesn't throw away our timer loadSystemConfigurationCache(); } @Override @SuppressWarnings("unchecked") public void loadSystemConfigurationCache() { // After this is done, the cachedSystemSettings contains the latest config. List<SystemConfiguration> configs = entityManager.createNamedQuery(SystemConfiguration.QUERY_FIND_ALL) .getResultList(); fillCache(configs); } @Override @Deprecated @RequiredPermission(Permission.MANAGE_SETTINGS) public void setSystemConfiguration(Subject subject, Properties properties, boolean skipValidation) throws Exception { Map<String, String> map = toMap(properties); transformToSystemSettingsFormat(map); SystemSettings settings = SystemSettings.fromMap(map); setAnySystemSettings(settings, skipValidation, false); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public void validateSystemConfiguration(Subject subject, Properties properties) throws InvalidSystemConfigurationException { Map<String, String> map = toMap(properties); transformToSystemSettingsFormat(map); SystemSettings settings = SystemSettings.fromMap(map); for (Map.Entry<SystemSetting, String> e : settings.entrySet()) { SystemSetting prop = e.getKey(); String value = e.getValue(); verifyNewSystemConfigurationProperty(prop, value, settings); } } /** * Call this to transform a system setting to a more appropriate value. * Importantly, this (de)obfuscates the password fields as they go from and to the DB. We use the * @{link PicketBoxObfuscator} so that people are able encode their passwords in the system settings export files * using the "rhq-encode-password.sh" script. */ private String transformSystemConfigurationPropertyFromDb(SystemSetting prop, String value, boolean unobfuscate) { // to support Oracle (whose booleans may be 1 or 0) transform the boolean settings properly switch (prop) { case LDAP_BASED_JAAS_PROVIDER: if (RHQConstants.JDBCJAASProvider.equals(value)) { return Boolean.toString(false); } else if (RHQConstants.LDAPJAASProvider.equals(value)) { return Boolean.toString(true); } else { return value == null ? "" : value; } case USE_SSL_FOR_LDAP: if (RHQConstants.LDAP_PROTOCOL_SECURED.equals(value)) { return Boolean.toString(true); } else { return Boolean.toString(false); } default: switch (prop.getType()) { case BOOLEAN: if ("0".equals(value)) { return Boolean.FALSE.toString(); } else if ("1".equals(value)) { return Boolean.TRUE.toString(); } else { return value == null ? Boolean.FALSE.toString() : value; } case PASSWORD: if (unobfuscate && value != null && value.trim().length() > 0) { return PicketBoxObfuscator.decode(value); } else { return value == null ? "" : value; } default: if (value == null) { switch (prop.getType()) { case DOUBLE: case FLOAT: case INTEGER: case LONG: value = "0"; break; default: value = ""; } } return value; } } } /** * Call this to transform a system setting to a more appropriate value. * Importantly, this (de)obfuscates the password fields as they go from and to the DB. We use the * @{link PicketBoxObfuscator} so that people are able encode their passwords in the system settings export files * using the "rhq-encode-password.sh" script. */ private String transformSystemConfigurationPropertyToDb(SystemSetting prop, String newValue, String oldValue) { //the 0,1 -> true false scenario is no problem here, because the values are stored //as string anyway (so no conversion is done). I assume the above is a historical //code that could be safely eliminated. switch (prop) { case LDAP_BASED_JAAS_PROVIDER: if (Boolean.parseBoolean(newValue)) { return RHQConstants.LDAPJAASProvider; } else { return RHQConstants.JDBCJAASProvider; } case USE_SSL_FOR_LDAP: if (Boolean.parseBoolean(newValue)) { return RHQConstants.LDAP_PROTOCOL_SECURED; } else { return RHQConstants.LDAP_PROTOCOL_UNSECURED; } default: if (prop.getType() == PropertySimpleType.PASSWORD && newValue != null) { if (PropertySimple.MASKED_VALUE.equals(newValue)) { return oldValue; } else { return PicketBoxObfuscator.encode(newValue); } } else { return newValue; } } } private String unmask(SystemSetting prop, String newValue, String currentValue) { if (prop.getType() == PropertySimpleType.PASSWORD && PropertySimple.MASKED_VALUE.equals(newValue)) { newValue = currentValue; } return newValue; } /** * This will make sure the given configuration has a valid value. An exception will be thrown if the given system * configuration value is invalid. This method returns if everything is OK. * * @param property name for the property * @param value value of the property * @param settings the full set of system configurations, in case the changed value needs to be compared against * other values */ private void verifyNewSystemConfigurationProperty(SystemSetting property, String value, SystemSettings settings) { if (property == SystemSetting.BASE_LINE_DATASET) { // 1h table holds at most 14 days worth of data, make sure we don't set a dataset more than that long baselineDataSet = Long.parseLong(value); if (baselineDataSet > (1000L * 60 * 60 * 24 * 14)) { throw new InvalidSystemConfigurationException("Baseline dataset must be less than 14 days"); } } else if (property == SystemSetting.BASE_LINE_FREQUENCY) { long baselineFrequency = Long.parseLong(value); long baselineDataSet = Long.parseLong(settings.get(SystemSetting.BASE_LINE_DATASET)); if (baselineFrequency > baselineDataSet) { throw new InvalidSystemConfigurationException( "baseline computation frequency must not be larger than baseline data set"); } } else if (property == SystemSetting.AGENT_MAX_QUIET_TIME_ALLOWED) { long time = Long.parseLong(value); // minimum should be 3 * the agent ping interval, any less risks unwanted backfilling if (time < 1000L * 60 * 3) { throw new InvalidSystemConfigurationException("Agent Max Quiet Time Allowed must be at least 3 minutes"); } } return; } @Override public void enableHibernateStatistics() { PersistenceUtility.enableHibernateStatistics(this.entityManager, ManagementFactory.getPlatformMBeanServer()); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public void reconfigureSystem(Subject whoami) { try { Object mbean = MBeanServerInvocationHandler.newProxyInstance(ManagementFactory.getPlatformMBeanServer(), CustomJaasDeploymentServiceMBean.OBJECT_NAME, CustomJaasDeploymentServiceMBean.class, false); ((CustomJaasDeploymentServiceMBean) mbean).installJaasModules(); } catch (Exception e) { throw new RuntimeException(e); } } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public long analyze(Subject whoami) { Connection conn = null; DatabaseType dbtype = null; try { conn = dataSource.getConnection(); dbtype = DatabaseTypeFactory.getDefaultDatabaseType(); if (!DatabaseTypeFactory.isPostgres(dbtype)) { return -1; } long duration = doCommand(dbtype, conn, SQL_ANALYZE, null); return duration; } catch (Exception e) { LOG.error("Error analyzing database", e); throw new RuntimeException("Error analyzing database", e); } finally { if (dbtype != null) { dbtype.closeConnection(conn); } } } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public long reindex(Subject whoami) { Connection conn = null; DatabaseType dbtype = null; try { conn = dataSource.getConnection(); dbtype = DatabaseTypeFactory.getDefaultDatabaseType(); long duration = 0; if (DatabaseTypeFactory.isPostgres(dbtype)) { for (String table : TABLES_TO_REINDEX) { duration += doCommand(dbtype, conn, SQL_REINDEX, table); } } else if (DatabaseTypeFactory.isOracle(dbtype)) { for (String index : ORA_INDEXES_TO_REBUILD) { duration += doCommand(dbtype, conn, SQL_REBUILD, index); } } else { return -1; } return duration; } catch (Exception e) { LOG.error("Error reindexing database", e); throw new RuntimeException("Error reindexing database", e); } finally { if (dbtype != null) { dbtype.closeConnection(conn); } } } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public long vacuum(Subject whoami) { return vacuum(whoami, null); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public long vacuum(Subject whoami, String[] tableNames) { long duration = 0; Connection conn = null; DatabaseType dbtype = null; try { conn = dataSource.getConnection(); dbtype = DatabaseTypeFactory.getDefaultDatabaseType(); if (!DatabaseTypeFactory.isPostgres(dbtype)) { return -1; } if (tableNames == null) // no names given -> operate on all tables. { tableNames = new String[1]; tableNames[0] = null; } for (String tableName : tableNames) { duration += doCommand(dbtype, conn, SQL_VACUUM, tableName); } return duration; } catch (Exception e) { LOG.error("Error vacuuming database: " + e.getMessage(), e); return duration; } finally { if (dbtype != null) { dbtype.closeConnection(conn); } } } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public long vacuumAppdef(Subject whoami) { return vacuum(whoami, TABLES_TO_VACUUM); } private long doCommand(DatabaseType dbtype, Connection conn, String command, String table) { Statement stmt = null; StopWatch watch = new StopWatch(); if (table == null) { table = ""; } command = command.replace("{0}", table); if (LOG.isDebugEnabled()) { LOG.debug("Execute command: " + command); } try { stmt = conn.createStatement(); stmt.execute(command); return watch.getElapsed(); } catch (SQLException e) { LOG.error("Error in command: " + command + ": " + e, e); return watch.getElapsed(); } finally { dbtype.closeStatement(stmt); } } /** * Ensures the installer is no longer deployed. * @deprecated */ @Override @Deprecated public void undeployInstaller() { // No need for this anymore - the new RHQ installer in AS7 detects it already installed things and will point the user to login screen // We leave this in here in case we want to re-introduce the ability to uninstall the installer. // try { // log.warn("!!!!!!!!TODO: UNDEPLOY THE INSTALLER HERE!!!!!"); // } catch (Exception e) { // log.warn("Please manually remove installer war to secure your deployment: " + e); // return; // } // // // we only get here if we are SURE we removed it! // log.info("Confirmed that the installer has been undeployed"); return; } @Override public boolean isDebugModeEnabled() { try { String setting = getUnmaskedSystemSettings(true).get(SystemSetting.DEBUG_MODE_ENABLED); if (setting == null) { setting = "false"; } return Boolean.valueOf(setting); } catch (Throwable t) { return false; // paranoid catch-all } } @Override public boolean isLoginWithoutRolesEnabled() { try { String setting = getUnmaskedSystemSettings(true).get(SystemSetting.LOGIN_WITHOUT_ROLES_ENABLED); if (setting == null) { setting = "true"; } return Boolean.valueOf(setting); } catch (Exception t) { return false; // paranoid catch-all } } @Override public boolean isExperimentalFeaturesEnabled() { try { String setting = getUnmaskedSystemSettings(true).get(SystemSetting.EXPERIMENTAL_FEATURES_ENABLED); if (setting == null) { setting = "false"; } return Boolean.valueOf(setting); } catch (Throwable t) { return false; // paranoid catch-all } } @Override public boolean isLdapAuthorizationEnabled() { SystemSettings settings = getUnmaskedSystemSettings(true); String ldapAuthValue = settings.get(SystemSetting.LDAP_BASED_JAAS_PROVIDER); boolean ldapAuth = ldapAuthValue == null ? false : Boolean.valueOf(ldapAuthValue); String groupFilter = settings.get(SystemSetting.LDAP_GROUP_FILTER); String groupMember = settings.get(SystemSetting.LDAP_GROUP_MEMBER); return ldapAuth && (((groupFilter != null) && groupFilter.trim().length() > 0) || ((groupMember != null) && groupMember .trim().length() > 0)); } @Override @RequiredPermission(Permission.MANAGE_SETTINGS) public ServerDetails getServerDetails(Subject subject) { CoreServerMBean coreServerMBean = LookupUtil.getCoreServer(); ServerDetails serverDetails = new ServerDetails(); serverDetails.setProductInfo(getProductInfo(subject)); HashMap<Detail, String> details = serverDetails.getDetails(); DateFormat localTimeFormatter = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.FULL); details.put(ServerDetails.Detail.SERVER_LOCAL_TIME, localTimeFormatter.format(new Date())); details.put(ServerDetails.Detail.SERVER_TIMEZONE, TimeZone.getDefault().getDisplayName()); details.put(ServerDetails.Detail.SERVER_HOME_DIR, coreServerMBean.getJBossServerHomeDir().getAbsolutePath()); details.put(ServerDetails.Detail.SERVER_INSTALL_DIR, coreServerMBean.getInstallDir().getAbsolutePath()); SystemDatabaseInformation dbInfo = SystemDatabaseInformation.getInstance(); details.put(ServerDetails.Detail.DATABASE_CONNECTION_URL, dbInfo.getDatabaseConnectionURL()); details.put(ServerDetails.Detail.DATABASE_DRIVER_NAME, dbInfo.getDatabaseDriverName()); details.put(ServerDetails.Detail.DATABASE_DRIVER_VERSION, dbInfo.getDatabaseDriverVersion()); details.put(ServerDetails.Detail.DATABASE_PRODUCT_NAME, dbInfo.getDatabaseProductName()); details.put(ServerDetails.Detail.DATABASE_PRODUCT_VERSION, dbInfo.getDatabaseProductVersion()); details.put(ServerDetails.Detail.SERVER_IDENTITY, serverManager.getServer().getName()); return serverDetails; } @Override public ProductInfo getProductInfo(Subject subject) { CoreServerMBean coreServer = LookupUtil.getCoreServer(); ProductInfo productInfo = coreServer.getProductInfo(); return productInfo; } @Override public void dumpSystemInfo(Subject subject) { systemInfoManager.dumpToLog(subject); } private synchronized SystemSettings getCachedSettings() { if (cachedSystemSettings == null) { loadSystemConfigurationCache(); } return new SystemSettings(cachedSystemSettings); } private synchronized SystemSettings getCachedObfuscatedSettings() { if (cachedSystemSettings == null) { loadSystemConfigurationCache(); } return new SystemSettings(cachedObfuscatedSystemSettings); } private void fillCache(Collection<SystemConfiguration> configs) { SystemSettings settings = new SystemSettings(); String[] obsoleteValues = {"PARTITION_EVENT_PURGE", "RESOURCE_CONFIG_HISTORY_PURGE"}; for (SystemConfiguration config : configs) { String configKey = config.getPropertyKey(); SystemSetting prop = SystemSetting.getByInternalName(configKey); if (prop == null && !Arrays.asList(obsoleteValues).contains(configKey) ) { LOG.warn("The database contains unknown system configuration setting [" + config.getPropertyKey() + "]."); continue; } if (config.getPropertyValue() == null) { // for some reason, the configuration is not found in the DB, so fallback to the persisted default. // if there isn't even a persisted default, just use an empty string. String defaultValue = config.getDefaultPropertyValue(); defaultValue = transformSystemConfigurationPropertyFromDb(prop, defaultValue, true); settings.put(prop, defaultValue); } else { String value = config.getPropertyValue(); value = transformSystemConfigurationPropertyFromDb(prop, value, true); settings.put(prop, value); } } settings.setDriftPlugins(getDriftServerPlugins()); synchronized (this) { //only update the caches if the settings were actually changed if (cachedSystemSettings == null || !safeEquals(cachedSystemSettings.get(SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME), settings.get(SystemSetting.LAST_SYSTEM_CONFIG_UPDATE_TIME))) { cachedSystemSettings = settings; cachedObfuscatedSystemSettings = new SystemSettings(settings); for (Map.Entry<SystemSetting, String> entry : cachedObfuscatedSystemSettings.entrySet()) { String value = entry.getValue(); if (value != null && entry.getKey().getType() == PropertySimpleType.PASSWORD) { entry.setValue(PicketBoxObfuscator.encode(value)); } } } } } private static boolean safeEquals(Object a, Object b) { return a == null ? b == null : (b != null && a.equals(b)); } }