org.rhq.enterprise.gui.installer.server.servlet.ServerInstallUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.rhq.enterprise.gui.installer.server.servlet.ServerInstallUtil.java

Source

/*
 * 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.gui.installer.server.servlet;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.InetAddress;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

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.as.controller.client.ModelControllerClient;
import org.jboss.dmr.ModelNode;

import org.rhq.common.jbossas.client.controller.Address;
import org.rhq.common.jbossas.client.controller.CoreJBossASClient;
import org.rhq.common.jbossas.client.controller.DatasourceJBossASClient;
import org.rhq.common.jbossas.client.controller.FailureException;
import org.rhq.common.jbossas.client.controller.InfinispanJBossASClient;
import org.rhq.common.jbossas.client.controller.JBossASClient;
import org.rhq.common.jbossas.client.controller.MessagingJBossASClient;
import org.rhq.common.jbossas.client.controller.SecurityDomainJBossASClient;
import org.rhq.common.jbossas.client.controller.SocketBindingJBossASClient;
import org.rhq.core.db.DatabaseType;
import org.rhq.core.db.DatabaseTypeFactory;
import org.rhq.core.db.DbUtil;
import org.rhq.core.db.OracleDatabaseType;
import org.rhq.core.db.PostgresqlDatabaseType;
import org.rhq.core.db.setup.DBSetup;
import org.rhq.core.util.exception.ThrowableUtil;
import org.rhq.core.util.stream.StreamUtil;
import org.rhq.enterprise.communications.util.SecurityUtil;
import org.rhq.enterprise.gui.installer.client.shared.ServerDetails;
import org.rhq.enterprise.gui.installer.client.shared.ServerProperties;

/**
 * Provides utility methods necessary to complete the server installation.
 *
 * @author John Mazzitelli
 */
public class ServerInstallUtil {
    private static final Log LOG = LogFactory.getLog(ServerInstallUtil.class);

    public enum ExistingSchemaOption {
        OVERWRITE, KEEP, SKIP
    };

    public enum SupportedDatabaseType {
        POSTGRES, ORACLE
    };

    public enum Marker {
        DO_DEPLOY(".dodeploy"), SKIP_DEPLOY(".skipdeploy"), DEPLOYED(".deployed"), FAILED(".failed"), ISDEPLOYING(
                ".isdeploying");

        private String extension;

        private Marker(String ext) {
            extension = ext;
        }

        public String getExtenstion() {
            return extension;
        }
    }

    private static class SocketBindingInfo {
        public String name;
        public String sysprop;
        public int port;

        public SocketBindingInfo(String n, String s, int p) {
            name = n;
            sysprop = s;
            port = p;
        }
    }

