com.mirth.connect.cli.CommandLineInterface.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.cli.CommandLineInterface.java

Source

/*
 * Copyright (c) Mirth Corporation. All rights reserved.
 * 
 * http://www.mirthcorp.com
 * 
 * The software in this package is published under the terms of the MPL license a copy of which has
 * been included with this distribution in the LICENSE.txt file.
 */

package com.mirth.connect.cli;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.PropertiesConfigurationLayout;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.math.NumberUtils;

import com.mirth.connect.client.core.Client;
import com.mirth.connect.client.core.ClientException;
import com.mirth.connect.client.core.ListHandlerException;
import com.mirth.connect.client.core.PaginatedEventList;
import com.mirth.connect.client.core.PaginatedMessageList;
import com.mirth.connect.donkey.model.channel.DeployedState;
import com.mirth.connect.donkey.model.message.ContentType;
import com.mirth.connect.donkey.model.message.Message;
import com.mirth.connect.donkey.model.message.attachment.Attachment;
import com.mirth.connect.donkey.util.xstream.SerializerException;
import com.mirth.connect.model.Channel;
import com.mirth.connect.model.ChannelDependency;
import com.mirth.connect.model.ChannelStatistics;
import com.mirth.connect.model.CodeTemplate;
import com.mirth.connect.model.CodeTemplateLibrary;
import com.mirth.connect.model.CodeTemplateLibrarySaveResult;
import com.mirth.connect.model.CodeTemplateLibrarySaveResult.CodeTemplateUpdateResult;
import com.mirth.connect.model.DashboardStatus;
import com.mirth.connect.model.InvalidChannel;
import com.mirth.connect.model.LoginStatus;
import com.mirth.connect.model.MessageImportResult;
import com.mirth.connect.model.ServerConfiguration;
import com.mirth.connect.model.ServerEvent;
import com.mirth.connect.model.User;
import com.mirth.connect.model.alert.AlertModel;
import com.mirth.connect.model.converters.ObjectXMLSerializer;
import com.mirth.connect.model.filters.EventFilter;
import com.mirth.connect.model.filters.MessageFilter;
import com.mirth.connect.util.ConfigurationProperty;
import com.mirth.connect.util.MessageExporter;
import com.mirth.connect.util.MessageImporter;
import com.mirth.connect.util.MessageImporter.MessageImportException;
import com.mirth.connect.util.MessageImporter.MessageImportInvalidPathException;
import com.mirth.connect.util.messagewriter.AttachmentSource;
import com.mirth.connect.util.messagewriter.MessageWriter;
import com.mirth.connect.util.messagewriter.MessageWriterException;
import com.mirth.connect.util.messagewriter.MessageWriterFactory;
import com.mirth.connect.util.messagewriter.MessageWriterOptions;

public class CommandLineInterface {
    private String DEFAULT_CHARSET = "UTF-8";
    private Client client;
    private boolean debug;
    private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yy_HH-mm-ss.SS");
    private String currentUser = new String();
    private PrintWriter out;
    private PrintWriter err;

    public CommandLineInterface(String[] args) {
        out = new PrintWriter(System.out, true);
        err = new PrintWriter(System.out, true);
        run(args);
    }

    public static void main(String[] args) {
        System.setProperty("log4j.configuration", "log4j-cli.properties");
        new CommandLineInterface(args);
    }

    @SuppressWarnings("static-access")
    private void run(String[] args) {
        Option serverOption = OptionBuilder.withArgName("address").hasArg().withDescription("server address")
                .create("a");
        Option userOption = OptionBuilder.withArgName("user").hasArg().withDescription("user login").create("u");
        Option passwordOption = OptionBuilder.withArgName("password").hasArg().withDescription("user password")
                .create("p");
        Option scriptOption = OptionBuilder.withArgName("script").hasArg().withDescription("script file")
                .create("s");
        Option versionOption = OptionBuilder.withArgName("version").hasArg().withDescription("version").create("v");
        Option configOption = OptionBuilder.withArgName("config file").hasArg()
                .withDescription("path to default configuration [default: mirth-cli-config.properties]")
                .create("c");
        Option helpOption = new Option("h", "help");
        Option debugOption = new Option("d", "debug");

        Options options = new Options();
        options.addOption(configOption);
        options.addOption(serverOption);
        options.addOption(userOption);
        options.addOption(passwordOption);
        options.addOption(scriptOption);
        options.addOption(versionOption);
        options.addOption(helpOption);
        options.addOption(debugOption);

        CommandLineParser parser = new GnuParser();

        try {
            CommandLine line = parser.parse(options, args);

            // Bail out early if they just want help
            if (line.hasOption("h")) {
                new HelpFormatter().printHelp("Shell", options);
                System.exit(0);
            }

            Properties configDefaults = new Properties();
            try {
                configDefaults.load(new FileInputStream(
                        line.getOptionValue("c", "conf" + File.separator + "mirth-cli-config.properties")));
            } catch (IOException e) {
                // Only error out if they tried to load the config
                if (line.hasOption("c")) {
                    error("We could not find the file: " + line.getOptionValue("c"), null);
                    System.exit(2);
                }
            }

            String server = line.getOptionValue("a", configDefaults.getProperty("address"));
            String user = line.getOptionValue("u", configDefaults.getProperty("user"));
            String password = line.getOptionValue("p", configDefaults.getProperty("password"));
            String script = line.getOptionValue("s", configDefaults.getProperty("script"));

            if ((server != null) && (user != null) && (password != null)) {
                runShell(server, user, password, script, line.hasOption("d"));
            } else {
                new HelpFormatter().printHelp("Shell", options);
                error("all of address, user, password, and version options must be supplied as arguments or in the default configuration file",
                        null);
                System.exit(2);
            }
        } catch (ParseException e) {
            error("Could not parse input arguments.", e);
            System.exit(2);
        }
    }

    private void runShell(String server, String user, String password, String script, boolean debug) {
        try {
            client = new Client(server);
            this.debug = debug;

            LoginStatus loginStatus = client.login(user, password);

            if (loginStatus.getStatus() != LoginStatus.Status.SUCCESS) {
                error("Could not login to server.", null);
                return;
            }

            String serverVersion = client.getVersion();
            try {
                ObjectXMLSerializer.getInstance().init(serverVersion);
            } catch (Exception e) {
            }

            out.println("Connected to Mirth Connect server @ " + server + " (" + serverVersion + ")");
            currentUser = StringUtils.defaultString(loginStatus.getUpdatedUsername(), user);

            if (script != null) {
                runScript(script);
            } else {
                runConsole();
            }
            client.logout();
            client.close();
            out.println("Disconnected from server.");
        } catch (ClientException ce) {
            ce.printStackTrace();
        } catch (IOException ioe) {
            error("Could not load script file.", ioe);
        } catch (URISyntaxException e) {
            error("Invalid server address.", e);
        }
    }

