com.tesora.dve.tools.DVEConfigCLI.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.tools.DVEConfigCLI.java

Source

package com.tesora.dve.tools;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import java.io.File;
import java.io.FilenameFilter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Scanner;
import java.util.Set;

import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ObjectName;

import com.tesora.dve.server.bootstrap.Host;
import com.tesora.dve.server.global.HostService;
import com.tesora.dve.singleton.Singletons;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;

import com.tesora.dve.common.DBHelper;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.common.PECryptoUtils;
import com.tesora.dve.common.PEFileUtils;
import com.tesora.dve.common.PEUrl;
import com.tesora.dve.common.PEXmlUtils;
import com.tesora.dve.common.SiteInstanceStatus;
import com.tesora.dve.common.catalog.DynamicPolicy;
import com.tesora.dve.common.catalog.ExternalService;
import com.tesora.dve.common.catalog.PersistentGroup;
import com.tesora.dve.common.catalog.PersistentSite;
import com.tesora.dve.common.catalog.Provider;
import com.tesora.dve.common.catalog.SiteInstance;
import com.tesora.dve.common.catalog.VariableConfig;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.groupmanager.HazelcastCoordinationServices;
import com.tesora.dve.groupmanager.LocalhostCoordinationServices;
import com.tesora.dve.siteprovider.onpremise.jaxb.OnPremiseSiteProviderConfig;
import com.tesora.dve.sql.schema.VariableScopeKind;
import com.tesora.dve.sql.template.jaxb.Template;
import com.tesora.dve.sql.transexec.CatalogHelper;
import com.tesora.dve.standalone.Main;
import com.tesora.dve.tools.field.FieldAction;
import com.tesora.dve.tools.field.FieldActionManager;
import com.tesora.dve.tools.jaxb.persistent.PersistentConfig;
import com.tesora.dve.tools.jaxb.persistent.PersistentGroupCfgList;
import com.tesora.dve.tools.jaxb.persistent.PersistentSiteCfgList;
import com.tesora.dve.tools.jaxb.policy.PolicyConfig;
import com.tesora.dve.upgrade.CatalogSchemaVersion;
import com.tesora.dve.upgrade.CatalogVersions;
import com.tesora.dve.variable.VariableConstants;

public class DVEConfigCLI extends CLIBuilder {

    private static final String PERSISTENT_SCHEMA = "persistent.xsd";
    private static final String POLICY_SCHEMA = "policy.xsd";
    private static final String ONPREMISE_SITE_PROVIDER_SCHEMA = "onPremiseSiteProvider.xsd";
    private static final String TEMPLATE_SCHEMA = "template.xsd";

    private static final String DEFAULT_JMX_HOST = "127.0.0.1";
    private static final Integer DEFAULT_JMX_PORT = 1234;