    private static final ArrayList<SocketBindingInfo> defaultSocketBindings;
    static {
        // all ports are -1000 from out-of-box AS7 defaults
        // except for the jboss.management ones - those are -3000 from their out-of-box defaults
        defaultSocketBindings = new ArrayList<SocketBindingInfo>();
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_AJP,
                "rhq.server.socket.binding.port.ajp", 7009));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_HTTP,
                "rhq.server.socket.binding.port.http", 7080));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_HTTPS,
                "rhq.server.socket.binding.port.https", 7443));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_JACORB,
                "rhq.server.socket.binding.port.jacorb", 2528));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_JACORB_SSL,
                "rhq.server.socket.binding.port.jacorb-ssl", 2529));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_MESSAGING,
                "rhq.server.socket.binding.port.messaging", 4445));
        defaultSocketBindings
                .add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_MESSAGING_THRUPUT,
                        "rhq.server.socket.binding.port.messaging-throughput", 4455));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_MGMT_HTTP,
                "jboss.management.http.port", 6990));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_MGMT_HTTPS,
                "jboss.management.https.port", 6443));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_MGMT_NATIVE,
                "jboss.management.native.port", 6999));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_OSGI_HTTP,
                "rhq.server.socket.binding.port.osgi-http", 7090));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_REMOTING,
                "rhq.server.socket.binding.port.remoting", 3447));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_TXN_RECOVERY_ENV,
                "rhq.server.socket.binding.port.txn-recovery-environment", 3712));
        defaultSocketBindings.add(new SocketBindingInfo(SocketBindingJBossASClient.DEFAULT_BINDING_TXN_STATUS_MGR,
                "rhq.server.socket.binding.port.txn-status-manager", 3713));
    }

    private static final String RHQ_DATASOURCE_NAME_NOTX = "NoTxRHQDS";
    private static final String RHQ_DATASOURCE_NAME_XA = "RHQDS";
    private static final String RHQ_DS_SECURITY_DOMAIN = "RHQDSSecurityDomain";
    private static final String RHQ_REST_SECURITY_DOMAIN = "RHQRESTSecurityDomain";
    private static final String JDBC_DRIVER_POSTGRES = "postgres";
    private static final String JDBC_DRIVER_ORACLE = "oracle";
    private static final String JMS_ALERT_CONDITION_QUEUE = "AlertConditionQueue";
    private static final String JMS_DRIFT_CHANGESET_QUEUE = "DriftChangesetQueue";
    private static final String JMS_DRIFT_FILE_QUEUE = "DriftFileQueue";
    private static final String RHQ_CACHE_CONTAINER = "rhq";
    private static final String RHQ_CACHE = "rhqCache";

    /**
     * Configure the deployment scanner to get ready to deploy the application.
     * @param mcc JBossAS management client
     * @throws Exception
     */
    public static void configureDeploymentScanner(ModelControllerClient mcc) throws Exception {
        CoreJBossASClient client = new CoreJBossASClient(mcc);

        // the EAR could take a long time to deploy, increase the deployment timeout
        client.setAppServerDefaultDeploymentTimeout(1800); // 30 minutes should be plenty of time

        // We don't need to scan the deployment directory after everything is installed.
        // TODO: We would like this to be 0, so it only scans at startup, but the installer
        // will need the scanner to deploy the ear after the .dodeploy is created.
        //client.setAppServerDefaultDeploymentScanInterval(0);
    }

    /**
     * Prepares the mail service by configuring the SMTP settings.
     * 
     * @param mcc JBossAS management client
     * @param serverProperties the server's properties
     * @throws Exception
     */
    public static void setupMailService(ModelControllerClient mcc, HashMap<String, String> serverProperties)
            throws Exception {

        String fromAddressExpr = "${" + ServerProperties.PROP_EMAIL_FROM_ADDRESS + ":rhqadmin@localhost.com}";
        //String smtpHostExpr = "${" + ServerProperties.PROP_EMAIL_SMTP_HOST + ":localhost}";
        String smtpPortExpr = "${" + ServerProperties.PROP_EMAIL_SMTP_PORT + ":25}";

        // Tweek the mail configuration that comes out of box. Setup a batch request to write the proper attributes.

        // First, the from address (TODO: there is also a "ssl", "username" and "password" attribute we could set for authz)
        Address addr = Address.root().add(JBossASClient.SUBSYSTEM, "mail", "mail-session",
                "java:jboss/mail/Default");
        ModelNode writeFromAddr = JBossASClient.createRequest(JBossASClient.WRITE_ATTRIBUTE, addr);
        writeFromAddr.get(JBossASClient.NAME).set("from");
        writeFromAddr.get(JBossASClient.VALUE).setExpression(fromAddressExpr);

        // now the SMTP host
        addr = Address.root().add("socket-binding-group", "standard-sockets",
                "remote-destination-outbound-socket-binding", "mail-smtp");
        ModelNode writeHost = JBossASClient.createRequest(JBossASClient.WRITE_ATTRIBUTE, addr);
        writeHost.get(JBossASClient.NAME).set("host");
        // TODO: see https://issues.jboss.org/browse/AS7-5321 - that must be fixed before supporting expressions
        //writeHost.get(JBossASClient.VALUE).setExpression(smtpHostExpr);
        writeHost.get(JBossASClient.VALUE).set(serverProperties.get(ServerProperties.PROP_EMAIL_SMTP_HOST)); // remove when AS7-5321 is fixed

        // now the SMTP port
        addr = Address.root().add("socket-binding-group", "standard-sockets",
                "remote-destination-outbound-socket-binding", "mail-smtp");
        ModelNode writePort = JBossASClient.createRequest(JBossASClient.WRITE_ATTRIBUTE, addr);
        writePort.get(JBossASClient.NAME).set("port");
        writePort.get(JBossASClient.VALUE).setExpression(smtpPortExpr);

        ModelNode batch = JBossASClient.createBatchRequest(writeFromAddr, writeHost, writePort);
        JBossASClient client = new JBossASClient(mcc);
        ModelNode response = client.execute(batch);
        if (!JBossASClient.isSuccess(response)) {
            throw new FailureException(response, "Failed to setup mail service");
        }
        LOG.info("Mail service has been configured.");
        return;
    }

    /**
     * Give the server properties, this returns the type of database that will be connected to.
     * 
     * @param serverProperties
     * @return the type of DB
     */
    public static SupportedDatabaseType getSupportedDatabaseType(HashMap<String, String> serverProperties) {
        return getSupportedDatabaseType(serverProperties.get(ServerProperties.PROP_DATABASE_TYPE));
    }

    /**
     * Give the database type string, this returns the type of database that it refers to.
     * 
     * @param dbType the database type string
     * @return the type of DB
     */
    public static SupportedDatabaseType getSupportedDatabaseType(String dbType) {
        if (dbType == null) {
            return null;
        }
        if (dbType.toLowerCase().indexOf("postgres") > -1) {
            return SupportedDatabaseType.POSTGRES;
        } else if (dbType.toLowerCase().indexOf("oracle") > -1) {
            return SupportedDatabaseType.ORACLE;
        }
        return null;
    }

    /**
     * Creates the security domain for the datasources. This is needed to support
     * obfuscation of the password in the configuration file.
     *
     * @param mcc the JBossAS management client
     * @param serverProperties contains the obfuscated password to store in the security domain
     * @throws Exception
     */
    public static void createDatasourceSecurityDomain(ModelControllerClient mcc,
            HashMap<String, String> serverProperties) throws Exception {

        final String dbUsername = serverProperties.get(ServerProperties.PROP_DATABASE_USERNAME);
        final String obfuscatedPassword = serverProperties.get(ServerProperties.PROP_DATABASE_PASSWORD);
        final SecurityDomainJBossASClient client = new SecurityDomainJBossASClient(mcc);
        final String securityDomain = RHQ_DS_SECURITY_DOMAIN;
        if (!client.isSecurityDomain(securityDomain)) {
            client.createNewSecureIdentitySecurityDomainRequest(securityDomain, dbUsername, obfuscatedPassword);
            LOG.info("Security domain [" + securityDomain + "] created");
        } else {
            LOG.info("Security domain [" + securityDomain + "] already exists, skipping the creation request");
        }
    }

    /**
     * Creates the JMS Queues required for Drift and Alerting.
     *
     * @param mcc the JBossAS management client
     * @param serverProperties contains the obfuscated password to store in the security domain
     * @throws Exception
     */
    public static void createNewJMSQueues(ModelControllerClient mcc, HashMap<String, String> serverProperties)
            throws Exception {

        final MessagingJBossASClient client = new MessagingJBossASClient(mcc);
        final List<String> entryNames = new ArrayList<String>();

        // TODO (jshaughn): Prior to HornetQ we set recoveryRetries to 0: "don't redeliver messages on failure. It
        // just causes more failures. just go straight to the dead messages by setting recoveryRetries to 0.
        // This is equivalent to setting the dLQMaxResent property to 0 in the MessageDriven annotation in
        // the class definition."
        // HornetQ has different semantics, and may behave well with default settings. If not, we'll
        // likely need to add specific <address-setting> elements for our queues, which set
        // max-delivery-attempts to 0.  The documented default is 10.

        String queueName = JMS_ALERT_CONDITION_QUEUE;
        if (!client.isQueue(queueName)) {
            entryNames.clear();
            entryNames.add("queue/" + queueName);
            ModelNode request = client.createNewQueueRequest(queueName, true, entryNames);
            ModelNode results = client.execute(request);
            if (!MessagingJBossASClient.isSuccess(results)) {
                throw new FailureException(results, "Failed to create JMS Queue [" + queueName + "]");
            } else {
                LOG.info("JMS queue [" + queueName + "] created");
            }
        } else {
            LOG.info("JMS Queue [" + queueName + "] already exists, skipping the creation request");
        }

        queueName = JMS_DRIFT_CHANGESET_QUEUE;
        if (!client.isQueue(queueName)) {
            entryNames.clear();
            entryNames.add("queue/" + queueName);
            ModelNode request = client.createNewQueueRequest(queueName, true, entryNames);
            ModelNode results = client.execute(request);
            if (!MessagingJBossASClient.isSuccess(results)) {
                throw new FailureException(results, "Failed to create JMS Queue [" + queueName + "]");
            } else {
                LOG.info("JMS queue [" + queueName + "] created");
            }
        } else {
            LOG.info("JMS Queue [" + queueName + "] already exists, skipping the creation request");
        }

        queueName = JMS_DRIFT_FILE_QUEUE;
        if (!client.isQueue(queueName)) {
            entryNames.clear();
            entryNames.add("queue/" + queueName);
            ModelNode request = client.createNewQueueRequest(queueName, true, entryNames);
            ModelNode results = client.execute(request);
            if (!MessagingJBossASClient.isSuccess(results)) {
                throw new FailureException(results, "Failed to create JMS Queue [" + queueName + "]");
            } else {
                LOG.info("JMS queue [" + queueName + "] created");
            }
        } else {
            LOG.info("JMS Queue [" + queueName + "] already exists, skipping the creation request");
        }

    }

    /**
     * Creates the security domain for REST.
     *
     * @param mcc the JBossAS management client
     * @param serverProperties contains the obfuscated password to store in the security domain
     * @throws Exception
     */
    public static void createRESTSecurityDomain(ModelControllerClient mcc, HashMap<String, String> serverProperties)
            throws Exception {

        final SecurityDomainJBossASClient client = new SecurityDomainJBossASClient(mcc);
        final String securityDomain = RHQ_REST_SECURITY_DOMAIN;
        if (!client.isSecurityDomain(securityDomain)) {
            client.createNewDatabaseServerSecurityDomainRequest(securityDomain, "java:jboss/datasources/RHQDS",
                    "SELECT PASSWORD FROM RHQ_PRINCIPAL WHERE principal=?",
                    "SELECT 'all', 'Roles' FROM RHQ_PRINCIPAL WHERE principal=?", null, null);
            LOG.info("Security domain [" + securityDomain + "] created");
        } else {
            LOG.info("Security domain [" + securityDomain + "] already exists, skipping the creation request");
        }
    }

    /**
     * Creates the Infinispan caches for RHQ.
     *
     * @param mcc the JBossAS management client
     * @param serverProperties contains the obfuscated password to store in the security domain
     * @throws Exception
     */
    public static void createNewCaches(ModelControllerClient mcc, HashMap<String, String> serverProperties)
            throws Exception {

        final InfinispanJBossASClient client = new InfinispanJBossASClient(mcc);
        final String cacheContainerName = RHQ_CACHE_CONTAINER;
        final String localCacheName = RHQ_CACHE;
        if (!client.isCacheContainer(cacheContainerName)) {
            ModelNode request = client.createNewCacheContainerRequest(cacheContainerName, localCacheName);
            ModelNode results = client.execute(request);
            if (!MessagingJBossASClient.isSuccess(results)) {
                throw new FailureException(results,
                        "Failed to create Cache container [" + cacheContainerName + "]");
            } else {
                LOG.info("Cache container [" + cacheContainerName + "] created");
            }

        } else {
            LOG.info("Cache container [" + cacheContainerName + "] already exists, skipping the creation request");
        }

        if (!client.isLocalCache(cacheContainerName, localCacheName)) {
            ModelNode request = client.createNewLocalCacheRequest(cacheContainerName, localCacheName, null, null,
                    null, null, null);
            ModelNode results = client.execute(request);
            if (!MessagingJBossASClient.isSuccess(results)) {
                throw new FailureException(results, "Failed to create Local Cache [" + localCacheName + "]");
            } else {
                LOG.info("Local Cache [" + localCacheName + "] created");
            }

        } else {
            LOG.info("Local Cache [" + localCacheName + "] already exists, skipping the creation request");
        }
    }

    /**
     * Creates JDBC driver configurations so the datasources can properly connect to the backend databases.
     * This will attempt to create drivers for all supported databases, not just for the database type that
     * is currently configured.
     *
     * @param mcc
     * @param serverProperties
     * @throws Exception
     */
    public static void createNewJdbcDrivers(ModelControllerClient mcc, HashMap<String, String> serverProperties)
            throws Exception {

        final DatasourceJBossASClient client = new DatasourceJBossASClient(mcc);

        final ModelNode postgresDriverRequest = client.createNewJdbcDriverRequest(JDBC_DRIVER_POSTGRES,
                "org.rhq.postgres", "org.postgresql.xa.PGXADataSource");
        final ModelNode oracleDriverRequest = client.createNewJdbcDriverRequest(JDBC_DRIVER_ORACLE,
                "org.rhq.oracle", "oracle.jdbc.xa.client.OracleXADataSource");

        // if we are to use Oracle, we throw an exception if we can't create the Oracle datasource. We also try to
        // create the Postgres datasource but because it isn't needed, we don't throw exceptions if that fails, we
        // just log a warning.
        // The reverse is true if we are to use Postgres (that is, we ensure Postgres driver is created, but not Oracle).
        ModelNode results;
        final SupportedDatabaseType supportedDbType = getSupportedDatabaseType(serverProperties);
        switch (supportedDbType) {
        case POSTGRES: {
            if (client.isJDBCDriver(JDBC_DRIVER_POSTGRES)) {
                LOG.info("Postgres JDBC driver is already deployed");
            } else {
                results = client.execute(postgresDriverRequest);
                if (!DatasourceJBossASClient.isSuccess(results)) {
                    throw new FailureException(results, "Failed to create postgres database driver");
                } else {
                    LOG.info("Deployed Postgres JDBC driver");
                }
            }

            if (client.isJDBCDriver(JDBC_DRIVER_ORACLE)) {
                LOG.info("Oracle JDBC driver is already deployed");
            } else {
                results = client.execute(oracleDriverRequest);
                if (!DatasourceJBossASClient.isSuccess(results)) {
                    LOG.warn(
                            "Could not create Oracle JDBC Driver - you will not be able to switch to an Oracle DB later: "
                                    + JBossASClient.getFailureDescription(results));
                } else {
                    LOG.info("Deployed Oracle JDBC driver for future use");
                }
            }
            break;
        }
        case ORACLE: {
            if (client.isJDBCDriver(JDBC_DRIVER_ORACLE)) {
                LOG.info("Oracle JDBC driver is already deployed");
            } else {
                results = client.execute(oracleDriverRequest);
                if (!DatasourceJBossASClient.isSuccess(results)) {
                    throw new FailureException(results, "Failed to create oracle database driver");
                } else {
                    LOG.info("Deployed Oracle JDBC driver");
                }
            }
            if (client.isJDBCDriver(JDBC_DRIVER_POSTGRES)) {
                LOG.info("Postgres JDBC driver is already deployed");
            } else {
                results = client.execute(postgresDriverRequest);
                if (!DatasourceJBossASClient.isSuccess(results)) {
                    LOG.warn(
                            "Could not create Postgres JDBC Driver - you will not be able to switch to a Postgres DB later: "
                                    + JBossASClient.getFailureDescription(results));
                } else {
                    LOG.info("Deployed Postgres JDBC driver for future use");
                }
            }
            break;
        }
        default:
            throw new RuntimeException("bad db type"); // this should never happen; should have never gotten to this point with a bad type
        }

    }

    /**
     * Creates the datasources needed by the RHQ Server.
     *
     * @param mcc the JBossAS management client
     * @param serverProperties properties to help determine the properties of the datasources to be created
     * @throws Exception
     */
    public static void createNewDatasources(ModelControllerClient mcc, HashMap<String, String> serverProperties)
            throws Exception {

        final SupportedDatabaseType supportedDbType = getSupportedDatabaseType(serverProperties);
        switch (supportedDbType) {
        case POSTGRES: {
            createNewDatasources_Postgres(mcc);
            break;
        }
        case ORACLE: {
            createNewDatasources_Oracle(mcc);
            break;
        }
        default:
            throw new RuntimeException("bad db type"); // this should never happen; should have never gotten to this point with a bad type
        }

        LOG.info("Created datasources");

        final DatasourceJBossASClient client = new DatasourceJBossASClient(mcc);
        client.enableDatasource(RHQ_DATASOURCE_NAME_NOTX);
        client.enableXADatasource(RHQ_DATASOURCE_NAME_XA);
        LOG.info("Enabled datasources");

    }

    private static void createNewDatasources_Postgres(ModelControllerClient mcc) throws Exception {
        final HashMap<String, String> props = new HashMap<String, String>(4);
        final DatasourceJBossASClient client = new DatasourceJBossASClient(mcc);

        ModelNode noTxDsRequest = null;
        ModelNode xaDsRequest = null;

        if (!client.isDatasource(RHQ_DATASOURCE_NAME_NOTX)) {
            props.put("char.encoding", "UTF-8");

            noTxDsRequest = client.createNewDatasourceRequest(RHQ_DATASOURCE_NAME_NOTX, 30000,
                    "${rhq.server.database.connection-url:jdbc:postgres://127.0.0.1:5432/rhq}",
                    JDBC_DRIVER_POSTGRES,
                    "org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter", 15, false, 2, 5,
                    75, RHQ_DS_SECURITY_DOMAIN, "-unused-stale-conn-checker-", "TRANSACTION_READ_COMMITTED",
                    "org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker", props);
            noTxDsRequest.get("steps").get(0).remove("stale-connection-checker-class-name"); // we don't have one of these for postgres
        } else {
            LOG.info("Postgres datasource [" + RHQ_DATASOURCE_NAME_NOTX + "] already exists");
        }

        if (!client.isXADatasource(RHQ_DATASOURCE_NAME_XA)) {
            props.clear();
            props.put("ServerName", "${rhq.server.database.server-name:127.0.0.1}");
            props.put("PortNumber", "${rhq.server.database.port:5432}");
            props.put("DatabaseName", "${rhq.server.database.db-name:rhq}");

            xaDsRequest = client.createNewXADatasourceRequest(RHQ_DATASOURCE_NAME_XA, 30000, JDBC_DRIVER_POSTGRES,
                    "org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter", 15, 5, 50, 75,
                    RHQ_DS_SECURITY_DOMAIN, "-unused-stale-conn-checker-", "TRANSACTION_READ_COMMITTED",
                    "org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker", props);
            xaDsRequest.get("steps").get(0).remove("stale-connection-checker-class-name"); // we don't have one of these for postgres
        } else {
            LOG.info("Postgres XA datasource [" + RHQ_DATASOURCE_NAME_XA + "] already exists");
        }

        if (noTxDsRequest != null || xaDsRequest != null) {
            ModelNode batch = DatasourceJBossASClient.createBatchRequest(noTxDsRequest, xaDsRequest);
            ModelNode results = client.execute(batch);
            if (!DatasourceJBossASClient.isSuccess(results)) {
                throw new FailureException(results, "Failed to create Postgres datasources");
            }
        }
    }

    private static void createNewDatasources_Oracle(ModelControllerClient mcc) throws Exception {
        final HashMap<String, String> props = new HashMap<String, String>(2);
        final DatasourceJBossASClient client = new DatasourceJBossASClient(mcc);

        ModelNode noTxDsRequest = null;
        ModelNode xaDsRequest = null;

        if (!client.isDatasource(RHQ_DATASOURCE_NAME_NOTX)) {
            props.put("char.encoding", "UTF-8");
            props.put("SetBigStringTryClob", "true");

            noTxDsRequest = client.createNewDatasourceRequest(RHQ_DATASOURCE_NAME_NOTX, 30000,
                    "${rhq.server.database.connection-url:jdbc:oracle:thin:@127.0.0.1:1521:rhq}",
                    JDBC_DRIVER_ORACLE, "org.jboss.jca.adapters.jdbc.extensions.oracle.OracleExceptionSorter", 15,
                    false, 2, 5, 75, RHQ_DS_SECURITY_DOMAIN,
                    "org.jboss.jca.adapters.jdbc.extensions.oracle.OracleStaleConnectionChecker",
                    "TRANSACTION_READ_COMMITTED",
                    "org.jboss.jca.adapters.jdbc.extensions.oracle.OracleValidConnectionChecker", props);
        } else {
            LOG.info("Oracle datasource [" + RHQ_DATASOURCE_NAME_NOTX + "] already exists");
        }

        if (!client.isDatasource(RHQ_DATASOURCE_NAME_XA)) {
            props.clear();
            props.put("URL", "${rhq.server.database.connection-url:jdbc:oracle:thin:@127.0.0.1:1521:rhq}");
            props.put("ConnectionProperties", "SetBigStringTryClob=true");

            xaDsRequest = client.createNewXADatasourceRequest(RHQ_DATASOURCE_NAME_XA, 30000, JDBC_DRIVER_ORACLE,
                    "org.jboss.jca.adapters.jdbc.extensions.oracle.OracleExceptionSorter", 15, 5, 50, 75,
                    RHQ_DS_SECURITY_DOMAIN,
                    "org.jboss.jca.adapters.jdbc.extensions.oracle.OracleStaleConnectionChecker",
                    "TRANSACTION_READ_COMMITTED",
                    "org.jboss.jca.adapters.jdbc.extensions.oracle.OracleValidConnectionChecker", props);
        } else {
            LOG.info("Oracle XA datasource [" + RHQ_DATASOURCE_NAME_XA + "] already exists");
        }

        if (noTxDsRequest != null || xaDsRequest != null) {
            ModelNode batch = DatasourceJBossASClient.createBatchRequest(noTxDsRequest, xaDsRequest);
            ModelNode results = client.execute(batch);
            if (!DatasourceJBossASClient.isSuccess(results)) {
                throw new FailureException(results, "Failed to create Oracle datasources");
            }
        }
    }

    /**
     * Determines if we are in auto-install mode. This means the properties file is
     * fully configured and the installation can begin without asking the user
     * for more input.
     *
     * @param serverProperties the full set of server properties
     *
     * @return true if we are in auto-install mode; false if the user must give us more
     *         information before we can complete the installation.
     */
    public static boolean isAutoinstallEnabled(HashMap<String, String> serverProperties) {
        String enableProp = serverProperties.get(ServerProperties.PROP_AUTOINSTALL_ENABLE);
        if (enableProp != null) {
            return Boolean.parseBoolean(enableProp);
        }
        return false;
    }

    /**
     * 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.
     *
     * @param connectionUrl
     * @param username
     * @param password
     * @return <code>true</code> if the database can be connected to
     *
     * @throws Exception if failed to communicate with the database
     */
    public static boolean isDatabaseSchemaExist(String connectionUrl, String username, String password)
            throws Exception {

        Connection conn = getDatabaseConnection(connectionUrl, username, password);
        DatabaseType db = DatabaseTypeFactory.getDatabaseType(conn);

        try {
            return db.checkTableExists(conn, "RHQ_PRINCIPAL");
        } catch (IllegalStateException e) {
            return false;
        } finally {
            db.closeConnection(conn);
        }
    }

    /**
     * Get the list of existing servers from an existing schema.
     *
     * @param connectionUrl
     * @param username
     * @param password
     * @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 static ArrayList<String> getServerNames(String connectionUrl, String username, String password)
            throws Exception {
        DatabaseType db = null;
        Connection conn = null;
        Statement stm = null;
        ResultSet rs = null;
        ArrayList<String> result = new ArrayList<String>();

        try {
            conn = getDatabaseConnection(connectionUrl, username, password);
            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;
    }

    /**
     * Returns information on the server as found in the database (port numbers, affinity group, etc).
     *
     * @param connectionUrl
     * @param username
     * @param password
     * @param serverName the server whose details are to be returned
     * @return the information on the named server
     */
    public static ServerDetails getServerDetails(String connectionUrl, String username, String password,
            String serverName) {

        DatabaseType db = null;
        Connection conn = null;
        ServerDetails result = null;

        try {
            conn = getDatabaseConnection(connectionUrl, username, password);
            db = DatabaseTypeFactory.getDatabaseType(conn);

            result = getServerDetails(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 static ServerDetails getServerDetails(DatabaseType db, Connection conn, String serverName) {
        PreparedStatement stm = null;
        ResultSet rs = null;
        ServerDetails result = null;

        if (null == serverName) {
            return result;
        }

        try {
            stm = conn.prepareStatement("" //
                    + "SELECT s.address, s.port, s.secure_port " //
                    + "  FROM rhq_server s " //
                    + " WHERE s.name = ?");
            stm.setString(1, serverName.trim());

            rs = stm.executeQuery();

            if (rs.next()) {
                result = new ServerDetails(serverName, rs.getString(1), rs.getInt(2), rs.getInt(3));
            }
        } catch (SQLException e) {
            LOG.info("Unable to get server details for server [" + serverName + "]: " + e.getMessage());
        } finally {
            if (null != db) {
                db.closeResultSet(rs);
                db.closeStatement(stm);
            }
        }

        return result;
    }

    /**
     * Tests to make sure the server can be connected to with the given settings.
     * If the test is successful, <code>null</code>. If the test fails, the returned string
     * will be the error message to indicate the problem.
     *
     * @param connectionUrl
     * @param username
     * @param password
     * @return error message if test failed; <code>null</code> if test succeeded
     */
    public static String testConnection(String connectionUrl, String username, String password) {

        // its possible the JDBC URL was changed, clear the factory cache in case the DB version is different now
        DatabaseTypeFactory.clearDatabaseTypeCache();

        try {
            ensureDatabaseIsSupported(connectionUrl, username, password);
            return null;
        } catch (Exception e) {
            LOG.warn("Installer failed to test connection", e);
            return ThrowableUtil.getAllMessages(e);
        }
    }

    /**
     * Call this when you need to confirm that the database is supported.
     *
     * @param connectionUrl
     * @param username
     * @param password
     *
     * @throws Exception if the database is not supported
     */
    public static void ensureDatabaseIsSupported(String connectionUrl, String username, String password)
            throws Exception {
        Connection conn = null;
        DatabaseType db = null;

        try {
            conn = getDatabaseConnection(connectionUrl, username, password);
            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 {
                throw new Exception("Unsupported DB [" + db + "]");
            }

            LOG.info("Database is supported: " + db);
        } finally {
            if (db != null) {
                db.closeConnection(conn);
            }
        }

        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 connectionUrl 
     * @param userName 
     * @param password 
     * @return the database connection
     *
     * @throws SQLException if cannot successfully connect to the database
     */
    public static Connection getDatabaseConnection(String connectionUrl, String userName, String password)
            throws SQLException {
        return DbUtil.getConnection(connectionUrl, userName, password);
    }

    /**
     * Use the internal JBossAS mechanism to obfuscate a password. This is not true encryption.
     *
     * @param password the clear text of the password to obfuscate
     * @return the obfuscated password
     */
    public static String obfuscatePassword(String password) {

        // We need to do some mumbo jumbo, as the interesting method is private
        // in SecureIdentityLoginModule

        try {
            String className = "org.picketbox.datasource.security.SecureIdentityLoginModule";
            Class<?> clazz = Class.forName(className);
            Object object = clazz.newInstance();
            Method method = clazz.getDeclaredMethod("encode", String.class);
            method.setAccessible(true);
            String result = method.invoke(object, password).toString();
            return result;
        } catch (Exception e) {
            throw new RuntimeException("obfuscating db password failed: ", e);
        }
    }

    /**
     * Use the internal JBossAS mechanism to de-obfuscate a password back to its
     * clear text form. This is not true encryption.
     *
     * @param obfuscatedPasswordd the obfuscated password
     * @return the clear-text password
     */
    public static String deobfuscatePassword(String obfuscatedPassword) {

        // We need to do some mumbo jumbo, as the interesting method is private
        // in SecureIdentityLoginModule

        try {
            String className = "org.picketbox.datasource.security.SecureIdentityLoginModule";
            Class<?> clazz = Class.forName(className);
            Object object = clazz.newInstance();
            Method method = clazz.getDeclaredMethod("decode", String.class);
            method.setAccessible(true);
            char[] result = (char[]) method.invoke(object, obfuscatedPassword);
            return new String(result);
        } catch (Exception e) {
            throw new RuntimeException("de-obfuscating db password failed: ", e);
        }
    }

    /**
     * Stores the server details (such as the public endpoint) in the database. If the server definition already
     * exists, it will be updated; otherwise, a new server will be added to the HA cloud.
     *
     * @param serverProperties the server properties
     * @param password clear text password to connect to the database
     * @param serverDetails the details of the server to put into the database
     * @throws Exception
     */
    public static void storeServerDetails(HashMap<String, String> serverProperties, String password,
            ServerDetails serverDetails) throws Exception {

        DatabaseType db = null;
        Connection conn = null;

        try {
            String dbUrl = serverProperties.get(ServerProperties.PROP_DATABASE_CONNECTION_URL);
            String userName = serverProperties.get(ServerProperties.PROP_DATABASE_USERNAME);
            conn = getDatabaseConnection(dbUrl, userName, password);
            db = DatabaseTypeFactory.getDatabaseType(conn);

            updateOrInsertServer(db, conn, serverDetails);

        } catch (SQLException e) {
            // TODO: should we throw an exception here? This would abort the rest of the installation
            LOG.info("Unable to store server entry in the database: " + ThrowableUtil.getAllMessages(e));
        } finally {
            if (null != db) {
                db.closeConnection(conn);
            }
        }
    }

    private static void updateOrInsertServer(DatabaseType db, Connection conn, ServerDetails serverDetails) {
        PreparedStatement stm = null;
        ResultSet rs = null;

        if (null == serverDetails || isEmpty(serverDetails.getName())) {
            return;
        }

        try {
            stm = conn.prepareStatement("UPDATE rhq_server SET address=?, port=?, secure_port=? WHERE name=?");
            stm.setString(1, serverDetails.getEndpointAddress());
            stm.setInt(2, serverDetails.getEndpointPort());
            stm.setInt(3, serverDetails.getEndpointSecurePort());
            stm.setString(4, serverDetails.getName());
            if (0 == stm.executeUpdate()) {
                stm.close();

                // set all new servers to operation_mode=INSTALLED
                int i = 1;
                if (db instanceof PostgresqlDatabaseType || db instanceof OracleDatabaseType) {
                    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++, serverDetails.getName());
                stm.setString(i++, serverDetails.getEndpointAddress());
                stm.setInt(i++, serverDetails.getEndpointPort());
                stm.setInt(i++, serverDetails.getEndpointSecurePort());
                long now = System.currentTimeMillis();
                stm.setLong(i++, now);
                stm.setLong(i++, now);
                stm.executeUpdate();
            }

        } catch (SQLException e) {
            LOG.info("Unable to put the server details in the database: " + ThrowableUtil.getAllMessages(e));
        } finally {
            if (null != db) {
                db.closeResultSet(rs);
                db.closeStatement(stm);
            }
        }
    }

    /**
     * This will create the database schema in the database. <code>props</code> define the connection to the database -
     *
     * <p>Note that if the {@link #isDatabaseSchemaExist(Properties) schema already exists}, it will be purged of all
     * data/tables and recreated.</p>
     *
     * @param props the full set of server properties
     * @param serverDetails additional information about the server being installed
     * @param password the database password in clear text
     * @param logDir a directory where the db schema upgrade logs can be written
     *
     * @throws Exception if failed to create the new schema for some reason
     */
    public static void createNewDatabaseSchema(HashMap<String, String> props, ServerDetails serverDetails,
            String password, String logDir) throws Exception {
        String dbUrl = props.get(ServerProperties.PROP_DATABASE_CONNECTION_URL);
        String userName = props.get(ServerProperties.PROP_DATABASE_USERNAME);

        try {
            // extract the dbsetup files which are located in the dbutils jar
            String dbsetupSchemaXmlFile = extractDatabaseXmlFile("db-schema-combined.xml", props, serverDetails,
                    logDir);
            String dbsetupDataXmlFile = extractDatabaseXmlFile("db-data-combined.xml", props, serverDetails,
                    logDir);

            // first uninstall any old existing schema, then create the tables then insert the data
            DBSetup dbsetup = new DBSetup(dbUrl, userName, password);
            dbsetup.uninstall(dbsetupSchemaXmlFile);
            dbsetup.setup(dbsetupSchemaXmlFile);
            dbsetup.setup(dbsetupDataXmlFile, null, true, false);
        } catch (Exception e) {
            LOG.fatal("Cannot install the database schema - the 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.
     *
     * <p>Note that if the {@link #isDatabaseSchemaExist(Properties) schema does not already exist}, errors will
     * occur.</p>
     *
     * @param props the full set of server properties
     * @param serverDetails additional information about the server being installed
     * @param password the database password in clear text
     * @param logDir a directory where the db schema upgrade logs can be written
     *
     * @throws Exception if the upgrade failed for some reason
     */
    public static void upgradeExistingDatabaseSchema(HashMap<String, String> props, ServerDetails serverDetails,
            String password, String logDir) throws Exception {
        String dbUrl = props.get(ServerProperties.PROP_DATABASE_CONNECTION_URL);
        String userName = props.get(ServerProperties.PROP_DATABASE_USERNAME);

        File logfile = new File(logDir, "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, serverDetails, logDir);

            Properties antProps = new Properties();
            antProps.setProperty("jdbc.url", dbUrl);
            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 - the server will not run properly.", e);
            throw e;
        }

        return;
    }

    /**
     * Given a server property value string, returns true if it is not specified.
     *
     * @param s the property string value
     *
     * @return true if it is null or empty
     */
    public static boolean isEmpty(String s) {
        return s == null || s.trim().length() == 0;
    }

    /**
     * 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
     * @param serverDetails additional information about the server being installed
     * @param logDir a directory where the db schema upgrade logs can be written
     *
     * @return the absolute path to the extracted file
     *
     * @throws IOException if failed to extract the file to the log directory
     */
    private static String extractDatabaseXmlFile(String xmlFileName, HashMap<String, String> props,
            ServerDetails serverDetails, String logDir) throws IOException {

        // first slurp the file contents in memory
        InputStream resourceInStream = ServerInstallUtil.class.getClassLoader().getResourceAsStream(xmlFileName);
        ByteArrayOutputStream contentOutStream = new ByteArrayOutputStream();
        StreamUtil.copy(resourceInStream, contentOutStream);

        // now replace their replacement strings with values from the properties
        String emailFromAddress = props.get(ServerProperties.PROP_EMAIL_FROM_ADDRESS);
        if (isEmpty(emailFromAddress)) {
            emailFromAddress = "rhqadmin@localhost";
        }

        String httpPort = props.get(ServerProperties.PROP_WEB_HTTP_PORT);
        if (isEmpty(httpPort)) {
            httpPort = String.valueOf(ServerDetails.DEFAULT_ENDPOINT_PORT);
        }

        String publicEndpoint = serverDetails.getEndpointAddress();
        if (isEmpty(publicEndpoint)) {
            try {
                publicEndpoint = props.get(ServerProperties.PROP_JBOSS_BIND_ADDRESS);
                if (isEmpty(publicEndpoint) || ("0.0.0.0".equals(publicEndpoint))) {
                    publicEndpoint = InetAddress.getLocalHost().getHostAddress();
                }
            } catch (Exception e) {
                publicEndpoint = "127.0.0.1";
            }
        }

        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@@@", emailFromAddress);
        content = content.replaceAll("@@@BASEURL@@@", "http://" + publicEndpoint + ":" + httpPort + "/");
        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(logDir, 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 static void startAnt(File buildFile, String customTaskDefs, Properties properties, File logFile) {
        PrintWriter logFileOutput = null;

        try {
            logFileOutput = new PrintWriter(new FileOutputStream(logFile));

            ClassLoader classLoader = ServerInstallUtil.class.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();
            }
        }
    }

    /**
     * Creates a keystore whose cert has a CN of this server's public endpoint address.
     * 
     * @param serverDetails details of the server being installed
     * @param configDirStr location of a configuration directory where the keystore is to be stored
     */
    public static void createKeystore(ServerDetails serverDetails, String configDirStr) {
        File confDir = new File(configDirStr);
        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=" + serverDetails.getEndpointAddress() + ", 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 + "]");
            }
        }
    }

    /**
     * Create an rhqadmin/rhqadmin management user so when discovered, the AS7 plugin can immediately
     * connect to the RHQ Server.
     * 
     * @param serverDetails details of the server being installed
     * @param configDirStr location of a configuration directory where the mgmt-users.properties file lives
     */
    public static void createDefaultManagementUser(ServerDetails serverDetails, String configDirStr) {
        File confDir = new File(configDirStr);
        File mgmtUsers = new File(confDir, "mgmt-users.properties");

        // Add the default admin user, or if for some reason this file does not exist, just log the issue
        if (mgmtUsers.exists()) {
            FileOutputStream fos = null;

            try {
                fos = new FileOutputStream(mgmtUsers, true);
                fos.write("\nrhqadmin=35c160c1f841a889d4cda53f0bfc94b6\n".getBytes());

            } catch (Exception e) {
                LOG.warn("Could not create default management user in file: [" + mgmtUsers.getPath() + "] : ", e);

            } finally {
                StreamUtil.safeClose(fos);
            }
        } else {
            LOG.warn(
                    "Could not create default management user. Could not find file: [" + mgmtUsers.getPath() + "]");
        }
    }

    public static File getMarkerFile(String dir, String artifact, Marker marker) {
        File markerFile = new File(dir, artifact + marker.getExtenstion());
        return markerFile;
    }

    public static void touchMarkerFile(String dir, String artifact, Marker marker) throws Exception {
        File markerFile = getMarkerFile(dir, artifact, marker);
        markerFile.createNewFile();
        markerFile.setLastModified(System.currentTimeMillis());
    }

    public static void deleteMarkerFile(String dir, String artifact, Marker marker) throws Exception {
        File markerFile = getMarkerFile(dir, artifact, marker);
        markerFile.delete();
    }

    public static boolean markerFileExists(String dir, String artifact, Marker marker) {
        File markerFile = getMarkerFile(dir, artifact, marker);
        return markerFile.exists();
    }

    public static void setSocketBindings(ModelControllerClient mcc, HashMap<String, String> serverProperties)
            throws Exception {
        final SocketBindingJBossASClient client = new SocketBindingJBossASClient(mcc);
        for (SocketBindingInfo binding : defaultSocketBindings) {
            // use the port defined by the server's properties if set, otherwise, just use our hardcoded default
            int newPort = binding.port;
            String overrideValue = serverProperties.get(binding.sysprop);
            if (overrideValue != null) {
                try {
                    newPort = Integer.parseInt(overrideValue);
                } catch (Exception e) {
                    LOG.warn("Invalid port in system property [" + binding.sysprop + "]: " + overrideValue);
                }
            }
            LOG.info(String.format("Setting socket binding [%s] to [${%s:%d}]", binding.name, binding.sysprop,
                    newPort));
            client.setStandardSocketBindingPortExpression(binding.name, binding.sysprop, newPort);
        }
    }

}