    private void runScript(String script) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(script));
        String statement = null;

        try {
            while ((statement = reader.readLine()) != null) {
                out.println("Executing statement: " + statement);
                executeStatement(statement);
            }
        } catch (Quit e) {
            // do nothing
        } finally {
            reader.close();
        }
    }

    private void runConsole() throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        String statement = null;
        writePrompt();
        try {
            while ((statement = reader.readLine()) != null) {
                executeStatement(statement);
                writePrompt();
            }
            out.println(); // want newline before "Disconnected" message
        } catch (Quit e) {
            // do nothing
        } finally {
            reader.close();
        }
    }

    private void error(String message, Throwable t) {
        err.println("Error: " + message);

        if ((t != null) && debug) {
            err.println(ExceptionUtils.getStackTrace(t));
        }
    }

    private void writePrompt() {
        out.print("$");
        out.flush();
    }

    private void executeStatement(String command) {
        try {
            Token[] arguments = tokenizeCommand(command);

            if (arguments.length >= 1) {
                Token arg1 = arguments[0];
                if (arg1 == Token.HELP) {
                    commandHelp(arguments);
                    return;
                } else if (arg1 == Token.USER) {
                    if (arguments.length < 2) {
                        error("invalid number of arguments.", null);
                        return;
                    }
                    Token arg2 = arguments[1];
                    if (arg2 == Token.LIST) {
                        commandUserList(arguments);
                    } else if (arg2 == Token.ADD) {
                        commandUserAdd(arguments);
                    } else if (arg2 == Token.REMOVE) {
                        commandUserRemove(arguments);
                    } else if (arg2 == Token.CHANGEPW) {
                        commandUserChangePassword(arguments);
                    }
                } else if (arg1 == Token.DEPLOY) {
                    commandDeploy(arguments);
                } else if (arg1 == Token.EXPORTCFG) {
                    commandExportConfig(arguments);
                } else if (arg1 == Token.IMPORTCFG) {
                    commandImportConfig(arguments);
                } else if (arg1 == Token.IMPORT) {
                    commandImport(arguments);
                } else if (arg1 == Token.IMPORTALERTS) {
                    commandImportAlerts(arguments);
                } else if (arg1 == Token.EXPORTALERTS) {
                    commandExportAlerts(arguments);
                } else if (arg1 == Token.IMPORTSCRIPTS) {
                    commandImportScripts(arguments);
                } else if (arg1 == Token.EXPORTSCRIPTS) {
                    commandExportScripts(arguments);
                } else if (arg1 == Token.IMPORTCODETEMPLATES) {
                    // Deprecated, remove in 3.4
                    error("The importcodetemplates command is deprecated. Please use \"codetemplate [library] import path [force]\" instead.",
                            null);
                    if (!hasInvalidNumberOfArguments(arguments, 2)) {
                        commandImportCodeTemplates(arguments[1].getText(), true);
                    }
                } else if (arg1 == Token.EXPORTCODETEMPLATES) {
                    // Deprecated, remove in 3.4
                    error("The exportcodetemplates command is deprecated. Please use \"codetemplate [library] export id|name|* path\" instead.",
                            null);
                    if (!hasInvalidNumberOfArguments(arguments, 2)) {
                        commandExportCodeTemplateLibraries("*", arguments[1].getText());
                    }
                } else if (arg1 == Token.IMPORTMESSAGES) {
                    commandImportMessages(arguments);
                } else if (arg1 == Token.EXPORTMESSAGES) {
                    commandExportMessages(arguments);
                } else if (arg1 == Token.IMPORTMAP) {
                    commandImportMap(arguments);
                } else if (arg1 == Token.EXPORTMAP) {
                    commandExportMap(arguments);
                } else if (arg1 == Token.STATUS) {
                    commandStatus(arguments);
                } else if (arg1 == Token.EXPORT) {
                    commandExport(arguments);
                } else if (arg1 == Token.CHANNEL) {
                    String syntax = "invalid number of arguments. Syntax is: channel start|stop|pause|resume|stats|remove|enable|disable <id|name>, channel rename <id|name> newname, or channel list|stats";
                    if (arguments.length < 2) {
                        error(syntax, null);
                        return;
                    } else if (arguments.length < 3 && arguments[1] != Token.LIST && arguments[1] != Token.STATS) {
                        error(syntax, null);
                        return;
                    }

                    Token comm = arguments[1];

                    if (comm == Token.STATS && arguments.length < 3) {
                        commandAllChannelStats(arguments);
                    } else if (comm == Token.LIST) {
                        commandChannelList(arguments);
                    } else if (comm == Token.DISABLE) {
                        commandChannelDisable(arguments);
                    } else if (comm == Token.ENABLE) {
                        commandChannelEnable(arguments);
                    } else if (comm == Token.REMOVE) {
                        commandChannelRemove(arguments);
                    } else if (comm == Token.START) {
                        commandChannelStart(arguments);
                    } else if (comm == Token.STOP) {
                        commandChannelStop(arguments);
                    } else if (comm == Token.HALT) {
                        commandChannelHalt(arguments);
                    } else if (comm == Token.PAUSE) {
                        commandChannelPause(arguments);
                    } else if (comm == Token.RESUME) {
                        commandChannelResume(arguments);
                    } else if (comm == Token.STATS) {
                        commandChannelStats(arguments);
                    } else if (comm == Token.RENAME) {
                        commandChannelRename(arguments);
                    } else if (comm == Token.DEPLOY) {
                        commandChannelDeploy(arguments);
                    } else if (comm == Token.UNDEPLOY) {
                        commandChannelUndeploy(arguments);
                    } else {
                        error("unknown channel command " + comm, null);
                    }
                } else if (arg1 == Token.CODE_TEMPLATE) {
                    if (arguments.length < 2) {
                        error("Invalid number of arguments. Syntax is: codetemplate library list [includecodetemplates], codetemplate list, codetemplate [library] import path [force], codetemplate export id|name path, codetemplate library export id|name|* path, codetemplate remove id|name, or codetemplate library remove id|name|*",
                                null);
                        return;
                    }

                    Token arg2 = arguments[1];

                    if (arg2 == Token.LIBRARY) {
                        if (arguments.length < 3) {
                            error("Invalid number of arguments. Syntax is: codetemplate library list [includecodetemplates], codetemplate library import path [force], codetemplate library export id|name|* path, or codetemplate library remove id|name|*",
                                    null);
                            return;
                        }

                        Token arg3 = arguments[2];

                        if (arg3 == Token.LIST) {
                            commandListCodeTemplateLibraries(arguments.length > 3 && StringUtils
                                    .equalsIgnoreCase(arguments[3].getText(), "includecodetemplates"));
                        } else if (arg3 == Token.IMPORT) {
                            if (arguments.length < 4) {
                                error("Invalid number of arguments. Syntax is: codetemplate library import path [force]",
                                        null);
                                return;
                            }
                            commandImportCodeTemplateLibraries(arguments[3].getText(), arguments.length > 4
                                    && StringUtils.equalsIgnoreCase(arguments[4].getText(), "force"));
                        } else if (arg3 == Token.EXPORT) {
                            if (arguments.length < 5) {
                                error("Invalid number of arguments. Syntax is: codetemplate library export id|name|* path",
                                        null);
                                return;
                            }
                            commandExportCodeTemplateLibraries(arguments[3].getText(), arguments[4].getText());
                        } else if (arg3 == Token.REMOVE) {
                            if (arguments.length < 4) {
                                error("Invalid number of arguments. Syntax is: codetemplate library remove id|name|*",
                                        null);
                                return;
                            }
                            commandRemoveCodeTemplateLibraries(arguments[3].getText());
                        } else {
                            error("Unknown code template library command " + arg3
                                    + ". Syntax is: codetemplate library list [includecodetemplates], codetemplate library import path [force], codetemplate library export id|name|* path, or codetemplate library remove id|name|*",
                                    null);
                            return;
                        }
                    } else if (arg2 == Token.LIST) {
                        commandListCodeTemplates();
                    } else if (arg2 == Token.IMPORT) {
                        if (arguments.length < 3) {
                            error("Invalid number of arguments. Syntax is: codetemplate import path [force]", null);
                            return;
                        }
                        commandImportCodeTemplates(arguments[2].getText(), arguments.length > 3
                                && StringUtils.equalsIgnoreCase(arguments[3].getText(), "force"));
                    } else if (arg2 == Token.EXPORT) {
                        if (arguments.length < 4) {
                            error("Invalid number of arguments. Syntax is: codetemplate export id|name path", null);
                            return;
                        }
                        commandExportCodeTemplate(arguments[2].getText(), arguments[3].getText());
                    } else if (arg2 == Token.REMOVE) {
                        if (arguments.length < 3) {
                            error("Invalid number of arguments. Syntax is: codetemplate remove id|name", null);
                            return;
                        }
                        commandRemoveCodeTemplate(arguments[2].getText());
                    } else {
                        error("Unknown code template command " + arg2
                                + ". Syntax is: codetemplate library list [includecodetemplates], codetemplate list, codetemplate [library] import path [force], codetemplate export id|name path, codetemplate library export id|name|* path, codetemplate remove id|name, or codetemplate library remove id|name|*",
                                null);
                        return;
                    }
                } else if (arg1 == Token.CLEARALLMESSAGES) {
                    commandClearAllMessages(arguments);
                } else if (arg1 == Token.RESETSTATS) {
                    commandResetstats(arguments);
                } else if (arg1 == Token.DUMP) {
                    if (arguments.length >= 2) {
                        Token arg2 = arguments[1];

                        if (arg2 == Token.STATS) {
                            commandDumpStats(arguments);
                        } else if (arg2 == Token.EVENTS) {
                            commandDumpEvents(arguments);
                        } else {
                            error("unknown dump command: " + arg2, null);
                        }
                    } else {
                        error("missing dump commands.", null);
                    }
                } else if (arg1 == Token.QUIT) {
                    throw new Quit();
                } else {
                    error("unknown command: " + command, null);
                }
            }
        } catch (ClientException e) {
            e.printStackTrace(err);
        }
    }

    private boolean hasInvalidNumberOfArguments(Token[] arguments, int expected) {
        if ((arguments.length - 1) < expected) {
            error("invalid number of arguments.", null);
            return true;
        }

        return false;
    }

    /** Split <code>command</code> into an array of tokens. */
    private Token[] tokenizeCommand(String command) {
        List<Token> tokens = new ArrayList<Token>();
        StringBuilder currentToken = null; // not in a token yet
        char[] chars = command.toCharArray();
        boolean inQuotes = false;
        for (int idx = 0; idx < chars.length; idx++) {
            char ch = chars[idx];
            if (currentToken == null) { // currently between tokens
                if (ch == ' ') {
                    // ignore spaces between tokens (including leading space)
                    continue;
                } else {
                    // start a new token
                    currentToken = new StringBuilder();
                }
            }

            if (inQuotes && ch != '"') {
                // add another char (possibly space) to the current token
                currentToken.append(ch);
            } else if (inQuotes && ch == '"') {
                // no longer in quotes: ignore the " char and switch modes
                inQuotes = false;
            } else if (!inQuotes && ch == '"') {
                // now in quotes: ignore the " char and switch modes
                inQuotes = true;
            } else if (!inQuotes && ch == ' ') {
                // end of current token
                addToken(tokens, currentToken);
                currentToken = null;
            } else if (!inQuotes && ch == '#') {
                // start of comment: stop tokenizing now (ie. treat it as end of
                // line)
                break;
            } else if (!inQuotes) {
                // any other char outside of quotes: just append to current
                // token
                currentToken.append(ch);
            } else {
                // impossible state because of the first two clauses above
                throw new IllegalStateException(
                        "impossible state in tokenizer: inQuotes=" + inQuotes + ", char=" + ch);
            }
        }
        addToken(tokens, currentToken);

        // out.println("token list: " + tokens);
        Token[] arguments = new Token[tokens.size()];
        tokens.toArray(arguments);
        return arguments;
    }

    private void addToken(List<Token> tokens, StringBuilder currentText) {
        if (currentText == null || StringUtils.isEmpty(currentText.toString())) {
            // empty or commented line
            return;
        }
        String text = currentText.toString();
        Token token = Token.getKeyword(text);
        if (token == null) {
            try {
                token = Token.intToken(text);
            } catch (NumberFormatException e) {
                token = Token.stringToken(text);
            }
        }
        tokens.add(token);
    }

    private void commandHelp(Token[] arguments) {
        out.println("Available Commands:");
        out.println("status\n\tReturns status of deployed channels\n");
        out.println("deploy [timeout]\n\tDeploys all Channels with optional timeout (in seconds)\n");
        out.println(
                "import \"path\" [force]\n\tImports channel specified by <path>.  Optional 'force' overwrites existing channels.\n");
        out.println("export id|\"name\"|* \"path\"\n\tExports the specified channel to <path>\n");
        out.println(
                "importcfg \"path\" [nodeploy]\n\tImports configuration specified by <path>.  Optional 'nodeploy' stops channels from being deployed after importing.\n");
        out.println("exportcfg \"path\"\n\tExports the configuration to <path>\n");
        out.println(
                "importalert \"path\" [force]\n\tImports alert specified by <path>.  Optional 'force' overwrites existing alerts.\n");
        out.println("exportalert id|\"name\"|* \"path\"\n\tExports the specified alert to <path>\n");
        out.println("importscripts \"path\"\n\tImports global script specified by <path>\n");
        out.println("exportscripts \"path\"\n\tExports global script to <path>\n");
        out.println(
                "codetemplate library list [includecodetemplates]\n\tLists all code template libraries. Optional 'includecodetemplates' additionally lists the code templates within each library.\n");
        out.println("codetemplate list\n\tLists all code templates.\n");
        out.println(
                "codetemplate [library] import path [force]\n\tImports code templates or libraries (with the 'library' option).\n");
        out.println(
                "codetemplate library export id|name|* path\n\tExports all matched code template libraries to <path>.\n");
        out.println("codetemplate export id|name path\n\tExports a single code template to <path>.\n");
        out.println("codetemplate library remove id|name|*\n\tRemoves all matched code template libraries.\n");
        out.println("codetemplate remove id|name\n\tRemoves a single code template.\n");
        out.println(
                "importmessages \"path\" id\n\tImports messages specified by <path> into the channel specified by <id>\n");
        out.println(
                "exportmessages \"path/file-pattern\" id [xml|xml-attach|raw|processedraw|transformed|encoded|response] [pageSize]\n\tExports all messages for channel specified by <id> to <path>\n");
        out.println("importmap \"path\"\n\tImports configuration map specified by <path>\n");
        out.println("exportmap \"path\"\n\tExports configuration map to <path>\n");
        out.println(
                "channel undeploy|deploy|start|stop|halt|pause|resume|stats id|\"name\"|*\n\tPerforms specified channel action\n");
        out.println("channel remove|enable|disable id|\"name\"|*\n\tRemove, enable or disable specified channel\n");
        out.println("channel list\n\tLists all Channels\n");
        out.println(
                "clearallmessages\n\tRemoves all messages from all Channels (running channels will be restarted)\n");
        out.println(
                "resetstats [lifetime]\n\tRemoves all stats from all Channels. Optional 'lifetime' includes resetting lifetime stats.\n");
        out.println("dump stats|events \"path\"\n\tDumps stats or events to specified file\n");
        out.println("user list\n\tReturns a list of the current users\n");
        out.println(
                "user add username \"password\" \"firstName\" \"lastName\" \"organization\" \"email\"\n\tAdds the specified user\n");
        out.println("user remove id|username\n\tRemoves the specified user\n");
        out.println("user changepw id|username \"newpassword\"\n\tChanges the specified user's password\n");
        out.println("quit\n\tQuits Mirth Connect Shell");
    }

    private void commandUserList(Token[] arguments) throws ClientException {
        List<User> users = client.getAllUsers();
        out.println("ID\tUser Name\tName\t\t\tEmail");
        for (Iterator<User> iter = users.iterator(); iter.hasNext();) {
            User user = iter.next();
            out.println(user.getId() + "\t" + user.getUsername() + "\t\t" + user.getFirstName() + "\t\t"
                    + user.getLastName() + "\t\t" + user.getOrganization() + "\t\t" + user.getEmail());
        }
    }

    private void commandUserAdd(Token[] arguments) throws ClientException {
        if (arguments.length < 8) {
            error("invalid number of arguments. Syntax is user add username \"password\" \"firstName\" \"lastName\" \"organization\" \"email\"",
                    null);
            return;
        }
        String username = arguments[2].getText();
        if (username.length() < 1) {
            error("unable to add user: username too short.", null);
            return;
        }

        String password = arguments[3].getText();
        String firstName = arguments[4].getText();
        String lastName = arguments[5].getText();
        String organization = arguments[6].getText();
        String email = arguments[7].getText();
        User user = new User();
        user.setUsername(username);
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setOrganization(organization);
        user.setEmail(email);

        List<User> users = client.getAllUsers();
        for (Iterator<User> iter = users.iterator(); iter.hasNext();) {
            User luser = iter.next();
            if (luser.getUsername().equalsIgnoreCase(username)) {
                error("unable to add user: username in use.", null);
                return;
            }
        }

        try {
            List<String> responses = client.checkUserPassword(password);
            if (responses != null) {
                for (String response : responses) {
                    out.println(response);
                }
                return;
            }
            client.createUser(user);
            // Get the new user object that contains the user id
            User newUser = client.getUser(username);
            responses = client.updateUserPassword(newUser.getId(), password);

            if (responses != null) {
                System.out.println("User \"" + username + "\" has been created but the password could not be set:");
                for (String response : responses) {
                    out.println(response);
                }
            } else {
                out.println("User \"" + username + "\" added successfully.");
            }
        } catch (Exception e) {
            error("unable to add user \"" + username + "\": " + e, e);
        }
    }

    private void commandUserRemove(Token[] arguments) throws ClientException {
        if (arguments.length < 3) {
            error("invalid number of arguments. Syntax is user remove username|id", null);
            return;
        }
        String key = arguments[2].getText();
        if (key.equalsIgnoreCase(currentUser)) {
            error("cannot remove current user.", null);
            return;
        }
        List<User> users = client.getAllUsers();
        for (Iterator<User> iter = users.iterator(); iter.hasNext();) {
            User user = iter.next();
            if (user.getId().toString().equalsIgnoreCase(key) || user.getUsername().equalsIgnoreCase(key)) {
                client.removeUser(user.getId());
                out.println("User \"" + user.getUsername() + "\" successfully removed.");
                return;
            }
        }
    }

    private void commandUserChangePassword(Token[] arguments) throws ClientException {
        if (arguments.length < 4) {
            error("invalid number of arguments. Syntax is user changepw username|id \"newpassword\"", null);
            return;
        }
        String key = arguments[2].getText();
        String newPassword = arguments[3].getText();
        List<User> users = client.getAllUsers();
        for (Iterator<User> iter = users.iterator(); iter.hasNext();) {
            User user = iter.next();
            if (user.getId().toString().equalsIgnoreCase(key) || user.getUsername().equalsIgnoreCase(key)) {
                List<String> responses = client.updateUserPassword(user.getId(), newPassword);
                if (responses != null) {
                    for (String response : responses) {
                        out.println(response);
                    }
                } else {
                    out.println("User \"" + user.getUsername() + "\" password updated.");
                }
                return;
            }
        }
    }

    private void commandDeploy(Token[] arguments) throws ClientException {
        out.println("Deploying Channels");
        List<Channel> channels = client.getAllChannels();

        boolean hasChannels = false;
        for (Channel channel : channels) {
            if (channel.isEnabled()) {
                hasChannels = true;
                break;
            }
        }

        client.redeployAllChannels();

        if (hasChannels) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            List<DashboardStatus> channelStatus = client.getAllChannelStatuses();
            int limit = 60; // 30 second limit
            if (arguments.length > 1 && arguments[1] instanceof IntToken) {
                limit = ((IntToken) arguments[1]).getValue() * 2; // multiply
                // by two
                // because
                // our sleep
                // is 500ms
            }
            while (channelStatus.size() == 0 && limit > 0) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                channelStatus = client.getAllChannelStatuses();
                limit--;
            }
            if (limit > 0) {
                out.println("Channels Deployed");
            } else {
                out.println("Deployment Timed out");
            }
        } else {
            out.println("No Channels to Deploy");
        }
    }

    private void commandExportConfig(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        String path = arguments[1].getText();
        ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();

        try {
            ServerConfiguration configuration = client.getServerConfiguration();
            String backupDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
            configuration.setDate(backupDate);

            File fXml = new File(path);
            out.println("Exporting Configuration");
            String configurationXML = serializer.serialize(configuration);
            FileUtils.writeStringToFile(fXml, configurationXML);
        } catch (IOException e) {
            error("unable to write file " + path + ": " + e, e);
        }

        out.println("Configuration Export Complete.");
    }

    private void commandImportConfig(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        String path = arguments[1].getText();
        File fXml = new File(path);
        boolean deploy = true;
        if (arguments.length >= 3 && arguments[2] == Token.NODEPLOY) {
            deploy = false;
        }

        ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();

        try {
            client.setServerConfiguration(
                    serializer.deserialize(FileUtils.readFileToString(fXml), ServerConfiguration.class), deploy);
        } catch (IOException e) {
            error("cannot read " + path, e);
            return;
        }

        out.println("Configuration Import Complete.");
    }

    private void commandImport(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        String path = arguments[1].getText();

        boolean force = false;
        if (arguments.length >= 3 && arguments[2] == Token.FORCE) {
            force = true;
        }

        File fXml = new File(path);
        doImportChannel(fXml, force);
    }

    private void commandImportAlerts(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        String path = arguments[1].getText();

        boolean force = false;
        if (arguments.length >= 3 && arguments[2] == Token.FORCE) {
            force = true;
        }

        File fXml = new File(path);
        doImportAlert(fXml, force);
    }

    private void commandExportAlerts(Token[] arguments) throws ClientException {
        if (arguments.length < 3) {
            error("invalid number of arguments. Syntax is: export id|name|* \"path\"", null);
            return;
        }

        Token key = arguments[1];
        String path = arguments[2].getText();
        ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();
        List<AlertModel> alerts = client.getAllAlerts();
        if (key == Token.WILDCARD) {
            for (AlertModel alert : alerts) {
                try {
                    File fXml = new File(path + alert.getName() + ".xml");
                    out.println("Exporting " + alert.getName());
                    String alertXML = serializer.serialize(alert);
                    FileUtils.writeStringToFile(fXml, alertXML);
                } catch (IOException e) {
                    error("unable to write file " + path + ": " + e, e);
                }
            }
            out.println("Export Complete.");
            return;
        } else {
            File fXml = new File(path);
            StringToken skey = Token.stringToken(key.getText());

            for (AlertModel alert : alerts) {
                if (skey.equalsIgnoreCase(alert.getName()) != skey.equalsIgnoreCase(alert.getId())) {
                    out.println("Exporting " + alert.getName());
                    String alertXML = serializer.serialize(alert);
                    try {
                        FileUtils.writeStringToFile(fXml, alertXML);
                    } catch (IOException e) {
                        error("unable to write file " + path + ": " + e, e);
                    }
                    out.println("Export Complete.");
                    return;
                }
            }
        }
    }

    private void commandExportScripts(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();
        String path = arguments[1].getText();
        File fXml = new File(path);

        try {
            String scriptsXml = serializer.serialize(client.getGlobalScripts());
            out.println("Exporting scripts");
            FileUtils.writeStringToFile(fXml, scriptsXml);
        } catch (IOException e) {
            error("unable to write file " + path + ": " + e, e);
        }

        out.println("Script Export Complete.");
    }

    private void commandImportScripts(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        String path = arguments[1].getText();
        File fXml = new File(path);
        doImportScript(fXml);
        out.println("Scripts Import Complete");
    }

    private void commandListCodeTemplateLibraries(boolean includeCodeTemplates) throws ClientException {
        List<CodeTemplateLibrary> libraries = client.getCodeTemplateLibraries(null, includeCodeTemplates);

        int maxLibraryNameLength = 4;
        for (CodeTemplateLibrary library : libraries) {
            if (library.getName().length() > maxLibraryNameLength) {
                maxLibraryNameLength = library.getName().length();
            }
        }

        int maxCodeTemplateNameLength = 4;
        if (includeCodeTemplates) {
            for (CodeTemplateLibrary library : libraries) {
                for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
                    if (codeTemplate.getName().length() > maxCodeTemplateNameLength) {
                        maxCodeTemplateNameLength = codeTemplate.getName().length();
                    }
                }
            }
        }

        boolean showLibraryHeader = true;
        for (CodeTemplateLibrary library : libraries) {
            if (showLibraryHeader) {
                out.printf("%-" + maxLibraryNameLength + "s  %-36s  %-8s  %s\n", "Name", "Id", "Revision",
                        "Last Modified");
                out.printf("%-" + maxLibraryNameLength + "s  %-36s  %-8s  %s\n",
                        StringUtils.repeat('-', maxLibraryNameLength), StringUtils.repeat('-', 36),
                        StringUtils.repeat('-', 8), StringUtils.repeat('-', 19));
                showLibraryHeader = false;
            }

            out.printf("%-" + maxLibraryNameLength + "s  %-36s  %-8d  %tF %<tT\n", library.getName(),
                    library.getId(), library.getRevision(), library.getLastModified());

            if (includeCodeTemplates && library.getCodeTemplates().size() > 0) {
                out.println();
                listCodeTemplates(library.getCodeTemplates(), true, maxCodeTemplateNameLength);
                out.println();
                showLibraryHeader = true;
            }
        }
    }

    private void commandImportCodeTemplateLibraries(String path, boolean force) throws ClientException {
        try {
            List<CodeTemplateLibrary> libraries = ObjectXMLSerializer.getInstance()
                    .deserializeList(FileUtils.readFileToString(new File(path)), CodeTemplateLibrary.class);
            removeInvalidItems(libraries, CodeTemplateLibrary.class);

            if (libraries.isEmpty()) {
                out.println("No code template libraries found in file \"" + path + "\".");
                return;
            }

            Map<String, CodeTemplateLibrary> libraryMap = new HashMap<String, CodeTemplateLibrary>();
            for (CodeTemplateLibrary library : client.getCodeTemplateLibraries(null, false)) {
                libraryMap.put(library.getId(), library);
            }

            Map<String, CodeTemplate> codeTemplateMap = new HashMap<String, CodeTemplate>();

            for (CodeTemplateLibrary library : libraries) {
                library = new CodeTemplateLibrary(library);

                CodeTemplateLibrary matchingLibrary = libraryMap.get(library.getId());
                if (matchingLibrary != null) {
                    library.getEnabledChannelIds().addAll(matchingLibrary.getEnabledChannelIds());
                    library.getDisabledChannelIds().addAll(matchingLibrary.getDisabledChannelIds());
                    library.getDisabledChannelIds().removeAll(library.getEnabledChannelIds());

                    for (CodeTemplate serverCodeTemplate : matchingLibrary.getCodeTemplates()) {
                        boolean found = false;
                        for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
                            if (serverCodeTemplate.getId().equals(codeTemplate.getId())) {
                                found = true;
                                break;
                            }
                        }

                        if (!found) {
                            library.getCodeTemplates().add(serverCodeTemplate);
                        }
                    }
                }

                for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
                    if (codeTemplate.getName() != null) {
                        codeTemplateMap.put(codeTemplate.getId(), codeTemplate);
                    }
                }

                libraryMap.put(library.getId(), library);
            }

            CodeTemplateLibrarySaveResult updateSummary = client.updateLibrariesAndTemplates(
                    new HashSet<CodeTemplateLibrary>(libraryMap.values()), new HashSet<String>(),
                    new HashSet<CodeTemplate>(codeTemplateMap.values()), new HashSet<String>(), force);

            if (!updateSummary.isOverrideNeeded()) {
                if (updateSummary.isLibrariesSuccess()) {
                    out.println(libraries.size() + " code template libraries imported successfully.");

                    List<CodeTemplate> failedCodeTemplates = new ArrayList<CodeTemplate>();
                    Throwable firstCause = null;

                    for (Entry<String, CodeTemplateUpdateResult> entry : updateSummary.getCodeTemplateResults()
                            .entrySet()) {
                        if (!entry.getValue().isSuccess()) {
                            failedCodeTemplates.add(codeTemplateMap.get(entry.getKey()));
                            if (firstCause == null) {
                                firstCause = entry.getValue().getCause();
                            }
                        }
                    }

                    if (!failedCodeTemplates.isEmpty()) {
                        out.println("The following code templates failed to be imported:\n");
                        listCodeTemplates(failedCodeTemplates, true);
                    }

                    if (firstCause != null) {
                        throw new ClientException(firstCause);
                    }
                } else {
                    error("Failed to import code template libraries.", updateSummary.getLibrariesCause());
                }
            } else {
                error("One or more code templates or libraries is outdated (use the \"force\" option to import them anyway).",
                        null);
            }
        } catch (IOException e) {
            error("Failed to read file: " + path, e);
        } catch (SerializerException e) {
            error("Invalid code template file: " + path, e);
        }
    }

    private void commandExportCodeTemplateLibraries(String searchText, String path) throws ClientException {
        List<CodeTemplateLibrary> libraries = client.getCodeTemplateLibraries(null, true);
        List<CodeTemplateLibrary> exportLibraries = new ArrayList<CodeTemplateLibrary>();

        for (CodeTemplateLibrary library : libraries) {
            if (library.getId().equals(searchText)) {
                exportLibraries.clear();
                exportLibraries.add(library);
                break;
            }

            if ((searchText.equals("*") || StringUtils.equals(library.getName(), searchText))) {
                exportLibraries.add(library);
            }
        }

        if (exportLibraries.isEmpty()) {
            out.println("No code template libraries found for search criteria \"" + searchText + "\".");
            return;
        }

        try {
            FileUtils.writeStringToFile(new File(path),
                    ObjectXMLSerializer.getInstance().serialize(exportLibraries));
            out.println("Successfully exported " + exportLibraries.size() + " code template librar"
                    + (exportLibraries.size() == 1 ? "y" : "ies") + ".");
        } catch (IOException e) {
            error("Error exporting code template libraries to file: " + path, e);
        }
    }

    private void commandRemoveCodeTemplateLibraries(String searchText) throws ClientException {
        List<CodeTemplateLibrary> libraries = client.getCodeTemplateLibraries(null, false);
        List<CodeTemplateLibrary> matchedLibraries = new ArrayList<CodeTemplateLibrary>();

        for (CodeTemplateLibrary library : libraries) {
            if (library.getId().equals(searchText)) {
                matchedLibraries.clear();
                matchedLibraries.add(library);
                break;
            }

            if (searchText.equals("*") || StringUtils.equals(library.getName(), searchText)) {
                matchedLibraries.add(library);
            }
        }

        if (matchedLibraries.isEmpty()) {
            out.println("No code template libraries found for search criteria \"" + searchText + "\".");
            return;
        }

        Set<CodeTemplateLibrary> updatedLibraries = new HashSet<CodeTemplateLibrary>();
        for (CodeTemplateLibrary library : libraries) {
            if (!matchedLibraries.contains(library)) {
                updatedLibraries.add(library);
            }
        }

        if (client.updateCodeTemplateLibraries(updatedLibraries, true)) {
            out.println("Successfully removed " + matchedLibraries.size() + " code template librar"
                    + (matchedLibraries.size() == 1 ? "y" : "ies") + ".");

            for (CodeTemplateLibrary library : matchedLibraries) {
                for (CodeTemplate codeTemplate : library.getCodeTemplates()) {
                    try {
                        client.removeCodeTemplate(codeTemplate.getId());
                    } catch (ClientException e) {
                        error("Error removing code template " + codeTemplate.getId() + ".", e);
                    }
                }
            }
        }
    }

    private void commandListCodeTemplates() throws ClientException {
        listCodeTemplates(client.getCodeTemplates(null), false);
    }

    private void listCodeTemplates(List<CodeTemplate> codeTemplates, boolean indent) {
        listCodeTemplates(codeTemplates, indent, 4);
    }

    private void listCodeTemplates(List<CodeTemplate> codeTemplates, boolean indent, int maxNameLength) {
        for (CodeTemplate codeTemplate : codeTemplates) {
            if (codeTemplate.getName().length() > maxNameLength) {
                maxNameLength = codeTemplate.getName().length();
            }
        }

        out.printf((indent ? '\t' : "") + "%-" + maxNameLength + "s  %-36s  %-24s  %-8s  %s\n", "Name", "Id",
                "Type", "Revision", "Last Modified");
        out.printf((indent ? '\t' : "") + "%-" + maxNameLength + "s  %-36s  %-24s  %-8s  %s\n",
                StringUtils.repeat('-', maxNameLength), StringUtils.repeat('-', 36), StringUtils.repeat('-', 24),
                StringUtils.repeat('-', 8), StringUtils.repeat('-', 19));
        for (CodeTemplate codeTemplate : codeTemplates) {
            out.printf((indent ? '\t' : "") + "%-" + maxNameLength + "s  %-36s  %-24s  %-8d  %tF %<tT\n",
                    codeTemplate.getName(), codeTemplate.getId(), codeTemplate.getType(),
                    codeTemplate.getRevision(), codeTemplate.getLastModified());
        }
    }

    private void commandImportCodeTemplates(String path, boolean force) throws ClientException {
        try {
            List<CodeTemplate> codeTemplates = ObjectXMLSerializer.getInstance()
                    .deserializeList(FileUtils.readFileToString(new File(path)), CodeTemplate.class);
            removeInvalidItems(codeTemplates, CodeTemplate.class);

            if (codeTemplates.isEmpty()) {
                out.println("No code templates found in file \"" + path + "\".");
                return;
            }

            List<CodeTemplate> outdatedCodeTemplates = new ArrayList<CodeTemplate>();
            List<CodeTemplate> failedCodeTemplates = new ArrayList<CodeTemplate>();
            ClientException firstCause = null;

            for (Iterator<CodeTemplate> it = codeTemplates.iterator(); it.hasNext();) {
                CodeTemplate codeTemplate = it.next();

                try {
                    if (!client.updateCodeTemplate(codeTemplate, force)) {
                        outdatedCodeTemplates.add(codeTemplate);
                        it.remove();
                    }
                } catch (ClientException e) {
                    failedCodeTemplates.add(codeTemplate);
                    it.remove();
                    if (firstCause == null) {
                        firstCause = e;
                    }
                }
            }

            out.println(codeTemplates.size() + " code template" + (codeTemplates.size() == 1 ? "" : "s")
                    + " imported successfully.");

            if (!outdatedCodeTemplates.isEmpty()) {
                out.println("The following code template" + (outdatedCodeTemplates.size() == 1 ? " is" : "s are")
                        + " outdated (use the \"force\" option to import them anyway):\n");
                listCodeTemplates(outdatedCodeTemplates, true);
            }

            if (!failedCodeTemplates.isEmpty()) {
                out.println("The following code template" + (failedCodeTemplates.size() == 1 ? "" : "s")
                        + " failed to be imported:\n");
                listCodeTemplates(failedCodeTemplates, true);
            }

            if (firstCause != null) {
                throw firstCause;
            }
        } catch (IOException e) {
            error("Failed to read file: " + path, e);
        } catch (SerializerException e) {
            error("Invalid code template file: " + path, e);
        }
    }

    private void commandExportCodeTemplate(String searchText, String path) throws ClientException {
        List<CodeTemplate> codeTemplates = client.getCodeTemplates(null);
        List<CodeTemplate> exportCodeTemplates = new ArrayList<CodeTemplate>();

        for (CodeTemplate codeTemplate : codeTemplates) {
            if (StringUtils.equals(codeTemplate.getId(), searchText)) {
                exportCodeTemplates.clear();
                exportCodeTemplates.add(codeTemplate);
                break;
            } else if (StringUtils.equals(codeTemplate.getName(), searchText)) {
                exportCodeTemplates.add(codeTemplate);
            }
        }

        if (exportCodeTemplates.isEmpty()) {
            out.println("No code templates found for search criteria \"" + searchText + "\".");
            return;
        } else if (exportCodeTemplates.size() > 1) {
            error("Error exporting code template by name, multiple found:", null);
            listCodeTemplates(exportCodeTemplates, false);
            return;
        }

        try {
            FileUtils.writeStringToFile(new File(path),
                    ObjectXMLSerializer.getInstance().serialize(exportCodeTemplates.get(0)));
            out.println("Successfully exported code template.");
        } catch (IOException e) {
            error("Error exporting code template to file: " + path, e);
        }
    }

    private void commandRemoveCodeTemplate(String searchText) throws ClientException {
        List<CodeTemplate> codeTemplates = client.getCodeTemplates(null);
        List<CodeTemplate> removeCodeTemplates = new ArrayList<CodeTemplate>();

        for (CodeTemplate codeTemplate : codeTemplates) {
            if (StringUtils.equals(codeTemplate.getId(), searchText)) {
                removeCodeTemplates.clear();
                removeCodeTemplates.add(codeTemplate);
                break;
            } else if (StringUtils.equals(codeTemplate.getName(), searchText)) {
                removeCodeTemplates.add(codeTemplate);
            }
        }

        if (removeCodeTemplates.isEmpty()) {
            out.println("No code templates found for search criteria \"" + searchText + "\".");
            return;
        } else if (removeCodeTemplates.size() > 1) {
            error("Error removing code template by name, multiple found:", null);
            listCodeTemplates(removeCodeTemplates, false);
            return;
        }

        out.println("Removing code template \"" + removeCodeTemplates.get(0).getName() + "\"...");
        client.removeCodeTemplate(removeCodeTemplates.get(0).getId());
        out.println("Successfully removed code template.");
    }

    private void commandImportMessages(Token[] arguments) {
        if (hasInvalidNumberOfArguments(arguments, 2)) {
            return;
        }

        String path = arguments[1].getText();
        final String channelId = arguments[2].getText();

        MessageWriter importer = new MessageWriter() {
            @Override
            public boolean write(Message message) throws MessageWriterException {
                try {
                    client.importMessage(channelId, message);
                } catch (ClientException e) {
                    throw new MessageWriterException(e);
                }

                return true;
            }

            @Override
            public void finishWrite() throws MessageWriterException {
            }

            @Override
            public void close() throws MessageWriterException {
            }
        };

        try {
            MessageImportResult result = new MessageImporter().importMessages(path, true, importer,
                    new File(".").getAbsolutePath());
            out.println(result.getSuccessCount() + " out of " + result.getTotalCount()
                    + " messages imported successfully.");
        } catch (InterruptedException e) {
            error("Message import was interrupted.", null);
        } catch (MessageImportException e) {
            error("An error occurred while attempting to import messages", e);
        } catch (MessageImportInvalidPathException e) {
            error(e.getMessage(), e);
        }
    }

    private void commandExportMessages(Token[] arguments) {
        if (hasInvalidNumberOfArguments(arguments, 2)) {
            return;
        }

        // file path
        String path = arguments[1].getText();
        File fXml = new File(path);

        // message filter
        MessageFilter filter = new MessageFilter();
        String channelId = arguments[2].getText();

        // export mode
        ContentType contentType = null;

        boolean includeAttachments = false;
        if (arguments.length >= 4) {
            String modeArg = arguments[3].getText();

            if (StringUtils.equals(modeArg, "raw")) {
                contentType = ContentType.RAW;
            } else if (StringUtils.equals(modeArg, "processedraw")) {
                contentType = ContentType.PROCESSED_RAW;
            } else if (StringUtils.equals(modeArg, "transformed")) {
                contentType = ContentType.TRANSFORMED;
            } else if (StringUtils.equals(modeArg, "encoded")) {
                contentType = ContentType.ENCODED;
            } else if (StringUtils.equals(modeArg, "sent")) {
                contentType = ContentType.SENT;
            } else if (StringUtils.equals(modeArg, "response")) {
                contentType = ContentType.RESPONSE;
            } else if (StringUtils.equals(modeArg, "responsetransformed")) {
                contentType = ContentType.RESPONSE_TRANSFORMED;
            } else if (StringUtils.equals(modeArg, "processedresponse")) {
                contentType = ContentType.PROCESSED_RESPONSE;
            } else if (StringUtils.equals(modeArg, "xml-attach")) {
                includeAttachments = true;
            }
        }

        // page size
        int pageSize = 100;

        if (arguments.length == 5) {
            pageSize = NumberUtils.toInt(arguments[4].getText());
        }

        int messageCount = 0;

        try {
            filter.setMaxMessageId(client.getMaxMessageId(channelId));
            MessageWriter messageWriter = null;

            try {
                out.println("Exporting messages to file: " + fXml.getPath());

                PaginatedMessageList messageList = new PaginatedMessageList();
                messageList.setChannelId(channelId);
                messageList.setClient(client);
                messageList.setIncludeContent(true);
                messageList.setMessageFilter(filter);
                messageList.setPageSize(pageSize);

                MessageWriterOptions writerOptions = new MessageWriterOptions();
                writerOptions.setBaseFolder(new File(".").getPath());
                writerOptions.setContentType(contentType);
                writerOptions.setDestinationContent(false);
                writerOptions.setEncrypt(false);
                writerOptions.setRootFolder(FilenameUtils.getFullPath(fXml.getAbsolutePath()));
                writerOptions.setFilePattern(FilenameUtils.getName(fXml.getAbsolutePath()));
                writerOptions.setArchiveFormat(null);
                writerOptions.setCompressFormat(null);
                writerOptions.setIncludeAttachments(includeAttachments);

                messageWriter = MessageWriterFactory.getInstance().getMessageWriter(writerOptions,
                        client.getEncryptor());

                AttachmentSource attachmentSource = null;
                if (writerOptions.includeAttachments()) {
                    attachmentSource = new AttachmentSource() {
                        @Override
                        public List<Attachment> getMessageAttachments(Message message) throws ClientException {
                            return client.getAttachmentsByMessageId(message.getChannelId(), message.getMessageId());
                        }
                    };
                }

                messageCount = new MessageExporter().exportMessages(messageList, messageWriter, attachmentSource);
                messageWriter.finishWrite();
            } catch (Exception e) {
                Throwable cause = ExceptionUtils.getRootCause(e);
                error("unable to write file(s) " + path + ": " + cause, cause);
            } finally {
                if (messageWriter != null) {
                    try {
                        messageWriter.close();
                    } catch (Exception e) {
                        Throwable cause = ExceptionUtils.getRootCause(e);
                        error("unable to close file(s) " + path + ": " + cause, cause);
                    }
                }
            }
        } catch (Exception e) {
            Throwable cause = ExceptionUtils.getRootCause(e);
            error("Unable to retrieve max message ID: " + cause, cause);
        }

        out.println("Messages Export Complete. " + messageCount + " Messages Exported.");
    }

    private void commandImportMap(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        // file path
        String path = arguments[1].getText();
        File file = new File(path);

        if (file != null && file.exists()) {
            try {
                PropertiesConfiguration properties = new PropertiesConfiguration(file);
                Map<String, ConfigurationProperty> configurationMap = new HashMap<String, ConfigurationProperty>();
                Iterator<String> iterator = properties.getKeys();

                while (iterator.hasNext()) {
                    String key = iterator.next();
                    String value = properties.getString(key);
                    String comment = properties.getLayout().getCanonicalComment(key, false);

                    configurationMap.put(key, new ConfigurationProperty(value, comment));
                }

                client.setConfigurationMap(configurationMap);

                out.println("Configuration map import complete");
            } catch (ConfigurationException e) {
                error("Unable to import configuration map", e);
            }
        } else {
            error("Unable to read file " + path, null);
        }
    }

    private void commandExportMap(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 1)) {
            return;
        }

        // file path
        String path = arguments[1].getText();
        File file = new File(path);

        if (file != null) {
            try {
                PropertiesConfiguration properties = new PropertiesConfiguration(file);
                properties.clear();
                PropertiesConfigurationLayout layout = properties.getLayout();

                Map<String, ConfigurationProperty> configurationMap = client.getConfigurationMap();
                Map<String, ConfigurationProperty> sortedMap = new TreeMap<String, ConfigurationProperty>(
                        String.CASE_INSENSITIVE_ORDER);
                sortedMap.putAll(configurationMap);

                for (Entry<String, ConfigurationProperty> entry : sortedMap.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue().getValue();
                    String comment = entry.getValue().getComment();

                    if (StringUtils.isNotBlank(key)) {
                        properties.setProperty(key, value);
                        layout.setComment(key, StringUtils.isBlank(comment) ? null : comment);
                    }
                }

                properties.save();

                out.println("Configuration map export complete.");
            } catch (ConfigurationException e) {
                error("Unable to export configuration map.", e);
            }
        }
    }

    private void commandStatus(Token[] arguments) throws ClientException {
        out.println("ID\t\t\t\t\tStatus\t\tName");
        List<DashboardStatus> channels = client.getAllChannelStatuses();
        for (Iterator<DashboardStatus> iter = channels.iterator(); iter.hasNext();) {
            DashboardStatus channel = iter.next();

            out.println(channel.getChannelId() + "\t" + channel.getState().toString() + "\t\t" + channel.getName());
        }
    }

    private void commandExport(Token[] arguments) throws ClientException {
        if (arguments.length < 3) {
            error("invalid number of arguments. Syntax is: export id|name|* \"path\"", null);
            return;
        }

        Token key = arguments[1];
        String path = arguments[2].getText();
        ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();
        List<Channel> channels = client.getAllChannels();
        Set<ChannelDependency> channelDependencies = client.getChannelDependencies();

        if (key == Token.WILDCARD) {
            for (Channel channel : channels) {
                try {
                    addDependenciesToChannel(channelDependencies, channel);
                    File fXml = new File(path + channel.getName() + ".xml");
                    out.println("Exporting " + channel.getName());
                    String channelXML = serializer.serialize(channel);
                    FileUtils.writeStringToFile(fXml, channelXML);
                } catch (IOException e) {
                    error("unable to write file " + path + ": " + e, e);
                }
            }
            out.println("Export Complete.");
            return;
        } else {
            File fXml = new File(path);
            StringToken skey = Token.stringToken(key.getText());

            for (Channel channel : channels) {
                if (skey.equalsIgnoreCase(channel.getName()) != skey.equalsIgnoreCase(channel.getId())) {
                    addDependenciesToChannel(channelDependencies, channel);
                    out.println("Exporting " + channel.getName());
                    String channelXML = serializer.serialize(channel);
                    try {
                        FileUtils.writeStringToFile(fXml, channelXML);
                    } catch (IOException e) {
                        error("unable to write file " + path + ": " + e, e);
                    }
                    out.println("Export Complete.");
                    return;
                }
            }
        }
    }

    private void addDependenciesToChannel(Set<ChannelDependency> channelDependencies, Channel channel) {
        Set<String> dependentIds = new HashSet<String>();
        Set<String> dependencyIds = new HashSet<String>();
        for (ChannelDependency channelDependency : channelDependencies) {
            if (StringUtils.equals(channelDependency.getDependencyId(), channel.getId())) {
                dependentIds.add(channelDependency.getDependentId());
            } else if (StringUtils.equals(channelDependency.getDependentId(), channel.getId())) {
                dependencyIds.add(channelDependency.getDependencyId());
            }
        }

        if (CollectionUtils.isNotEmpty(dependentIds)) {
            channel.setDependentIds(dependentIds);
        }
        if (CollectionUtils.isNotEmpty(dependencyIds)) {
            channel.setDependencyIds(dependencyIds);
        }
    }

    private void commandAllChannelStats(Token[] arguments) throws ClientException {
        out.println("Received\tFiltered\tQueued\t\tSent\t\tErrored\t\tName");

        List<DashboardStatus> channelStatuses = client.getAllChannelStatuses();

        for (DashboardStatus channelStatus : channelStatuses) {
            ChannelStatistics stats = client.getStatistics(channelStatus.getChannelId());
            out.println(stats.getReceived() + "\t\t" + stats.getFiltered() + "\t\t" + channelStatus.getQueued()
                    + "\t\t" + stats.getSent() + "\t\t" + stats.getError() + "\t\t" + channelStatus.getName());
        }
    }

    private void commandChannelList(Token[] arguments) throws ClientException {
        List<Channel> allChannels = client.getAllChannels();
        out.println("ID\t\t\t\t\tEnabled\t\tName");
        String enable = "";
        for (Iterator<Channel> iter = allChannels.iterator(); iter.hasNext();) {
            Channel channel = iter.next();
            if (channel.isEnabled()) {
                enable = "YES";
            } else {
                enable = "NO";
            }
            out.println(channel.getId() + "\t" + enable + "\t\t" + channel.getName());
        }
    }

    private void commandChannelDisable(Token[] arguments) throws ClientException {
        for (Channel channel : getMatchingChannels(arguments[2])) {
            if (channel.isEnabled()) {
                channel.setEnabled(false);
                client.updateChannel(channel, true);
                out.println("Channel '" + channel.getName() + "' Disabled");
            }
        }
    }

    private void commandChannelEnable(Token[] arguments) throws ClientException {
        for (Channel channel : getMatchingChannels(arguments[2])) {
            if (!(channel instanceof InvalidChannel) && !channel.isEnabled()) {
                channel.setEnabled(true);
                client.updateChannel(channel, true);
                out.println("Channel '" + channel.getName() + "' Enabled");
            }
        }
    }

    private void commandChannelRemove(Token[] arguments) throws ClientException {
        for (Channel channel : getMatchingChannels(arguments[2])) {
            if (channel.isEnabled()) {
                channel.setEnabled(false);
            }
            client.removeChannel(channel.getId());
            out.println("Channel '" + channel.getName() + "' Removed");
        }
    }

    private void commandChannelStart(Token[] arguments) throws ClientException {
        for (DashboardStatus channel : getMatchingChannelStatuses(arguments[2])) {
            if (channel.getState().equals(DeployedState.PAUSED)
                    || channel.getState().equals(DeployedState.STOPPED)) {
                if (channel.getState().equals(DeployedState.PAUSED)) {
                    client.resumeChannel(channel.getChannelId());
                    out.println("Channel '" + channel.getName() + "' Resumed");
                } else {
                    client.startChannel(channel.getChannelId());
                    out.println("Channel '" + channel.getName() + "' Started");
                }
            }
        }
    }

    private void commandChannelStop(Token[] arguments) throws ClientException {
        for (DashboardStatus channel : getMatchingChannelStatuses(arguments[2])) {
            if (channel.getState().equals(DeployedState.PAUSED)
                    || channel.getState().equals(DeployedState.STARTED)) {
                client.stopChannel(channel.getChannelId());
                out.println("Channel '" + channel.getName() + "' Stopped");
            }
        }
    }

    private void commandChannelHalt(Token[] arguments) throws ClientException {
        for (DashboardStatus channel : getMatchingChannelStatuses(arguments[2])) {
            client.haltChannel(channel.getChannelId());
            out.println("Channel '" + channel.getName() + "' Halted");
        }
    }

    private void commandChannelPause(Token[] arguments) throws ClientException {
        for (DashboardStatus channel : getMatchingChannelStatuses(arguments[2])) {
            if (channel.getState().equals(DeployedState.STARTED)) {
                client.pauseChannel(channel.getChannelId());
                out.println("Channel '" + channel.getName() + "' Paused");
            }
        }
    }

    private void commandChannelResume(Token[] arguments) throws ClientException {
        for (DashboardStatus channel : getMatchingChannelStatuses(arguments[2])) {
            if (channel.getState().equals(DeployedState.PAUSED)) {
                client.resumeChannel(channel.getChannelId());
                out.println("Channel '" + channel.getName() + "' Resumed");
            }
        }
    }

    private void commandChannelStats(Token[] arguments) throws ClientException {
        for (DashboardStatus channel : getMatchingChannelStatuses(arguments[2])) {
            ChannelStatistics stats = client.getStatistics(channel.getChannelId());
            out.println("Channel Stats for " + channel.getName());
            out.println("Received: " + stats.getReceived());
            out.println("Filtered: " + stats.getFiltered());
            out.println("Queued: " + channel.getQueued());
            out.println("Sent: " + stats.getSent());
            out.println("Errored: " + stats.getError());
        }
    }

    private void commandChannelRename(Token[] arguments) throws ClientException {
        for (Channel channel : getMatchingChannels(arguments[2])) {
            if (!(channel instanceof InvalidChannel)) {
                String oldName = channel.getName();
                channel.setName(arguments[3].getText());
                if (checkChannelName(channel.getName())) {
                    client.updateChannel(channel, true);
                    out.println("Channel '" + oldName + "' renamed to '" + channel.getName() + "'");
                }
            }
        }
    }

    private void commandChannelDeploy(Token[] arguments) throws ClientException {
        Set<String> channelIds = new LinkedHashSet<String>();

        for (Channel channel : getMatchingChannels(arguments[2])) {
            channelIds.add(channel.getId());
        }

        client.deployChannels(channelIds);
    }

    private void commandChannelUndeploy(Token[] arguments) throws ClientException {
        Set<String> channelIds = new LinkedHashSet<String>();

        for (Channel channel : getMatchingChannels(arguments[2])) {
            channelIds.add(channel.getId());
        }

        client.undeployChannels(channelIds);
    }

    /**
     * Checks to see if the passed in channel id already exists
     */
    public Channel getChannelById(String id) throws ClientException {
        for (Channel channel : client.getAllChannels()) {
            if (channel.getId().equalsIgnoreCase(id)) {
                return channel;
            }
        }
        return null;
    }

    /**
     * Checks to see if the passed in channel name already exists and is formatted correctly
     */
    public boolean checkChannelName(String name) throws ClientException {
        if (StringUtils.isEmpty(name)) {
            out.println("Channel name cannot be empty.");
            return false;
        } else if (name.length() > 40) {
            out.println("Channel name cannot be longer than 40 characters.");
            return false;
        }

        Pattern alphaNumericPattern = Pattern.compile("^[a-zA-Z_0-9\\-\\s]*$");
        Matcher matcher = alphaNumericPattern.matcher(name);

        if (!matcher.find()) {
            out.println("Channel name cannot have special characters besides hyphen, underscore, and space.");
            return false;
        }

        if (getChannelByName(name) != null) {
            return false;
        }

        return true;
    }

    private Channel getChannelByName(String name) throws ClientException {
        for (Channel channel : client.getAllChannels()) {
            if (channel.getName().equalsIgnoreCase(name)) {
                out.println("Channel \"" + name + "\" already exists.");
                return channel;
            }
        }
        return null;
    }

    private List<Channel> getMatchingChannels(Token key) throws ClientException {
        List<Channel> result = new ArrayList<Channel>();

        for (Channel channel : client.getAllChannels()) {
            if (matchesChannel(key, channel.getName(), channel.getId())) {
                result.add(channel);
            }

            // What if the key matches *two* channels, e.g. it's the ID of one
            // and
            // the name of another? Unlikely but possible...
            // if (result.size() > 0 && key != Token.WILDCARD)
            // break;
        }
        return result;
    }

    // Yuck: this is nearly identical to getMatchingChannels(), but there does
    // not appear to be a way to go from Channel to ChannelStatus (or
    // vice-versa). If
    // there was, all channel methods could operate on a Channel object (or a
    // ChannelStatus
    // object), and we would only need one getMatching...() method.
    private List<DashboardStatus> getMatchingChannelStatuses(Token key) throws ClientException {
        List<DashboardStatus> result = new ArrayList<DashboardStatus>();

        for (DashboardStatus status : client.getAllChannelStatuses()) {
            if (matchesChannel(key, status.getName(), status.getChannelId())) {
                result.add(status);
            }

            // Again, what if the key matches two channels?
            // if (key != Token.WILDCARD)
            // break;

        }
        return result;
    }

    private boolean matchesChannel(Token key, String name, String id) {
        if (key == Token.WILDCARD)
            return true;
        StringToken skey = (StringToken) key;
        return skey.equalsIgnoreCase(name) || skey.equalsIgnoreCase(id);
    }

    private void commandClearAllMessages(Token[] arguments) throws ClientException {
        Set<String> channelIds = new HashSet<String>();

        for (Channel channel : client.getAllChannels()) {
            channelIds.add(channel.getId());
        }

        client.removeAllMessages(channelIds, true, false);
    }

    private void commandResetstats(Token[] arguments) throws ClientException {
        boolean lifetime = false;
        if (arguments.length >= 2 && arguments[1] == Token.LIFETIME) {
            lifetime = true;
        }

        if (lifetime) {
            client.clearAllStatistics();
        } else {
            List<DashboardStatus> channelStatuses = client.getAllChannelStatuses();

            Map<String, List<Integer>> channelConnectorMap = new HashMap<String, List<Integer>>();

            for (DashboardStatus status : channelStatuses) {
                String channelId = status.getChannelId();
                Integer metaDataId = status.getMetaDataId();

                List<Integer> metaDataIds = channelConnectorMap.get(channelId);

                if (metaDataIds == null) {
                    metaDataIds = new ArrayList<Integer>();
                    channelConnectorMap.put(channelId, metaDataIds);
                }

                metaDataIds.add(metaDataId);

                if (CollectionUtils.isNotEmpty(status.getChildStatuses())) {
                    for (DashboardStatus childStatus : status.getChildStatuses()) {
                        metaDataIds.add(childStatus.getMetaDataId());
                    }
                }
            }

            client.clearStatistics(channelConnectorMap, true, true, true, true);
        }
    }

    private void commandDumpEvents(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 2)) {
            return;
        }

        String dumpFilename = arguments[2].getText();
        dumpFilename = replaceValues(dumpFilename);

        StringBuilder builder = new StringBuilder();
        builder.append("Mirth Connect Event Log Dump: " + (new Date()).toString() + "\n");
        builder.append(ServerEvent.getExportHeader() + "\n");

        File dumpFile = new File(dumpFilename);

        try {
            int maxEventId = client.getMaxEventId();
            EventFilter filter = new EventFilter();
            filter.setMaxEventId(maxEventId);

            PaginatedEventList eventList = new PaginatedEventList();
            eventList.setClient(client);
            eventList.setPageSize(20);
            eventList.setEventFilter(filter);

            int pageNumber = 1;

            while (eventList.loadPageNumber(pageNumber)) {
                for (ServerEvent event : eventList) {
                    builder.append(event.toExportString() + "\n");
                }

                pageNumber++;
            }

            FileUtils.writeStringToFile(dumpFile, builder.toString());
        } catch (ListHandlerException lhe) {
            lhe.printStackTrace();
        } catch (IOException ioe) {
            error("Could not write file: " + dumpFile.getAbsolutePath(), ioe);
        } catch (Exception e) {
            error("Could not retrieve events", e);
            e.printStackTrace();
        }

        out.println("Events written to " + dumpFilename);
    }

    private void commandDumpStats(Token[] arguments) throws ClientException {
        if (hasInvalidNumberOfArguments(arguments, 2)) {
            return;
        }

        String dumpFilename = arguments[2].getText();
        dumpFilename = replaceValues(dumpFilename);

        StringBuilder builder = new StringBuilder();
        builder.append("Mirth Channel Statistics Dump: " + (new Date()).toString() + "\n");
        builder.append("Name, Received, Filtered, Queued, Sent, Errored\n");

        List<DashboardStatus> channelStatuses = client.getAllChannelStatuses();

        for (DashboardStatus channelStatus : channelStatuses) {
            ChannelStatistics stats = client.getStatistics(channelStatus.getChannelId());
            builder.append(channelStatus.getName() + ", " + stats.getReceived() + ", " + stats.getFiltered() + ", "
                    + channelStatus.getQueued() + ", " + stats.getSent() + ", " + stats.getError() + "\n");
        }

        File dumpFile = new File(dumpFilename);

        try {
            FileUtils.writeStringToFile(dumpFile, builder.toString());
            out.println("Stats written to " + dumpFilename);
        } catch (IOException e) {
            error("Could not write file: " + dumpFile.getAbsolutePath(), e);
        }
    }

    private void doImportScript(File scriptFile) throws ClientException {
        ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();
        String scriptsXml = new String();

        try {
            scriptsXml = FileUtils.readFileToString(scriptFile);
        } catch (Exception e) {
            error("invalid script file.", e);
            return;
        }

        Map<String, String> scriptsMap = serializer.deserialize(scriptsXml, Map.class);
        client.setGlobalScripts(scriptsMap);
    }

    private void doImportChannel(File importFile, boolean force) throws ClientException {
        Channel importChannel = null;

        try {
            String channelXML = FileUtils.readFileToString(importFile);
            importChannel = ObjectXMLSerializer.getInstance().deserialize(channelXML, Channel.class);
        } catch (Exception e1) {
            error("invalid channel file.", e1);
            return;
        }

        String channelName = importChannel.getName();
        String channelId = importChannel.getId();
        String tempId = client.getGuid();
        importChannel.setRevision(0);

        Channel idChannelMatch = getChannelById(channelId);
        Channel nameChannelMatch = getChannelByName(channelName);

        // Check if channel id already exists
        if (idChannelMatch != null) {
            if (!force) {
                importChannel.setId(tempId);
            } else {
                importChannel.setRevision(idChannelMatch.getRevision());
            }
        }

        // Check if channel name already exists
        if (nameChannelMatch != null) {
            if (!force) {
                importChannel.setName(tempId);
            } else {
                importChannel.setRevision(nameChannelMatch.getRevision());
                importChannel.setId(nameChannelMatch.getId());
            }
        }

        importChannelDependencies(importChannel);

        client.updateChannel(importChannel, true);
        out.println("Channel '" + channelName + "' imported successfully.");
    }

    private void importChannelDependencies(Channel importChannel) throws ClientException {
        if (CollectionUtils.isNotEmpty(importChannel.getDependentIds())
                || CollectionUtils.isNotEmpty(importChannel.getDependencyIds())) {
            Set<ChannelDependency> cachedChannelDependencies = client.getChannelDependencies();
            Set<ChannelDependency> channelDependencies = new HashSet<ChannelDependency>(cachedChannelDependencies);

            if (CollectionUtils.isNotEmpty(importChannel.getDependentIds())) {
                for (String dependentId : importChannel.getDependentIds()) {
                    if (StringUtils.isNotBlank(dependentId)
                            && !StringUtils.equals(dependentId, importChannel.getId())) {
                        channelDependencies.add(new ChannelDependency(dependentId, importChannel.getId()));
                    }
                }
            }

            if (CollectionUtils.isNotEmpty(importChannel.getDependencyIds())) {
                for (String dependencyId : importChannel.getDependencyIds()) {
                    if (StringUtils.isNotBlank(dependencyId)
                            && !StringUtils.equals(dependencyId, importChannel.getId())) {
                        channelDependencies.add(new ChannelDependency(importChannel.getId(), dependencyId));
                    }
                }
            }

            if (!channelDependencies.equals(cachedChannelDependencies)) {
                try {
                    client.setChannelDependencies(channelDependencies);
                } catch (ClientException e) {
                    error("Unable to save channel dependencies.", e);
                }
            }

            importChannel.clearDependencies();
        }
    }

    private void doImportAlert(File importFile, boolean force) throws ClientException {
        ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();
        List<AlertModel> alertList;

        try {
            alertList = (List<AlertModel>) serializer.deserializeList(FileUtils.readFileToString(importFile)
                    .replaceAll("\\&\\#x0D;\\n", "\n").replaceAll("\\&\\#x0D;", "\n"), AlertModel.class);
        } catch (Exception e) {
            error("invalid alert file.", e);
            return;
        }

        removeInvalidItems(alertList, AlertModel.class);

        for (AlertModel importAlert : alertList) {
            String alertName = importAlert.getName();
            String tempId = client.getGuid();

            // Check to see that the alert name doesn't already exist.
            if (!checkAlertName(alertName)) {
                if (!force) {
                    importAlert.setName(tempId);
                    importAlert.setId(tempId);
                } else {
                    for (AlertModel alert : client.getAllAlerts()) {
                        if (alert.getName().equalsIgnoreCase(alertName)) {
                            // If overwriting, use the old id
                            importAlert.setId(alert.getId());
                        }
                    }
                }
            }

            client.updateAlert(importAlert);
            out.println("Alert '" + alertName + "' imported successfully.");
        }
    }

    private boolean checkAlertName(String name) throws ClientException {
        if (name.equals("")) {
            out.println("Alert name cannot be empty.");
            return false;
        }

        Pattern alphaNumericPattern = Pattern.compile("^[a-zA-Z_0-9\\-\\s]*$");
        Matcher matcher = alphaNumericPattern.matcher(name);

        if (!matcher.find()) {
            out.println("Alert name cannot have special characters besides hyphen, underscore, and space.");
            return false;
        }

        for (AlertModel alert : client.getAllAlerts()) {
            if (alert.getName().equalsIgnoreCase(name)) {
                out.println("Alert \"" + name + "\" already exists.");
                return false;
            }
        }
        return true;
    }

    private String replaceValues(String source) {
        source = source.replaceAll("\\$\\{date\\}", getTimeStamp());
        return source;
    }

    private String getTimeStamp() {
        Date currentTime = new Date();
        return formatter.format(currentTime);
    }

    /**
     * Removes items from the list that are not of the expected class.
     */
    private void removeInvalidItems(List<?> list, Class<?> expectedClass) {
        int originalSize = list.size();

        for (int i = 0; i < list.size(); i++) {
            if (!expectedClass.isInstance(list.get(i))) {
                list.remove(i--);
            }
        }

        if (list.size() < originalSize) {
            if (list.size() == 0) {
                out.println(
                        "The imported object(s) are not of the expected class: " + expectedClass.getSimpleName());
            } else {
                out.println(
                        "One or more imported objects were skipped, because they are not of the expected class: "
                                + expectedClass.getSimpleName());
            }
        }
    }
}