    private static final FilenameFilter TEMPLATE_FILE_NAME_FILTER = new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            return name.toLowerCase().endsWith(DVEAnalyzerCLI.TEMPLATE_FILE_EXTENSION);
        }
    };

    private CatalogHelper catHelper;
    private DBHelper dbHelper;

    public DVEConfigCLI(String[] args) throws PEException {
        super(args, "Configuration Tool");

        registerCommand(
                new CommandType(new String[] { "locate", "catalog" }, "<url> <user> <password> [<database>]",
                        "Specify the location of the DVE catalog and the user / password to use when connecting."));
        registerCommand(new CommandType(new String[] { "encrypt" }, "<text>",
                "Internal - Encrypt the specified text.", true));
        registerCommand(new CommandType(new String[] { "setup", "catalog" },
                "<persistent file> <dynamic file> <policy file>",
                "Create an initial catalog required to run the server using the specified files for configuration."));
        registerCommand(new CommandType(new String[] { "setup", "localhost", "catalog" },
                "[<num persistent site>] [<num dynamic sites>]",
                "Create an initial catalog required to run the server with the specified number of sites on localhost."));
        registerCommand(new CommandType(new String[] { "delete", "catalog" }, "Delete the DVE catalog."));
        registerCommand(new CommandType(new String[] { "upgrade", "catalog" }, "Upgrade the catalog."));
        registerCommand(
                new CommandType(new String[] { "check", "catalog", "version" }, "Check the catalog version."));
        registerCommand(new CommandType(new String[] { "truncate", "servers" },
                "Remove all registered servers from the catalog."));

        registerCommand(new CommandType(new String[] { "set", "root", "user" }, "<user> <password>",
                "Set the username and password for the root user"));

        registerCommand(new CommandType(new String[] { "set", "default", "pg" }, "<name>",
                "Set the default persistent group."));
        registerCommand(new CommandType(new String[] { "show", "pg" }, "<name>",
                "Show the contents of the specified persistent group."));

        registerCommand(new CommandType(new String[] { "add", "provider" }, "<name> <plugin> [<filename>]",
                "Add a new provider with the specified plugin and configuration."));
        registerCommand(new CommandType(new String[] { "configure", "provider" }, "<name> <filename>",
                "Configure the specified dynamic site provider using the contents of the file."));
        registerCommand(new CommandType(new String[] { "show", "provider" }, "<name>",
                "Show the configuration for the specified dynamic site provider."));
        registerCommand(new CommandType(new String[] { "remove", "provider" }, "<name>",
                "Remove the specified dynamic site provider."));

        registerCommand(new CommandType(new String[] { "add", "policy" }, "<filename>",
                "Add a new dynamic policy with the specifed configuration."));
        registerCommand(new CommandType(new String[] { "configure", "policy" }, "<filename>",
                "Configure the specified dynamic policy using the contents of the file."));
        registerCommand(new CommandType(new String[] { "show", "policy" }, "<name>",
                "Show the configuration for the specified dynamic policy."));
        registerCommand(
                new CommandType(new String[] { "set", "default", "policy" }, "<name>", "Set the default policy"));

        registerCommand(new CommandType(new String[] { "set", "groupService" }, "<Hazelcast | Localhost>",
                "Set the group service."));

        registerCommand(new CommandType(new String[] { "add", "es" },
                "<name> <plugin> <use data store> [<connect user>] [<filename>]",
                "Add a new external service with the specified plugin and configuration."));
        registerCommand(new CommandType(new String[] { "configure", "es" }, "<name> <filename>",
                "Configure the specified external service using the contents of the file."));
        registerCommand(new CommandType(new String[] { "show", "es" }, "<name>",
                "Show the configuration for the specified external service."));
        registerCommand(new CommandType(new String[] { "remove", "es" }, "<name>",
                "Remove the specified external service."));

        registerCommand(
                new CommandType(new String[] { "dump" }, "[verbose]", "Display the contents of the catalog."));

        registerCommand(
                new CommandType(new String[] { "action", "list" }, "List all available action modules.", true));
        registerCommand(new CommandType(new String[] { "action", "report" }, "<name>",
                "Run the specified action report.", true));
        registerCommand(new CommandType(new String[] { "action", "run" }, "<name>",
                "Run the specified action process.", true));

        // The following commands require an active connection to the DVE server.
        registerCommand(new CommandType(new String[] { "connect" }, "[<url> <user> <password>]",
                "Connect to a DVE server (default=" + PEConstants.MYSQL_URL + " " + PEConstants.ROOT + " "
                        + PEConstants.PASSWORD + ")"));
        registerCommand(new CommandType(new String[] { "disconnect" }, "Disconnect from a DVE server."));

        registerCommand(new CommandType(new String[] { "add", "template" }, "<template file> [<match>]",
                "Add a new template to the server using the specified file."));
        registerCommand(new CommandType(new String[] { "add", "templates" }, "<templates folder>",
                "Add a new set of templates to the server from the specified folder."));
        registerCommand(new CommandType(new String[] { "update", "template" }, "<template file> [<match>]",
                "Alter an existing template using the specified file."));
        registerCommand(new CommandType(new String[] { "update", "templates" }, "<template folder>",
                "Alter a set of existing template from the specified folder."));

        registerJMXCommands();

        try {
            catHelper = new CatalogHelper(Main.class);
        } catch (final Exception e) {
            throw new PEException("Failed to create catalog helper - " + e.getMessage(), e);
        }
    }

    protected CatalogHelper getCatalogHelper() {
        return catHelper;
    }

    @Override
    public void close() {
        if (catHelper != null) {
            catHelper.close();
            catHelper = null;
        }
        super.close();
    }

    public void cmd_dump(Scanner scanner) throws PEException {

        final boolean verbose = StringUtils.equalsIgnoreCase("VERBOSE", scan(scanner));

        printlnDots("Generating " + (verbose ? "verbose " : "") + "dump of catalog");

        try (final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw)) {
            catHelper.dumpCatalogInfo(pw, verbose);
            println(sw.toString());
        } catch (final PEException e) {
            throw e;
        } catch (final Exception e) {
            throw new PEException("Failed to generate dump - " + e.getMessage(), e);
        }
    }

    public void cmd_locate_catalog(Scanner scanner) throws PEException {
        if (scanner.hasNext()) {
            final String url = buildValidCatalogUrlFromLocation(scan(scanner, "url"));
            final String user = scan(scanner, "user");
            final String password = scan(scanner, "password");
            final String database = scan(scanner);

            final DBHelper dbHelper = new DBHelper(url, user, password);
            try {
                dbHelper.connect();
            } catch (final Exception e) {
                throw new PEException(
                        "Failed to connect to DVE catalog database at '" + url + "' - " + e.getMessage(), e);
            } finally {
                dbHelper.disconnect();
            }

            try {
                final Properties serverProps = PEFileUtils.loadPropertiesFromClasspath(Main.class,
                        PEConstants.SERVER_FILE_NAME);

                serverProps.setProperty(DBHelper.CONN_URL, url);
                serverProps.setProperty(DBHelper.CONN_USER, user);
                serverProps.setProperty(DBHelper.CONN_PASSWORD, password);
                serverProps.setProperty(DBHelper.CONN_DBNAME, (database != null) ? database : PEConstants.CATALOG);

                PEFileUtils.savePropertiesToClasspath(Main.class, PEConstants.SERVER_FILE_NAME, serverProps);

                printlnDots("Updating properties file at: " + PEFileUtils
                        .convert(PEFileUtils.getClasspathURL(Main.class, PEConstants.SERVER_FILE_NAME)));

            } catch (final Exception e) {
                throw new PEException("Failed to update '" + PEConstants.SERVER_FILE_NAME + "' - " + e.getMessage(),
                        e);
            }

            try {
                catHelper.close();
                catHelper = new CatalogHelper(Main.class);
            } catch (final Exception e) {
                throw new PEException("Failed to re-create catalog helper - " + e.getMessage(), e);
            }
        }

        final Properties props = catHelper.getProperties();

        printlnDots("DVE catalog location settings are:");
        printlnIndent("URL      = " + props.getProperty(DBHelper.CONN_URL, ""));
        printlnIndent("User     = " + props.getProperty(DBHelper.CONN_USER, ""));
        printlnIndent("Password = " + props.getProperty(DBHelper.CONN_PASSWORD, ""));
        printlnIndent("Database = " + props.getProperty(DBHelper.CONN_DBNAME, ""));
    }

    private String buildValidCatalogUrlFromLocation(final String catalogLocation) throws PEException {
        PEUrl.isValidUrlWithPort(catalogLocation);
        final PEUrl urlBuilder = PEUrl.fromUrlString(catalogLocation);
        urlBuilder.setQueryOption("useUnicode", "true");
        urlBuilder.setQueryOption("characterEncoding", "utf8");
        urlBuilder.setQueryOption("connectionCollation", "utf8_general_ci");

        return urlBuilder.getURL();
    }

    public void cmd_encrypt(Scanner scanner) throws PEException {
        final String value = scan(scanner, "text");
        printlnDots("encrypt '" + value + "' as '" + PECryptoUtils.encrypt(value));
    }

    public void cmd_delete_catalog() throws PEException {
        final Properties props = catHelper.getProperties();
        final String catalogLocation = props.getProperty(DBHelper.CONN_URL, "");

        if (StringUtils.isBlank(catalogLocation)) {
            throw new PEException(
                    "Catalog location not set. Use the locate command to specify catalog location first.");
        }

        println("You are about to completely remove the DVE catalog located at " + catalogLocation);
        askQuestion("Do you want to continue [y/N]? ", new QuestionCallback() {
            @Override
            public void answer(String line) throws PEException {
                if ((line != null) && (line.equalsIgnoreCase("y") || line.equalsIgnoreCase("yes"))) {
                    catHelper.deleteCatalog();
                    printlnDots("DVE catalog deleted from '" + catalogLocation + "'");
                }
            }
        });
    }

    public void cmd_set_root_user(Scanner scanner) throws PEException {
        final String user = scan(scanner, "user");
        final String password = scan(scanner, "password");

        catHelper.setRootCredentials(user, password);
    }

    public void cmd_setup_localhost_catalog(Scanner scanner) throws PEException {
        Integer persistent = scanInteger(scanner);
        Integer dynamic = scanInteger(scanner);

        if (persistent == null) {
            persistent = 1;
        }

        if (dynamic == null) {
            dynamic = 1;
        }

        printlnDots("Generating localhost catalog with " + persistent + " persistent site(s) and " + dynamic
                + " dynamic site(s)");

        catHelper.createLocalhostCatalog("OnPremise", PEConstants.DEFAULT_GROUP_NAME, persistent, dynamic);

        printlnDots("DVE catalog created at '" + catHelper.getCatalogDatabaseLocation() + "'");
    }

    public void cmd_setup_catalog(Scanner scanner) throws PEException {
        final File persistentFile = scanFile(scanner, "persistent sites");
        final File dynamicFile = scanFile(scanner, "dynamic sites");
        final File policyFile = scanFile(scanner, "policy");

        final String persistentContent = PEFileUtils.readToString(persistentFile);
        final String dynamicContent = PEFileUtils.readToString(dynamicFile);
        final String policyContent = PEFileUtils.readToString(policyFile);

        // Unmarshall XML for persistent site configuration.
        final PersistentConfig persistentConfig = PEXmlUtils.unmarshalJAXB(persistentContent,
                PersistentConfig.class, PEFileUtils.getClasspathURL(PersistentConfig.class, PERSISTENT_SCHEMA));

        // Create in memory representations of sites and groups.
        final List<PersistentSite> sites = createPersistentSites(persistentConfig);
        final List<PersistentGroup> groups = createPersistentGroups(persistentConfig, sites);

        // Determine default Persistent Group to set.
        PersistentGroup defaultGroup = null;
        if ((persistentConfig.getPersistentGroups() != null)
                && (persistentConfig.getPersistentGroups().getDefaultGroup() != null)) {
            // Make sure it exists in the group list we created above.
            for (final PersistentGroup group : groups) {
                if (persistentConfig.getPersistentGroups().getDefaultGroup().equals(group.getName())) {
                    defaultGroup = group;
                    break;
                }
            }
            if (defaultGroup == null) {
                throw new PEException("Persistent group specified a default group that doesn't exist");
            }
        } else {
            defaultGroup = groups.get(0);
        }

        // Unmarshall XML for policy configuration.
        final DynamicPolicy policy = parsePolicyConfig(PEXmlUtils.unmarshalJAXB(policyContent, PolicyConfig.class,
                PEFileUtils.getClasspathURL(PolicyConfig.class, POLICY_SCHEMA)));

        OnPremiseSiteProviderConfig dynamic = PEXmlUtils.unmarshalJAXB(dynamicContent,
                OnPremiseSiteProviderConfig.class,
                PEFileUtils.getClasspathURL(OnPremiseSiteProviderConfig.class, ONPREMISE_SITE_PROVIDER_SCHEMA));

        // Encrypt any passwords.
        dynamic = CatalogHelper.encryptProviderPasswords(dynamic);

        printlnDots("Generating DVE catalog");

        catHelper.createStandardCatalog(sites, groups, defaultGroup, dynamic, policy);

        printlnDots("DVE catalog created at '" + catHelper.getCatalogDatabaseLocation() + "'");
    }

    private List<PersistentSite> createPersistentSites(PersistentConfig config) throws PEException {
        final List<PersistentSite> sites = new ArrayList<PersistentSite>();
        if ((config.getPersistentSites() == null) || (config.getPersistentSites().getSite() == null)
                || config.getPersistentSites().getSite().isEmpty()) {
            throw new PEException("Persistent site list needs to contain at least one persistent site");
        }

        for (final PersistentSiteCfgList.Site siteCfg : config.getPersistentSites().getSite()) {

            if (!PersistentSite.isValidHAType(siteCfg.getHaMode())) {
                throw new PEException("Persistent site contains an invalid haMode '" + siteCfg.getHaMode() + "'");
            }

            final PersistentSite site = new PersistentSite(siteCfg.getName(), siteCfg.getHaMode());

            // Create all of the site instances.
            SiteInstance master = null;
            for (final PersistentSiteCfgList.Site.SiteInstance inst : siteCfg.getSiteInstance()) {
                // Initially we will create all site as non-masters.
                final SiteInstance i = new SiteInstance(inst.getName(), inst.getUrl(), inst.getUser(),
                        inst.getPassword(), false,
                        inst.isEnabled() ? SiteInstanceStatus.ONLINE.name() : SiteInstanceStatus.OFFLINE.name());
                site.addInstance(i);

                // If the configuration specifies a master then remember it.
                if ((inst.isMaster() != null) && inst.isMaster()) {
                    if (!inst.isEnabled()) {
                        throw new PEException("Can't specify a disabled site instance as master for site '"
                                + site.getName() + "'");
                    }

                    // Unless we already have a master specified.
                    if (master != null) {
                        throw new PEException(
                                "Multiple instances specified as master for site '" + site.getName() + "'");
                    }
                    master = i;
                }
            }

            // If we don't have a master defined then simply use the first
            // available.
            if (master == null) {
                for (final SiteInstance inst : site.getSiteInstances()) {
                    if (inst.isEnabled() && inst.isMaster()) {
                        master = inst;
                        break;
                    }
                }
            }

            // If master is still null then we don't have a viable master.
            if (master == null) {
                throw new PEException("No master site instance available for site '" + site.getName() + "'");
            }

            site.setMasterInstance(master);

            if (siteCfg.getHaMode().equalsIgnoreCase("single")) {
                if (site.getSiteInstances().size() != 1) {
                    throw new PEException(
                            "Persistent site of haMode 'Single' should have exactly one site instance");
                }
            } else if (siteCfg.getHaMode().equalsIgnoreCase("mastermaster")) {
                if (site.getSiteInstances().size() != 2) {
                    throw new PEException(
                            "Persistent site of haMode 'MasterMaster' should have exactly two site instances");
                }
            }
            sites.add(site);
        }

        return sites;
    }

    private List<PersistentGroup> createPersistentGroups(PersistentConfig config, List<PersistentSite> sites)
            throws PEException {
        final List<PersistentGroup> groups = new ArrayList<PersistentGroup>();

        if ((config.getPersistentGroups() == null) || (config.getPersistentGroups().getGroup() == null)
                || config.getPersistentGroups().getGroup().isEmpty()) {
            // We don't have a groups declaration therefore we are going to
            // create a default group containing all of the sites we know about.

            final PersistentGroup group = new PersistentGroup(PEConstants.DEFAULT_GROUP_NAME);
            group.addAllSites(sites);
            groups.add(group);
        } else {
            for (final PersistentGroupCfgList.Group groupCfg : config.getPersistentGroups().getGroup()) {
                final PersistentGroup group = new PersistentGroup(groupCfg.getName());

                if (groupCfg.getSite().isEmpty()) {
                    throw new PEException("Persistent group contains a group with no sites");
                }

                for (final String siteName : groupCfg.getSite()) {
                    // Iterate over available PersistentSites looking for the
                    // one we want to add to the group.
                    boolean found = false;
                    for (final PersistentSite site : sites) {
                        if (siteName.equals(site.getName())) {
                            group.addStorageSite(site);
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        throw new PEException(
                                "Persistent group contains site '" + siteName + "' that doesn't exist");
                    }
                }
                groups.add(group);
            }
        }
        return groups;
    }

    public void cmd_set_default_pg(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        catHelper.setDefaultStorageGroup(name);

        printlnDots("Default persistent group set to '" + name + "'");
    }

    public void cmd_show_pg(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        final PersistentGroup pg = catHelper.getStorageGroup(name);
        final List<PersistentSite> sites = pg.getStorageSites();

        printlnDots("Persistent group '" + pg.getName() + "' size=" + sites.size());
        println("Sites:");
        for (final PersistentSite site : sites) {
            final StringBuffer siteInstances = new StringBuffer();
            for (final SiteInstance siteInstance : site.getSiteInstances()) {
                if (!siteInstance.isMaster()) {
                    if (siteInstances.length() > 0) {
                        siteInstances.append(", ");
                    }
                    siteInstances.append(siteInstance.getInstanceURL());
                    if (!siteInstance.isEnabled()) {
                        siteInstances.append("(disabled)");
                    }
                }
            }

            final String instText = (siteInstances.length() > 0) ? ", [" + siteInstances.toString() + "]" : "";
            println(site.getName() + ", " + site.getHAType() + ", " + site.getMasterUrl() + instText);
        }
    }

    public void cmd_add_provider(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");
        final String plugin = scan(scanner, "plugin");
        final File providerFile = scanFile(scanner);

        final Provider provider = catHelper.createSiteProvider(name, plugin,
                (providerFile == null) ? null : PEFileUtils.readToString(providerFile));

        printlnDots("Site provider '" + provider.getName() + "' created");
    }

    public void cmd_configure_provider(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");
        final File providerFile = scanFile(scanner, "filename");

        final Provider provider = catHelper.setSiteProviderConfig(name, PEFileUtils.readToString(providerFile));

        printlnDots("Site provider '" + provider.getName() + "' configuration complete");
    }

    public void cmd_show_provider(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        final Provider provider = catHelper.getSiteProvider(name);

        printlnDots("Site provider '" + provider.getName() + "' plugin='" + provider.getPlugin() + "' enabled="
                + provider.isEnabled());
        println("Configuration:");
        println(provider.getConfig());
    }

    public void cmd_remove_provider(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        catHelper.removeSiteProvider(name);
        printlnDots("Site provider '" + name + "' removed");
    }

    public void cmd_add_policy(Scanner scanner) throws PEException {
        final File policyFile = scanFile(scanner, "filename");

        final String policyContent = PEFileUtils.readToString(policyFile);
        final PolicyConfig policyConfig = PEXmlUtils.unmarshalJAXB(policyContent, PolicyConfig.class,
                PEFileUtils.getClasspathURL(PolicyConfig.class, POLICY_SCHEMA));
        final DynamicPolicy policy = parsePolicyConfig(policyConfig);

        catHelper.createDynamicPolicy(policy);

        printlnDots("Dynamic policy '" + policy.getName() + "' created");
    }

    public void cmd_configure_policy(Scanner scanner) throws PEException {
        final File policyFile = scanFile(scanner, "filename");

        final String policyContent = PEFileUtils.readToString(policyFile);
        final PolicyConfig policyConfig = PEXmlUtils.unmarshalJAXB(policyContent, PolicyConfig.class,
                PEFileUtils.getClasspathURL(PolicyConfig.class, POLICY_SCHEMA));
        final DynamicPolicy policy = parsePolicyConfig(policyConfig);

        catHelper.setDynamicPolicyConfig(policy);

        printlnDots("Dynamic policy '" + policy.getName() + "' configuration complete");
    }

    public void cmd_show_policy(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        final DynamicPolicy policy = catHelper.getDynamicPolicy(name);

        printlnDots("Dynamic policy '" + policy.getName() + "'");
        println("Configuration:");
        println("Aggregation : " + policy.getAggregationClass().toString());
        println("Small       : " + policy.getSmallClass().toString());
        println("Medium      : " + policy.getMediumClass().toString());
        println("Large       : " + policy.getLargeClass().toString());
    }

    public void cmd_set_default_policy(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        catHelper.setDefaultDynamicPolicy(name);

        printlnDots("Default dynamic policy set to '" + name + "'");
    }

    public void cmd_set_groupService(Scanner scanner) throws PEException {
        final String name = scan(scanner, "groupService");

        String type = null;

        if (name.equalsIgnoreCase(LocalhostCoordinationServices.TYPE)) {
            type = LocalhostCoordinationServices.TYPE;
        } else if (name.equalsIgnoreCase(HazelcastCoordinationServices.TYPE)) {
            type = HazelcastCoordinationServices.TYPE;
        }

        if (type == null) {
            throw new PEException("'" + name + "' isn't a recognized type for group service");
        }

        catHelper.setVariable(VariableConstants.GROUP_SERVICE_NAME, type, true);
    }

    @SuppressWarnings("unused")
    public void cmd_show_variables(Scanner scanner) throws PEException {
        final List<VariableConfig> variables = catHelper.getAllVariables();

        printlnDots("Variables size: " + variables.size());
        for (final VariableConfig variable : variables) {
            if (variable.getScopes().indexOf(VariableScopeKind.GLOBAL.name()) > -1)
                println(variable.getName() + " = " + variable.getValue());
        }
    }

    public void cmd_set_variable(Scanner scanner) throws PEException {
        final String variable = scan(scanner, "variable");

        final int index = variable.indexOf('=');

        if (index == -1) {
            throw new PEException("Variable should be in the form <key>=<value>");
        }

        final String key = variable.substring(0, index);
        final String value = variable.substring(index + 1);

        catHelper.setVariable(key, value, true);

        printlnDots("Successfully set variable '" + key + "' to value '" + value + "'");
    }

    public void cmd_add_es(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");
        final String plugin = scan(scanner, "plugin");
        final boolean usesDataStore = scanBoolean(scanner, "use data source");

        String connectUser = scan(scanner);
        if ((connectUser != null) && connectUser.equals("null")) {
            connectUser = null;
        }

        final File esFile = scanFile(scanner);

        final ExternalService externalService = catHelper.createExternalService(name, plugin, connectUser,
                usesDataStore, (esFile == null) ? null : PEFileUtils.readToString(esFile));

        printlnDots("External service '" + externalService.getName() + "' created");
    }

    public void cmd_configure_es(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");
        final File esFile = scanFile(scanner, "filename");

        final ExternalService externalService = catHelper.setExternalServiceConfig(name,
                PEFileUtils.readToString(esFile));

        printlnDots("External service '" + externalService.getName() + "' configuration complete");
    }

    public void cmd_show_es(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        final ExternalService externalService = catHelper.getExternalService(name);

        printlnDots("External service '" + externalService.getName() + "' plugin='" + externalService.getPlugin()
                + "' auto start=" + externalService.isAutoStart() + "' uses datastore="
                + externalService.usesDataStore() + " 'connect user='" + externalService.getConnectUser());
        println("Configuration:");
        println(externalService.getConfig());
    }

    public void cmd_remove_es(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        catHelper.removeExternalService(name);
        printlnDots("External service '" + name + "' removed");
    }

    public void cmd_upgrade_catalog() throws PEException {
        catHelper.checkURLAvailable();

        final Properties props = catHelper.getProperties();
        final String catalogLocation = props.getProperty(DBHelper.CONN_URL, "");

        boolean upgradeRequired = false;
        try {
            CatalogVersions.catalogVersionCheck(props);
        } catch (final PEException e) {
            upgradeRequired = true;
        }

        if (!upgradeRequired) {
            throw new PEException(
                    "DVE catalog at '" + catalogLocation + "' is latest version. Upgrade not required.");
        }

        println("You are about to upgrade your DVE catalog located at '" + catalogLocation + "'");
        askQuestion("Do you want to continue [y/N]? ", new QuestionCallback() {
            @Override
            public void answer(String line) throws PEException {

                if ((line != null) && (line.equalsIgnoreCase("y") || line.equalsIgnoreCase("yes"))) {
                    printlnDots("Starting DVE catalog upgrade at '" + catalogLocation + "'");
                    //this Host initialization was pulled out of CatalogVersions to break a dependency cycle between Host and CatalogVersions.
                    if (Singletons.lookup(HostService.class) == null) {
                        props.put(DBHelper.CONN_DRIVER_CLASS, PEConstants.MYSQL_DRIVER_CLASS);
                        // we can't start the catalog here - otherwise we would modify it while sitting on it
                        new Host(props, false /* startCatalog */); //TODO: the startCatalog flag has a bad smell.  HostService needs further decomposition. -sgossard
                    }
                    CatalogVersions.upgradeToLatest(props, DVEConfigCLI.this);
                    printlnDots("Catalog upgrade complete");
                }
            }
        });
    }

    public void cmd_check_catalog_version() throws PEException {
        catHelper.checkURLAvailable();

        final Properties props = catHelper.getProperties();
        final String catalogLocation = props.getProperty(DBHelper.CONN_URL, "");

        printlnDots("Checking version of DVE catalog at '" + catalogLocation + "'");

        final CatalogSchemaVersion version = CatalogVersions.catalogVersionCheck(props);
        printlnDots(
                "DVE catalog at " + catalogLocation + " is at latest version (" + version.getSchemaVersion() + ")");
    }

    public void cmd_truncate_servers() {
        println("You are about to completely remove all registered servers from the DVE catalog");
        askQuestion("Do you want to continue [y/N]? ", new QuestionCallback() {
            @Override
            public void answer(String line) throws PEException {
                if ((line != null) && (line.equalsIgnoreCase("y") || line.equalsIgnoreCase("yes"))) {
                    catHelper.deleteAllServerRegistration();
                    printlnDots("All registered servers removed from the DVE catalog");
                }
            }
        });
    }

    public void cmd_action_list() {
        printlnDots("Available actions are:");
        for (final FieldAction action : FieldActionManager.getAllActions()) {
            println(action.getName() + " - " + action.getDescription());
        }
    }

    public void cmd_action_report(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        final FieldAction action = getActionOnCurrentCatalog(name);
        action.report(catHelper.getProperties(), getPrintStream());
    }

    public void cmd_action_run(Scanner scanner) throws PEException {
        final String name = scan(scanner, "name");

        final FieldAction action = getActionOnCurrentCatalog(name);
        action.execute(catHelper.getProperties(), getPrintStream());
    }

    private FieldAction getActionOnCurrentCatalog(final String name) throws PEException {
        final FieldAction action = FieldActionManager.getAction(name);

        if (!action.isValid(CatalogVersions.getCurrentVersion())) {
            throw new PEException("Action '" + name + "' isn't available for the current catalog version");
        }

        return action;
    }

    private DynamicPolicy parsePolicyConfig(PolicyConfig config) {
        return new DynamicPolicy(config.getName(), config.isStrict(), config.getAggregation().getProvider(),
                config.getAggregation().getPool(), config.getAggregation().getCount(),
                config.getSmall().getProvider(), config.getSmall().getPool(), config.getSmall().getCount(),
                config.getMedium().getProvider(), config.getMedium().getPool(), config.getMedium().getCount(),
                config.getLarge().getProvider(), config.getLarge().getPool(), config.getLarge().getCount());
    }

    public void cmd_connect(Scanner scanner) throws PEException {
        cmd_disconnect();

        String url = PEConstants.MYSQL_URL;
        String user = PEConstants.ROOT;
        String password = PEConstants.PASSWORD;

        if (scanner.hasNext()) {
            url = scan(scanner, "url");
            user = scan(scanner, "user");
            password = scan(scanner, "password");
        }

        url = PEUrl.fromUrlString(url).getURL();

        dbHelper = new DBHelper(url, user, password).connect();

        printlnDots("You are now connected to the DVE server at '" + url + "'");
    }

    public void cmd_disconnect() {
        if (dbHelper != null) {
            dbHelper.disconnect();
            printlnDots("You are now disconnected from the DVE server");
        }
        dbHelper = null;
    }

    public void cmd_add_template(Scanner scanner) throws PEException {
        final File templateFile = scanFile(scanner, "filename");
        final String match = scan(scanner);

        addTemplate(templateFile, false, match);
        printlnDots("Template '" + templateFile.getAbsolutePath() + "' added to catalog");
    }

    public void cmd_add_templates(Scanner scanner) throws PEException {
        final File folder = scanFile(scanner, "folder");
        pushTemplatesToCatalog(folder, false);
    }

    public void cmd_update_template(Scanner scanner) throws PEException {
        final File templateFile = scanFile(scanner, "filename");
        final String match = scan(scanner);

        addTemplate(templateFile, true, match);
        printlnDots("Template '" + templateFile + "' updated in catalog");
    }

    public void cmd_update_templates(Scanner scanner) throws PEException {
        final File folder = scanFile(scanner, "folder");
        pushTemplatesToCatalog(folder, true);
    }

    private void pushTemplatesToCatalog(final File folder, final boolean updateExisting) throws PEException {
        if (!folder.isDirectory()) {
            throw new PEException("'" + folder + "' isn't a valid directory");
        }

        final File[] files = folder.listFiles(TEMPLATE_FILE_NAME_FILTER);
        for (final File file : files) {
            try {
                addTemplate(file, updateExisting, null);
                printlnDots("Template '" + file.getPath() + "' added to catalog");
            } catch (final Exception e) {
                printlnDots("Could not add template '" + file.getAbsolutePath() + "' - " + e.getMessage());
            }
        }
    }

    private String addTemplate(File templateFile, boolean update, String match) throws PEException {
        final String templateStr = PEFileUtils.readToString(templateFile);

        final Template template = PEXmlUtils.unmarshalJAXB(templateStr, Template.class,
                PEFileUtils.getClasspathURL(Template.class, TEMPLATE_SCHEMA));

        final String useName = template.getName();
        final String useComment = template.getComment();
        final String useMatch = StringUtils.isBlank(match) ? template.getMatch() : match;

        try {
            if (dbHelper != null) {
                addTemplateToServer(useName, templateStr, update, useMatch, useComment);
            } else {
                addTemplateToCatalog(useName, templateStr, update, useMatch, useComment);
            }

            return useName;
        } catch (final Exception e) {
            if (update) {
                throw new PEException("Failed to update template '" + template + "' in catalog - " + e.getMessage(),
                        e);
            } else {
                throw new PEException("Failed to add template '" + template + "' to catalog - " + e.getMessage(),
                        e);
            }
        }
    }

    private void addTemplateToServer(String name, String templateStr, boolean update, String match, String comment)
            throws Exception {
        final StringBuilder query = new StringBuilder();
        if (update) {
            query.append("ALTER TEMPLATE `").append(name).append("` SET XML='")
                    .append(StringEscapeUtils.escapeSql(templateStr)).append("'");
        } else {
            query.append("CREATE TEMPLATE `").append(name).append("` XML='")
                    .append(StringEscapeUtils.escapeSql(templateStr)).append("'");
        }

        if (StringUtils.isNotBlank(match)) {
            query.append(" MATCH='").append(match).append("'");
        }

        if (StringUtils.isNotBlank(comment)) {
            query.append(" COMMENT='").append(comment).append("'");
        }

        dbHelper.executeQuery(query.toString());
    }

    private void addTemplateToCatalog(String name, String templateStr, boolean update, String match, String comment)
            throws Exception {
        if (update) {
            catHelper.updateTemplate(name, templateStr, match, comment);
        } else {
            catHelper.createTemplate(name, templateStr, match, comment);
        }
    }

    private JMXMBeanConnector jmxConnector = null;
    private String jmxDomain = null;
    private ObjectName jmxMBean = null;
    private MBeanInfo jmxMBeanInfo = null;

    public void registerJMXCommands() {
        // The following commands are for JMX mode
        final CommandMapType map = createCommandMap("jmx");

        map.registerCommand(new CommandType(new String[] { "connect" }, "[<host> <port>]"));
        map.registerCommand(new CommandType(new String[] { "list", "domains" }, ""));
        map.registerCommand(new CommandType(new String[] { "domain" }, "<domain>"));
        map.registerCommand(new CommandType(new String[] { "list", "mbeans" }, ""));
        map.registerCommand(new CommandType(new String[] { "mbean" }, "<mbean>"));
        map.registerCommand(new CommandType(new String[] { "get", "attribute" }, "<attribute>"));
        map.registerCommand(new CommandType(new String[] { "get", "attributes" }, ""));
        map.registerCommand(new CommandType(new String[] { "list", "operations" }, ""));
        map.registerCommand(new CommandType(new String[] { "invoke" }, "[<arg 1> ... <arg n>]"));

    }

    public void cmd_jmx_connect(Scanner scanner) throws PEException {
        String host = scan(scanner);
        Integer port = scanInteger(scanner);

        if (host == null) {
            host = DEFAULT_JMX_HOST;
        }

        if (port == null) {
            port = DEFAULT_JMX_PORT;
        }

        jmxConnector = new JMXMBeanConnector(host, port);

        printlnDots("You are now connected to the MBean server at '" + jmxConnector.serviceUrl + "'");

        cmd_jmx_list_domains();
    }

    public void cmd_jmx_disconnect() throws PEException {
        checkJMXConnected();

        jmxConnector.close();

        this.jmxConnector = null;
        this.jmxDomain = null;
        this.jmxMBean = null;
        this.jmxMBeanInfo = null;
    }

    public void cmd_jmx_list_domains() throws PEException {
        checkJMXConnected();

        final String[] domains = jmxConnector.getBeanDomains();

        printlnDots("Available MBean domain names are:");

        for (final String domain : domains) {
            println(domain);
        }
    }

    public void cmd_jmx_domain(Scanner scanner) throws PEException {
        checkJMXConnected();

        final String domain = scan(scanner, "domain");

        jmxConnector.getBeanNames(domain);

        jmxDomain = domain;
        jmxMBean = null;
        jmxMBeanInfo = null;

        printlnDots("Domain '" + jmxDomain + "' selected");

        cmd_jmx_list_mbeans();
    }

    public void cmd_jmx_list_mbeans() throws PEException {
        checkJMXConnected();
        checkJMXDomain();

        final Set<ObjectName> beans = jmxConnector.getBeanNames(jmxDomain);

        printlnDots("Available MBean(s) in domain '" + jmxDomain + "' are:");

        for (final ObjectName bean : beans) {
            println(bean.getCanonicalKeyPropertyListString());
        }
    }

    public void cmd_jmx_mbean(Scanner scanner) throws PEException {
        checkJMXConnected();
        checkJMXDomain();

        final String mbean = scan(scanner, "mbean");

        final ObjectName on = jmxConnector.makeObjectName(jmxDomain, mbean);

        final MBeanInfo info = jmxConnector.getBeanInfo(on);

        jmxMBean = on;
        jmxMBeanInfo = info;

        printlnDots("MBean '" + jmxMBean + "' selected");

        println("Attributes = " + (info.getAttributes() == null ? "N/A" : info.getAttributes().length));
        println("Operations = " + (info.getOperations() == null ? "N/A" : info.getOperations().length));
    }

    public void cmd_jmx_get_attribute(Scanner scanner) throws PEException {

        checkJMXConnected();
        checkJMXDomain();
        checkJMXMBean();

        final String attribute = scan(scanner, "attribute");

        printlnDots("Attribute value for '" + attribute + "' in mbean '" + jmxMBean + "' is:");

        println(attribute + " = " + jmxConnector.getAttribute(jmxMBean, attribute));
    }

    public void cmd_jmx_get_attributes() throws PEException {

        checkJMXConnected();
        checkJMXDomain();
        checkJMXMBean();

        for (final MBeanAttributeInfo attrib : jmxMBeanInfo.getAttributes()) {
            if (attrib.isReadable()) {
                println(attrib.getName() + " = " + jmxConnector.getAttribute(jmxMBean, attrib.getName()));
            }
        }
    }

    public void cmd_jmx_list_operations() throws PEException {

        checkJMXConnected();
        checkJMXDomain();
        checkJMXMBean();

        for (final MBeanOperationInfo operation : jmxMBeanInfo.getOperations()) {
            final String name = operation.getName();
            final StringBuilder sParam = new StringBuilder();
            for (final MBeanParameterInfo param : operation.getSignature()) {
                if (sParam.length() > 0) {
                    sParam.append(", ");
                }
                sParam.append(formatType(param.getType())).append(" ").append(param.getName());
            }

            final StringBuilder builder = new StringBuilder();
            println(builder.append(name).append("(").append(sParam.toString()).append(")").toString());
        }
    }

    public void cmd_jmx_invoke(Scanner scanner) throws PEException {

        checkJMXConnected();
        checkJMXDomain();
        checkJMXMBean();

        final String opName = scan(scanner, "operation");

        // First lookup the operation to ensure it exists
        MBeanOperationInfo found = null;
        for (final MBeanOperationInfo operation : jmxMBeanInfo.getOperations()) {
            if (operation.getName().equals(opName)) {
                found = operation;
                break;
            }
        }

        if (found == null) {
            throw new PEException("'" + opName + "' isn't a valid operation on '" + jmxMBean + "'");
        }

        final MBeanParameterInfo[] params = found.getSignature();

        final Object[] paramO = params.length == 0 ? null : new Object[params.length];
        final String[] paramT = params.length == 0 ? null : new String[params.length];

        for (int i = 0; i < params.length; i++) {
            // For each parameter we expect to have a matching item in the
            // scanner

            final String val = scan(scanner, params[i].getName());

            paramO[i] = jmxConnector.stringToObject(val, params[i].getType());
            paramT[i] = params[i].getType();
        }
        jmxConnector.invokeOperation(jmxMBean, found, paramO, paramT);
    }

    private String formatType(String type) {
        if (type.startsWith("[")) {
            switch (type.charAt(1)) {
            case 'C':
                return "char[]";
            case 'D':
                return "double[]";
            case 'F':
                return "float[]";
            case 'I':
                return "int[]";
            case 'J':
                return "long[]";
            case 'S':
                return "short[]";
            case 'Z':
                return "boolean[]";
            case 'L':
                return type.substring(2, type.indexOf(";"));
            }
        }
        return type;
    }

    private void checkJMXConnected() throws PEException {
        if (jmxConnector == null) {
            throw new PEException("Your aren't connected to a JMX MBean server");
        }
    }

    private void checkJMXDomain() throws PEException {
        if (jmxDomain == null) {
            throw new PEException("Your haven't selected a JMX MBean domain");
        }
    }

    private void checkJMXMBean() throws PEException {
        if ((jmxMBean == null) || (jmxMBeanInfo == null)) {
            throw new PEException("Your haven't selected a JMX MBean");
        }
    }

    public static void main(String[] args) {
        try {
            new DVEConfigCLI(args).start();
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
}