Java tutorial
/* * RHQ Management Platform * Copyright (C) 2005-2012 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.installer; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import javax.management.MBeanServer; import javax.management.MBeanServerInvocationHandler; import javax.management.ObjectName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tools.ant.Project; import org.apache.tools.ant.helper.ProjectHelper2; import org.jboss.mx.util.MBeanServerLocator; import org.jboss.mx.util.ObjectNameFactory; import org.jboss.security.auth.login.XMLLoginConfigMBean; import org.jboss.system.server.ServerConfig; import org.rhq.core.db.DatabaseType; import org.rhq.core.db.DatabaseTypeFactory; import org.rhq.core.db.DbUtil; import org.rhq.core.db.H2DatabaseType; import org.rhq.core.db.OracleDatabaseType; import org.rhq.core.db.PostgresqlDatabaseType; import org.rhq.core.db.SQLServerDatabaseType; import org.rhq.core.db.setup.DBSetup; import org.rhq.core.util.PropertiesFileUpdate; import org.rhq.core.util.jdbc.JDBCUtil; import org.rhq.core.util.stream.StreamUtil; import org.rhq.enterprise.communications.util.SecurityUtil; /** * Finds out information about the RHQ Server and the JBossAS server it is running in. It can also be used to modify * things about the RHQ Server, such as the names of the deployment artifacts. * * @author John Mazzitelli */ @SuppressWarnings("deprecation") public class ServerInformation { private static final Log LOG = LogFactory.getLog(ServerInformation.class); private static final String DEPLOYED_EAR_FILENAME = "rhq.ear"; private static final String UNDEPLOYED_EAR_FILENAME = DEPLOYED_EAR_FILENAME + ".rej"; private static final String DEPLOYED_DS_FILENAME = "rhq-ds.xml"; private static final String UNDEPLOYED_POSTGRES_DS_FILENAME = DEPLOYED_DS_FILENAME + ".postgres.rej"; private static final String UNDEPLOYED_ORACLE_DS_FILENAME = DEPLOYED_DS_FILENAME + ".oracle.rej"; private static final String UNDEPLOYED_H2_DS_FILENAME = DEPLOYED_DS_FILENAME + ".h2.rej"; private static final String UNDEPLOYED_SQLSERVER_DS_FILENAME = DEPLOYED_DS_FILENAME + ".sqlserver.rej"; private static final String DEPLOYED_EMBEDDED_AGENT_FILENAME = "rhq-agent.sar"; private static final String UNDEPLOYED_EMBEDDED_AGENT_FILENAME = DEPLOYED_EMBEDDED_AGENT_FILENAME + ".rej"; private static final String DEPLOYED_MAIL_SERVICE_FILENAME = "mail-service.xml"; private static final String UNDEPLOYED_MAIL_SERVICE_FILENAME = DEPLOYED_MAIL_SERVICE_FILENAME + ".rej"; private static final String DEPLOYED_MDB_SERVICE_FILENAME = "rhq-mdb-service.xml"; private static final String UNDEPLOYED_MDB_SERVICE_FILENAME = DEPLOYED_MDB_SERVICE_FILENAME + ".rej"; private static final String DEPLOYED_JMS_FILENAME = "jms"; private static final String UNDEPLOYED_POSTGRES_JMS_FILENAME = "jms-postgres.rej"; private static final String UNDEPLOYED_ORACLE_JMS_FILENAME = "jms-oracle.rej"; private static final String UNDEPLOYED_H2_JMS_FILENAME = "jms-h2.rej"; private static final String UNDEPLOYED_SQLSERVER_JMS_FILENAME = "jms-sqlserver.rej"; private static final String SERVER_PROPERTIES_FILENAME = "rhq-server.properties"; private static final String PRODUCT_INFO_PROPERTIES_RESOURCE_PATH = "org/rhq/enterprise/installer/ProductInfo.properties"; private static final String JON_UNSUPPORTED_FEATURES_ENABLED_SYSPROP = "jon.unsupportedFeaturesEnabled"; private MBeanServer mbeanServer = null; private File deployDirectory = null; private File binDirectory = null; private File logDirectory = null; private File dataDirectory = null; private File confDirectory = null; private Properties productInfo = null; public enum Product { RHQ, JON } public ServerInformation() { // This is called both within the context of the GUI and the auto-install startup servlet. // Make sure you don't do anything that shouldn't be done from within the startup servlet. } /** * Returns <code>true</code> if the given set of properties provides settings that allow for a successful database * connection. If <code>props</code> is <code>null</code>, it will use the server properties from * {@link #getServerProperties()}. * * @param props set of properties where the connection information is found * * @return <code>true</code> if the database can be connected to */ public boolean isDatabaseConnectionValid(Properties props) { if (props == null) { props = getServerProperties(); } String jdbcUrl = props.getProperty(ServerProperties.PROP_DATABASE_CONNECTION_URL, "-unknown-"); String userName = props.getProperty(ServerProperties.PROP_DATABASE_USERNAME, "-unknown-"); String password = props.getProperty(ServerProperties.PROP_DATABASE_PASSWORD, "-unknown-"); return DbUtil.ping(jdbcUrl, userName, password); } /** * Call this when you need to confirm that the database is supported. * * @param props set of properties where the connection information is found * * @throws Exception if the database is not supported */ public void ensureDatabaseIsSupported(Properties props) throws Exception { Connection conn = null; DatabaseType db = null; try { conn = getDatabaseConnection(props); db = DatabaseTypeFactory.getDatabaseType(conn); String version = db.getVersion(); if (DatabaseTypeFactory.isPostgres(db)) { if (version.startsWith("7") || version.equals("8") || version.startsWith("8.0") || version.startsWith("8.1")) { throw new Exception("Unsupported PostgreSQL [" + db + "]"); } } else if (DatabaseTypeFactory.isOracle(db)) { if (version.startsWith("8") || version.startsWith("9")) { throw new Exception("Unsupported Oracle [" + db + "]"); } } else if (DatabaseTypeFactory.isH2(db)) { if (version.startsWith("1.0")) { throw new Exception("Unsupported H2 [" + db + "]"); } } else if (DatabaseTypeFactory.isSQLServer(db)) { if (version.startsWith("2000")) { throw new Exception("Unsupported SQL Server [" + db + "]"); } } else { throw new Exception("Unsupported DB [" + db + "]"); } LOG.info("Database is supported: " + db); } finally { if (db != null) { db.closeConnection(conn); } } return; } /** * Returns <code>true</code> if the database already has the database schema created for it. It will not be known * what version of schema or if its the latest, all this method tells you is that some RHQ database schema exists. * * <p>The given set of properties provides settings that allow for a successful database connection. If <code> * props</code> is <code>null</code>, it will use the server properties from {@link #getServerProperties()}.</p> * * <p>Do not call this method unless {@link #isDatabaseConnectionValid(Properties)} is <code>true</code>.</p> * * @param props set of properties where the connection information is found * * @return <code>true</code> if the database can be connected to * * @throws Exception if failed to communicate with the database */ public boolean isDatabaseSchemaExist(Properties props) throws Exception { Connection conn = getDatabaseConnection(props); DatabaseType db = DatabaseTypeFactory.getDatabaseType(conn); try { return db.checkTableExists(conn, "RHQ_PRINCIPAL"); } catch (IllegalStateException e) { return false; } finally { db.closeConnection(conn); } } /** * This will create the database schema in the database. <code>props</code> define the connection to the database - * see {@link #isDatabaseConnectionValid(Properties)}. * * <p>Note that if the {@link #isDatabaseSchemaExist(Properties) schema already exists}, it will be purged of all * data/tables and recreated.</p> * * @param props * * @throws Exception if failed to create the new schema for some reason */ public void createNewDatabaseSchema(Properties props) throws Exception { if (props == null) { props = getServerProperties(); } String jdbcUrl = props.getProperty(ServerProperties.PROP_DATABASE_CONNECTION_URL, "-unknown-"); String userName = props.getProperty(ServerProperties.PROP_DATABASE_USERNAME, "-unknown-"); String password = props.getProperty(ServerProperties.PROP_DATABASE_PASSWORD, "-unknown-"); try { // extract the dbsetup files which are located in the dbutils jar String dbsetupSchemaXmlFile = extractDatabaseXmlFile("db-schema-combined.xml", props); String dbsetupDataXmlFile = extractDatabaseXmlFile("db-data-combined.xml", props); // first uninstall any old existing schema, then create the tables then insert the data DBSetup dbsetup = new DBSetup(jdbcUrl, userName, password); dbsetup.uninstall(dbsetupSchemaXmlFile); dbsetup.setup(dbsetupSchemaXmlFile); dbsetup.setup(dbsetupDataXmlFile, null, true, false); } catch (Exception e) { LOG.fatal("Cannot install the database schema - " + getProduct() + " Server will not run properly.", e); throw e; } return; } /** * This will update an existing database schema so it can be upgraded to the latest schema version. <code> * props</code> define the connection to the database - see {@link #isDatabaseConnectionValid(Properties)}. * * <p>Note that if the {@link #isDatabaseSchemaExist(Properties) schema does not already exist}, errors will * occur.</p> * * @param props * * @throws Exception if the upgrade failed for some reason */ public void upgradeExistingDatabaseSchema(Properties props) throws Exception { if (props == null) { props = getServerProperties(); } String jdbcUrl = props.getProperty(ServerProperties.PROP_DATABASE_CONNECTION_URL, "-unknown-"); String userName = props.getProperty(ServerProperties.PROP_DATABASE_USERNAME, "-unknown-"); String password = props.getProperty(ServerProperties.PROP_DATABASE_PASSWORD, "-unknown-"); File logfile = new File(getLogDirectory(), "rhq-installer-dbupgrade.log"); logfile.delete(); // do not keep logs from previous dbupgrade runs try { // extract the dbupgrade ANT script which is located in the dbutils jar String dbupgradeXmlFile = extractDatabaseXmlFile("db-upgrade.xml", props); Properties antProps = new Properties(); antProps.setProperty("jdbc.url", jdbcUrl); antProps.setProperty("jdbc.user", userName); antProps.setProperty("jdbc.password", password); antProps.setProperty("target.schema.version", "LATEST"); startAnt(new File(dbupgradeXmlFile), "db-ant-tasks.properties", antProps, logfile); } catch (Exception e) { LOG.fatal("Cannot upgrade the database schema - " + getProduct() + " Server will not run properly.", e); throw e; } return; } /** * Gets the current set of properties currently configured for the RHQ Server. * * @return current configuration properties * * @throws RuntimeException if failed to read the properties file */ public Properties getServerProperties() { File file = getServerPropertiesFile(); FileInputStream fis = null; try { fis = new FileInputStream(file); Properties props = new Properties(); props.load(fis); return props; } catch (Exception e) { throw new RuntimeException("Cannot load configuration from [" + file + "]. Cause:" + e, e); } finally { JDBCUtil.safeClose(fis); } } /** * Writes the given properties to the RHQ Server properties file and also sets them as system properties. * * @param props the new properties * * @throws RuntimeException if failed to write the file */ public void setServerProperties(Properties props) { clearNullProperties(props); File file = getServerPropertiesFile(); try { PropertiesFileUpdate updater = new PropertiesFileUpdate(file.getAbsolutePath()); updater.update(props); } catch (Exception e) { throw new RuntimeException("Cannot store configuration to [" + file + "]. Cause:" + e, e); } // we need to put them as system properties now so when we hot deploy, // the replacement variables in the config files pick up the new values for (Map.Entry<Object, Object> entry : props.entrySet()) { System.setProperty(entry.getKey().toString(), entry.getValue().toString()); } return; } /** * This will move the RHQ Server deployment artifacts such that they either get hot-deployed or hot-undeployed in * the JBossAS server. If <code>deploy</code> is <code>true</code>, this ensures the RHQ Server deployment artifacts * (e.g. the EAR and its data source) are deployed so they can run. If <code>deploy</code> is <code>false</code>, * the caller is saying he wants the RHQ Server to be undeployed so it doesn't run anymore. * * @param deploy <code>true</code> means the RHQ Server should be deployed; otherwise, its deployment artifacts * should be undeployed * * @throws RuntimeException if failed to move one or more artifacts */ public void moveDeploymentArtifacts(boolean deploy) { try { // order is important here - do agent and data source first, then JMS, then alert cache, and then ear last // MAIL SERVICE File mail = getMailServiceFile(!deploy); File mailRenameTo = getMailServiceFile(deploy); if (!mailRenameTo.exists()) { mail.renameTo(mailRenameTo); } // EMBEDDED AGENT // if we are to deploy, then we need to rename the undeployed files; and vice versa File agent = getEmbeddedAgentFile(!deploy); File agentRenameTo = getEmbeddedAgentFile(deploy); if (!agentRenameTo.exists()) { agent.renameTo(agentRenameTo); } // DATA SOURCE // we leave the undeployed versions - there is one per supported database // to undeploy, we just delete the deployed datasource file // to deploy, we copy one of the undeployed versions for the DB to be used File ds = getDataSourceFile(true); if (deploy) { File undeployedDataSource = getDataSourceFile(false); // gets the DB specific one copySingleFile(undeployedDataSource, ds); } else { // being told to undeploy - delete the deployed datasource file if exists deleteFile(ds); } // JMS // we leave the undeployed versions - there is one per supported database // to undeploy, we just delete the deployed JMS directory // to deploy, we copy one of the undeployed version for the DB to be used File jms = getJmsFile(true); if (deploy) { File undeployedJms = getJmsFile(false); // gets the DB specific one copyDirectory(undeployedJms, jms); } else { // being told to undeploy - delete the deployed JMS directory if exists deleteFile(jms); } // MDB SERVICE File mdb = getMdbServiceFile(!deploy); File mdbRenameTo = getMdbServiceFile(deploy); if (!mdbRenameTo.exists()) { mdb.renameTo(mdbRenameTo); } // EAR File ear = getEarFile(!deploy); File earRenameTo = getEarFile(deploy); if (!earRenameTo.exists()) { ear.renameTo(earRenameTo); } } catch (Exception e) { throw new RuntimeException("Failed to move deployment artifacts. Cause: " + e, e); } return; } /** * Returns <code>true</code> if the RHQ Server deployment artifacts are fully deployed. <code>false</code> if the * installer needs to perform some work to finish the installation. * * @return installation status of RHQ Server */ public boolean isFullyDeployed() { File ear = getEarFile(true); File agent = getEmbeddedAgentFile(true); File jms = getJmsFile(true); File ds = getDataSourceFile(true); File mail = getMailServiceFile(true); File alert = getMdbServiceFile(true); return ds.exists() && jms.exists() && ear.exists() && agent.exists() && mail.exists() && alert.exists(); } private File getEarFile(boolean deployed) { File deployDir = getDeployDirectory(); File file = new File(deployDir, deployed ? DEPLOYED_EAR_FILENAME : UNDEPLOYED_EAR_FILENAME); return file; } private File getMailServiceFile(boolean deployed) { File deployDir = getDeployDirectory(); File file = new File(deployDir, deployed ? DEPLOYED_MAIL_SERVICE_FILENAME : UNDEPLOYED_MAIL_SERVICE_FILENAME); return file; } private File getMdbServiceFile(boolean deployed) { File deployDir = getDeployDirectory(); File file = new File(deployDir, deployed ? DEPLOYED_MDB_SERVICE_FILENAME : UNDEPLOYED_MDB_SERVICE_FILENAME); return file; } private File getEmbeddedAgentFile(boolean deployed) { File deployDir = getDeployDirectory(); File file = new File(deployDir, deployed ? DEPLOYED_EMBEDDED_AGENT_FILENAME : UNDEPLOYED_EMBEDDED_AGENT_FILENAME); return file; } private File getDataSourceFile(boolean deployed) { File deployDir = getDeployDirectory(); File file; if (deployed) { file = new File(deployDir, DEPLOYED_DS_FILENAME); } else { String db = getServerProperties().getProperty(ServerProperties.PROP_DATABASE_TYPE); if (db.toLowerCase().indexOf("postgres") > -1) { file = new File(deployDir, UNDEPLOYED_POSTGRES_DS_FILENAME); } else if (db.toLowerCase().indexOf("oracle") > -1) { file = new File(deployDir, UNDEPLOYED_ORACLE_DS_FILENAME); } else if (db.toLowerCase().indexOf("h2") > -1) { file = new File(deployDir, UNDEPLOYED_H2_DS_FILENAME); } else if (db.toLowerCase().indexOf("sqlserver") > -1) { file = new File(deployDir, UNDEPLOYED_SQLSERVER_DS_FILENAME); } else { throw new RuntimeException("Unsupported database: " + db); } } return file; } private File getJmsFile(boolean deployed) { File deployDir = getDeployDirectory(); File file; if (deployed) { file = new File(deployDir, DEPLOYED_JMS_FILENAME); } else { String db = getServerProperties().getProperty(ServerProperties.PROP_DATABASE_TYPE); if (db.toLowerCase().indexOf("postgres") > -1) { file = new File(deployDir, UNDEPLOYED_POSTGRES_JMS_FILENAME); } else if (db.toLowerCase().indexOf("oracle") > -1) { file = new File(deployDir, UNDEPLOYED_ORACLE_JMS_FILENAME); } else if (db.toLowerCase().indexOf("h2") > -1) { file = new File(deployDir, UNDEPLOYED_H2_JMS_FILENAME); } else if (db.toLowerCase().indexOf("sqlserver") > -1) { file = new File(deployDir, UNDEPLOYED_SQLSERVER_JMS_FILENAME); } else { throw new RuntimeException("Unsupported database: " + db); } } return file; } private void clearNullProperties(Properties props) { // some properties, if blank, must be removed so their defaults are picked up String bindAddr = props.getProperty(ServerProperties.PROP_SERVER_BIND_ADDRESS); if ((bindAddr != null) && (bindAddr.trim().length() == 0)) { props.remove(ServerProperties.PROP_SERVER_BIND_ADDRESS); } return; } private File getServerPropertiesFile() { File binDir = getBinDirectory(); File file = new File(binDir, SERVER_PROPERTIES_FILENAME); return file; } private File getDeployDirectory() { if (deployDirectory == null) { MBeanServer mbs = getMBeanServer(); ObjectName name = ObjectNameFactory.create("jboss.system:type=ServerConfig"); Object mbean = MBeanServerInvocationHandler.newProxyInstance(mbs, name, ServerConfig.class, false); deployDirectory = new File(((ServerConfig) mbean).getServerHomeDir(), "deploy"); } return deployDirectory; } private File getBinDirectory() { if (binDirectory == null) { MBeanServer mbs = getMBeanServer(); ObjectName name = ObjectNameFactory.create("jboss.system:type=ServerConfig"); Object mbean = MBeanServerInvocationHandler.newProxyInstance(mbs, name, ServerConfig.class, false); File homeDir = ((ServerConfig) mbean).getHomeDir(); binDirectory = new File(homeDir.getParentFile(), "bin"); } return binDirectory; } File getLogDirectory() { if (logDirectory == null) { MBeanServer mbs = getMBeanServer(); ObjectName name = ObjectNameFactory.create("jboss.system:type=ServerConfig"); Object mbean = MBeanServerInvocationHandler.newProxyInstance(mbs, name, ServerConfig.class, false); File homeDir = ((ServerConfig) mbean).getHomeDir(); logDirectory = new File(homeDir.getParentFile(), "logs"); logDirectory.mkdirs(); // just in case it doesn't exist yet, let's create it now } return logDirectory; } void setLogDirectory(File dir) { logDirectory = dir; } public File getDataDirectory() { if (dataDirectory == null) { MBeanServer mbs = getMBeanServer(); ObjectName name = ObjectNameFactory.create("jboss.system:type=ServerConfig"); Object mbean = MBeanServerInvocationHandler.newProxyInstance(mbs, name, ServerConfig.class, false); dataDirectory = new File(((ServerConfig) mbean).getServerHomeDir(), "data"); dataDirectory.mkdirs(); // just in case it doesn't exist yet, let's create it now } return dataDirectory; } private File getConfDirectory() { if (confDirectory == null) { MBeanServer mbs = getMBeanServer(); ObjectName name = ObjectNameFactory.create("jboss.system:type=ServerConfig"); Object mbean = MBeanServerInvocationHandler.newProxyInstance(mbs, name, ServerConfig.class, false); confDirectory = new File(((ServerConfig) mbean).getServerHomeDir(), "conf"); } return confDirectory; } private MBeanServer getMBeanServer() { if (mbeanServer == null) { mbeanServer = MBeanServerLocator.locateJBoss(); } return mbeanServer; } private void deleteFile(File file) { if (file != null) { if (file.isDirectory()) { File[] doomedFiles = file.listFiles(); if (doomedFiles != null) { for (File doomedFile : doomedFiles) { deleteFile(doomedFile); // call this method recursively } } } else { file.delete(); } } return; } private void copyDirectory(File from, File to) throws Exception { to.mkdirs(); File[] children = from.listFiles(); for (File fromChild : children) { File toChild = new File(to, fromChild.getName()); if (fromChild.isDirectory()) { copyDirectory(fromChild, toChild); } else { copySingleFile(fromChild, toChild); } } return; } private void copySingleFile(File from, File to) throws Exception { FileInputStream fis = new FileInputStream(from); FileOutputStream fos = new FileOutputStream(to); try { byte[] buf = new byte[1024]; int i = 0; while ((i = fis.read(buf)) != -1) { fos.write(buf, 0, i); } } finally { JDBCUtil.safeClose(fis); JDBCUtil.safeClose(fos); } return; } /** * Returns a database connection with the given set of properties providing the settings that allow for a successful * database connection. If <code>props</code> is <code>null</code>, it will use the server properties from * {@link #getServerProperties()}. * * @param props set of properties where the connection information is found * * @return the database connection * * @throws SQLException if cannot successfully connect to the database */ public Connection getDatabaseConnection(Properties props) throws SQLException { if (props == null) { props = getServerProperties(); } String jdbcUrl = props.getProperty(ServerProperties.PROP_DATABASE_CONNECTION_URL, "-unknown-"); String userName = props.getProperty(ServerProperties.PROP_DATABASE_USERNAME, "-unknown-"); String password = props.getProperty(ServerProperties.PROP_DATABASE_PASSWORD, "-unknown-"); return DbUtil.getConnection(jdbcUrl, userName, password); } /** * Takes the named XML file from the classloader and writes the file to the log directory. This is meant to extract * the schema/data xml files from the dbutils jar file. It can also be used to extract the db upgrade XML file. * * @param xmlFileName the name of the XML file, as found in the classloader * @param props properties whose values are used to replace the replacement strings found in the XML file * * @return the absolute path to the extracted file * * @throws IOException if failed to extract the file to the log directory */ private String extractDatabaseXmlFile(String xmlFileName, Properties props) throws IOException { // first slurp the file contents in memory InputStream resourceInStream = this.getClass().getClassLoader().getResourceAsStream(xmlFileName); ByteArrayOutputStream contentOutStream = new ByteArrayOutputStream(); StreamUtil.copy(resourceInStream, contentOutStream); // now replace their replacement strings with values from the properties String content = contentOutStream.toString(); content = content.replaceAll("@@@LARGE_TABLESPACE_FOR_DATA@@@", "DEFAULT"); content = content.replaceAll("@@@LARGE_TABLESPACE_FOR_INDEX@@@", "DEFAULT"); content = content.replaceAll("@@@ADMINUSERNAME@@@", "rhqadmin"); content = content.replaceAll("@@@ADMINPASSWORD@@@", "x1XwrxKuPvYUILiOnOZTLg=="); // rhqadmin content = content.replaceAll("@@@ADMINEMAIL@@@", props.getProperty(ServerProperties.PROP_EMAIL_FROM_ADDRESS)); content = content.replaceAll("@@@BASEURL@@@", "http://" + ServerProperties.getValidServerBindAddress(props) + ":" + ServerProperties.getHttpPort(props) + "/"); content = content.replaceAll("@@@JAASPROVIDER@@@", "JDBC"); content = content.replaceAll("@@@LDAPURL@@@", "ldap://localhost/"); content = content.replaceAll("@@@LDAPPROTOCOL@@@", ""); content = content.replaceAll("@@@LDAPLOGINPROP@@@", "cn"); content = content.replaceAll("@@@LDAPBASEDN@@@", "o=JBoss,c=US"); content = content.replaceAll("@@@LDAPSEARCHFILTER@@@", ""); content = content.replaceAll("@@@LDAPBINDDN@@@", ""); content = content.replaceAll("@@@LDAPBINDPW@@@", ""); content = content.replaceAll("@@@MULTICAST_ADDR@@@", ""); content = content.replaceAll("@@@MULTICAST_PORT@@@", ""); // we now have the finished XML content - write out the file to the log directory File xmlFile = new File(getLogDirectory(), xmlFileName); FileOutputStream xmlFileOutStream = new FileOutputStream(xmlFile); ByteArrayInputStream contentInStream = new ByteArrayInputStream(content.getBytes()); StreamUtil.copy(contentInStream, xmlFileOutStream); return xmlFile.getAbsolutePath(); } /** * Launches ANT and runs the default target in the given build file. * * @param buildFile the build file that ANT will run * @param customTaskDefs the properties file found in classloader that contains all the taskdef definitions * @param properties set of properties to set for the ANT task to access * @param logFile where ANT messages will be logged (in addition to the app server's log file) * * @throws RuntimeException */ private void startAnt(File buildFile, String customTaskDefs, Properties properties, File logFile) { PrintWriter logFileOutput = null; try { logFileOutput = new PrintWriter(new FileOutputStream(logFile)); ClassLoader classLoader = getClass().getClassLoader(); Properties taskDefs = new Properties(); InputStream taskDefsStream = classLoader.getResourceAsStream(customTaskDefs); try { taskDefs.load(taskDefsStream); } finally { taskDefsStream.close(); } Project project = new Project(); project.setCoreLoader(classLoader); project.init(); for (Map.Entry<Object, Object> property : properties.entrySet()) { project.setProperty(property.getKey().toString(), property.getValue().toString()); } // notice we add our listener after we set the properties - we do not want the password to be in the log file // our dbupgrade script will echo the property settings, so we can still get the other values project.addBuildListener(new LoggerAntBuildListener(logFileOutput)); for (Map.Entry<Object, Object> taskDef : taskDefs.entrySet()) { project.addTaskDefinition(taskDef.getKey().toString(), Class.forName(taskDef.getValue().toString(), true, classLoader)); } new ProjectHelper2().parse(project, buildFile); project.executeTarget(project.getDefaultTarget()); } catch (Exception e) { throw new RuntimeException("Cannot run ANT on script [" + buildFile + "]. Cause: " + e, e); } finally { if (logFileOutput != null) { logFileOutput.close(); } } } /** * Clean up messages in the JMS message table. Make sure you call this when no other Servers * are communicating with the database, otherwise, its possible in-flight messages will get lost * or go into a bad state. * * @param props */ public void cleanJmsTables(Properties props) { Connection conn = null; Statement stm = null; try { conn = getDatabaseConnection(props); stm = conn.createStatement(); stm.executeUpdate("DELETE FROM JMS_MESSAGES"); // TODO: we should do truncate, no? } catch (SQLException e) { LOG.info("Was not able to delete existing JMS messages: " + e.getMessage()); } finally { try { if (null != stm) stm.close(); } catch (Exception e) { // best effort only } try { if (null != conn) conn.close(); } catch (Exception e) { // best effort only } } } public void restartLoginConfig() throws Exception { MBeanServer mbs = getMBeanServer(); ObjectName name = ObjectNameFactory.create("jboss.security:service=XMLLoginConfig"); Object mbean = MBeanServerInvocationHandler.newProxyInstance(mbs, name, XMLLoginConfigMBean.class, false); XMLLoginConfigMBean conf = (XMLLoginConfigMBean) mbean; conf.stop(); conf.start(); } public static class Server { public static final String DEFAULT_AFFINITY_GROUP = ""; public static final int DEFAULT_ENDPOINT_PORT = 7080; public static final int DEFAULT_ENDPOINT_SECURE_PORT = 7443; private String name; private String endpointAddress; private int endpointPort; private int endpointSecurePort; private String affinityGroup; public Server(String name, String endpointAddress, int port, int securePort, String affinityGroup) { this.name = name; this.endpointAddress = endpointAddress; this.endpointPort = port; this.endpointSecurePort = securePort; this.affinityGroup = affinityGroup; } public String getName() { return name; } public void setName(String name) { if ((null != name) && (!"".equals(name.trim()))) { this.name = name; } } public String getEndpointAddress() { return endpointAddress; } public void setEndpointAddress(String endpointAddress) { if ((null != endpointAddress) && (!"".equals(endpointAddress.trim()))) { this.endpointAddress = endpointAddress; } } public int getEndpointPort() { return endpointPort; } public void setEndpointPort(int endpointPort) { this.endpointPort = endpointPort; } public String getEndpointPortString() { return (String.valueOf(this.endpointPort)); } public void setEndpointPortString(String endpointPort) { try { this.endpointPort = Integer.valueOf(endpointPort).intValue(); } catch (NumberFormatException e) { LOG.debug("Failed to set port with invalid number: " + endpointPort); } } public int getEndpointSecurePort() { return endpointSecurePort; } public void setEndpointSecurePort(int endpointSecurePort) { this.endpointSecurePort = endpointSecurePort; } public String getEndpointSecurePortString() { return (String.valueOf(this.endpointSecurePort)); } public void setEndpointSecurePortString(String endpointSecurePort) { try { this.endpointSecurePort = Integer.valueOf(endpointSecurePort).intValue(); } catch (NumberFormatException e) { LOG.debug("Failed to set secure port with invalid number: " + endpointSecurePort); } } public String getAffinityGroup() { return affinityGroup; } public void setAffinityGroup(String affinityGroup) { if ((null != affinityGroup) && (!"".equals(affinityGroup.trim()))) { this.affinityGroup = affinityGroup; } } @Override public String toString() { return "[name=" + name + " address=" + endpointAddress + " port=" + endpointPort + " secureport=" + endpointSecurePort + " affinitygroup=" + affinityGroup + "]"; } } /** * Get the list of existing servers from an existing RHQ schema. * * <p>The given set of properties provides settings that allow for a successful database connection. If <code> * props</code> is <code>null</code>, it will use the server properties from {@link #getServerProperties()}.</p> * * <p>Do not call this method unless {@link #isDatabaseConnectionValid(Properties)} is <code>true</code>.</p> * * @param props set of properties where the connection information is found * * @return List of server names registered in the database. Empty list if the table does not exist or there are no entries in the table. * * @throws Exception if failed to communicate with the database */ public List<String> getServerNames(Properties props) throws Exception { DatabaseType db = null; Connection conn = null; Statement stm = null; ResultSet rs = null; List<String> result = new ArrayList<String>(); try { conn = getDatabaseConnection(props); db = DatabaseTypeFactory.getDatabaseType(conn); if (db.checkTableExists(conn, "rhq_server")) { stm = conn.createStatement(); rs = stm.executeQuery("SELECT name FROM rhq_server ORDER BY name asc"); while (rs.next()) { result.add(rs.getString(1)); } } } catch (IllegalStateException e) { // table does not exist } catch (SQLException e) { LOG.info("Unable to fetch existing server info: " + e.getMessage()); } finally { if (null != db) { db.closeJDBCObjects(conn, stm, rs); } } return result; } public Product getProduct() { Properties productInfo = getProductInfo(); return (productInfo.getProperty("shortName").equals("JON")) ? Product.JON : Product.RHQ; } private Properties getProductInfo() { if (this.productInfo == null) { ClassLoader classLoader = this.getClass().getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream(PRODUCT_INFO_PROPERTIES_RESOURCE_PATH); if (inputStream != null) { Properties props = new Properties(); try { try { props.load(inputStream); } finally { inputStream.close(); } } catch (IOException e) { throw new IllegalStateException( "Failed to load product info properties from class loader resource [" + PRODUCT_INFO_PROPERTIES_RESOURCE_PATH + "]."); } return props; } else { throw new IllegalStateException( "Failed to find class loader resource [" + PRODUCT_INFO_PROPERTIES_RESOURCE_PATH + "]."); } } return this.productInfo; } public boolean isUnsupportedJonFeaturesEnabled() { return Boolean.getBoolean(JON_UNSUPPORTED_FEATURES_ENABLED_SYSPROP); } private int getAffinityGroupId(DatabaseType db, Connection conn, String affinityGroup) { PreparedStatement stm = null; ResultSet rs = null; int result = -1; if ((null == affinityGroup) || ServerInformation.Server.DEFAULT_AFFINITY_GROUP.equals(affinityGroup)) return result; try { stm = conn.prepareStatement("SELECT id FROM rhq_affinity_group WHERE name = ?"); stm.setString(1, affinityGroup.trim()); rs = stm.executeQuery(); if (rs.next()) result = rs.getInt(1); } catch (SQLException e) { LOG.info("Unable to get affinity group id: " + e.getMessage()); } finally { if (null != db) { db.closeResultSet(rs); db.closeStatement(stm); } } return result; } private String getAffinityGroup(DatabaseType db, Connection conn, String serverName) { PreparedStatement stm = null; ResultSet rs = null; String result = null; if (null == serverName) return result; try { stm = conn.prepareStatement( "SELECT ag.name FROM rhq_affinity_group ag JOIN rhq_server s ON ag.id = s.affinity_group_id WHERE s.name = ?"); stm.setString(1, serverName.trim()); rs = stm.executeQuery(); if (rs.next()) result = rs.getString(1); } catch (SQLException e) { LOG.info("Unable to get affinity group name for server: " + e.getMessage()); } finally { if (null != db) { db.closeResultSet(rs); db.closeStatement(stm); } } return result; } public String getAffinityGroupForServer(Properties props, String serverName) { DatabaseType db = null; Connection conn = null; String result = null; try { conn = getDatabaseConnection(props); db = DatabaseTypeFactory.getDatabaseType(conn); result = getAffinityGroup(db, conn, serverName); } catch (Exception e) { LOG.info("Unable to get affinity group for server: " + e.getMessage()); } finally { if (null != db) { db.closeConnection(conn); } } return result; } private void insertAffinityGroup(DatabaseType db, Connection conn, String affinityGroup) { PreparedStatement stm = null; ResultSet rs = null; // don't insert anything if the affinity group is null or empty string if (null == affinityGroup) { return; } affinityGroup = affinityGroup.trim(); if ("".equals(affinityGroup)) { return; } try { int i = 1; if (db instanceof SQLServerDatabaseType) { stm = conn.prepareStatement("INSERT INTO rhq_affinity_group ( name ) VALUES ( ? )"); } else if (db instanceof PostgresqlDatabaseType || db instanceof OracleDatabaseType || db instanceof H2DatabaseType) { stm = conn.prepareStatement("INSERT INTO rhq_affinity_group ( id, name ) VALUES ( ?, ? )"); stm.setInt(i++, db.getNextSequenceValue(conn, "rhq_affinity_group", "id")); } else { throw new IllegalArgumentException("Unknown database type, can't continue: " + db); } stm.setString(i++, affinityGroup); stm.executeUpdate(); } catch (SQLException e) { LOG.info("Unable to insert affinity group: " + e.getMessage()); } finally { if (null != db) { db.closeResultSet(rs); db.closeStatement(stm); } } } private ServerInformation.Server getServer(DatabaseType db, Connection conn, String serverName) { PreparedStatement stm = null; ResultSet rs = null; ServerInformation.Server result = null; if (null == serverName) { return result; } try { stm = conn.prepareStatement( "SELECT s.address, s.port, s.secure_port, ag.name FROM rhq_server s LEFT JOIN rhq_affinity_group ag ON ag.id = s.affinity_group_id WHERE s.name = ?"); stm.setString(1, serverName.trim()); rs = stm.executeQuery(); if (rs.next()) { result = new ServerInformation.Server(serverName, rs.getString(1), rs.getInt(2), rs.getInt(3), rs.getString(4)); } } catch (SQLException e) { LOG.info("Unable to get affinity group name for server: " + e.getMessage()); } finally { if (null != db) { db.closeResultSet(rs); db.closeStatement(stm); } } return result; } public ServerInformation.Server getServerDetail(Properties props, String serverName) { DatabaseType db = null; Connection conn = null; ServerInformation.Server result = null; try { conn = getDatabaseConnection(props); db = DatabaseTypeFactory.getDatabaseType(conn); result = getServer(db, conn, serverName); } catch (Exception e) { LOG.info("Unable to get server detail: " + e.getMessage()); } finally { if (null != db) { db.closeConnection(conn); } } return result; } private void updateServerAffinityGroup(DatabaseType db, Connection conn, String serverName, int affinityGroupId) { PreparedStatement stm = null; ResultSet rs = null; if ((affinityGroupId < 0) || (null == serverName)) { return; } try { stm = conn.prepareStatement("UPDATE rhq_server SET affinity_group_id = ? WHERE name = ?"); stm.setInt(1, affinityGroupId); stm.setString(2, serverName); stm.executeUpdate(); } catch (SQLException e) { LOG.info("Unable to set server affinity group id: " + e.getMessage()); } finally { if (null != db) { db.closeResultSet(rs); db.closeStatement(stm); } } } private void updateOrInsertServer(DatabaseType db, Connection conn, ServerInformation.Server server) { PreparedStatement stm = null; ResultSet rs = null; if (null == server || null == server.name) { return; } if ("".equals(server.name.trim())) { return; } try { stm = conn.prepareStatement("UPDATE rhq_server SET address=?, port=?, secure_port=? WHERE name=?"); stm.setString(1, server.endpointAddress); stm.setInt(2, server.endpointPort); stm.setInt(3, server.endpointSecurePort); stm.setString(4, server.name); if (0 == stm.executeUpdate()) { stm.close(); // set all new servers to operation_mode=INSTALLED int i = 1; if (db instanceof SQLServerDatabaseType) { stm = conn.prepareStatement("INSERT INTO rhq_server " // + " ( name, address, port, secure_port, ctime, mtime, operation_mode, compute_power ) " // + "VALUES ( ?, ?, ?, ?, ?, ?, 'INSTALLED', 1 )"); } else if (db instanceof PostgresqlDatabaseType || db instanceof OracleDatabaseType || db instanceof H2DatabaseType) { stm = conn.prepareStatement("INSERT INTO rhq_server " // + " ( id, name, address, port, secure_port, ctime, mtime, operation_mode, compute_power ) " // + "VALUES ( ?, ?, ?, ?, ?, ?, ?, 'INSTALLED', 1 )"); stm.setInt(i++, db.getNextSequenceValue(conn, "rhq_server", "id")); } else { throw new IllegalArgumentException("Unknown database type, can't continue: " + db); } stm.setString(i++, server.name); stm.setString(i++, server.endpointAddress); stm.setInt(i++, server.endpointPort); stm.setInt(i++, server.endpointSecurePort); long now = System.currentTimeMillis(); stm.setLong(i++, now); stm.setLong(i++, now); stm.executeUpdate(); } int affinityGroupId = getAffinityGroupId(db, conn, server.getAffinityGroup()); if (affinityGroupId < 0) { insertAffinityGroup(db, conn, server.affinityGroup); affinityGroupId = getAffinityGroupId(db, conn, server.affinityGroup); } if (affinityGroupId > 0) { this.updateServerAffinityGroup(db, conn, server.name, affinityGroupId); } } catch (SQLException e) { LOG.info("Unable to insert server: " + e.getMessage()); } finally { if (null != db) { db.closeResultSet(rs); db.closeStatement(stm); } } } public void storeServer(Properties props, ServerInformation.Server server) throws Exception { DatabaseType db = null; Connection conn = null; try { conn = getDatabaseConnection(props); db = DatabaseTypeFactory.getDatabaseType(conn); updateOrInsertServer(db, conn, server); } catch (SQLException e) { LOG.info("Unable to create server entry: " + e.getMessage()); } finally { if (null != db) { db.closeConnection(conn); } } } public void createKeystore(Server haServer) { File confDir = getConfDirectory(); File keystore = new File(confDir, "rhq.keystore"); File keystoreBackup = new File(confDir, "rhq.keystore.backup"); // if there is one out-of-box, we want to remove it and create one with our proper CN if (keystore.exists()) { keystoreBackup.delete(); if (!keystore.renameTo(keystoreBackup)) { LOG.warn("Cannot backup existing keystore - cannot generate a new cert with a proper domain name. [" + keystore + "] will be the keystore used by this server"); return; } } try { String keystorePath = keystore.getAbsolutePath(); String keyAlias = "RHQ"; String domainName = "CN=" + haServer.endpointAddress + ", OU=RHQ, O=rhq-project.org, C=US"; String keystorePassword = "RHQManagement"; String keyPassword = keystorePassword; String keyAlgorithm = "rsa"; int validity = 7300; SecurityUtil.createKeyStore(keystorePath, keyAlias, domainName, keystorePassword, keyPassword, keyAlgorithm, validity); LOG.info("New keystore created [" + keystorePath + "] with cert domain name of [" + domainName + "]"); } catch (Exception e) { LOG.warn("Could not generate a new cert with a proper domain name, will use the original keystore"); keystore.delete(); if (!keystoreBackup.renameTo(keystore)) { LOG.warn("Failed to restore the original keystore from backup - please rename [" + keystoreBackup + "] to [" + keystore + "]"); } } } }