com.nridge.core.app.mgr.AppMgr.java Source code

Java tutorial

Introduction

Here is the source code for com.nridge.core.app.mgr.AppMgr.java

Source

/*
 * NorthRidge Software, LLC - Copyright (c) 2015.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nridge.core.app.mgr;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.nridge.core.base.field.data.DataField;
import com.nridge.core.base.field.data.DataTextField;
import com.nridge.core.base.std.NSException;
import org.apache.commons.cli.*;
import org.apache.commons.configuration.*;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * The Application Manager provides a number of services to a parent application
 * while simplifying the interfaces needed to use them.  The list of services
 * provided include:
 * <ul>
 *     <li>Command line processing</li>
 *     <li>Configuration property management</li>
 *     <li>Robust logging facility</li>
 *     <li>Task (a.k.a. thread) management</li>
 * </ul>
 * <p>
 * Much of this framework was designed around the application manager to ensure
 * consistency and code reuse.
 * </p>
 *
 * @see <a href="http://logback.qos.ch/">Logback Project</a>
 * @see <a href="http://commons.apache.org/cli/index.html">Apache Commons CLI</a>
 * @see <a href="http://commons.apache.org/configuration/">Apache Commons Configuration</a>
 * @see <a href="http://www.ibm.com/developerworks/library/j-jtp05236/">Dealing with InterruptedException</a>
 *
 * @since 1.0
 * @author Al Cole
 */
public class AppMgr {
    public final String LOGMSG_TRACE_ENTER = "Enter";
    public final String LOGMSG_TRACE_DEPART = "Depart";

    public final String CMDARG_RUNALL_TASKS = "all";
    public final String CMDARG_TESTALL_TASKS = "all";

    public final String LOG_PROPERTY_FILE_NAME = "logback.xml";
    public final String APP_PROPERTY_FILE_NAME = "application.properties";

    public final String APP_PROPERTY_DS_PATH = "app.ds_path";
    public final String APP_PROPERTY_INS_PATH = "app.ins_path";
    public final String APP_PROPERTY_CFG_PATH = "app.cfg_path";
    public final String APP_PROPERTY_LOG_PATH = "app.log_path";
    public final String APP_PROPERTY_RDB_PATH = "app.rdb_path";
    public final String APP_PROPERTY_GDB_PATH = "app.gdb_path";

    private CommandLine mCmdLine;
    private AtomicBoolean mIsAlive;
    private boolean mIsPathsExplicit;
    private ArrayList<Task> mTaskList;
    private boolean mIsAbortHandlerEnabled;
    private HashMap<String, Object> mPropertyMap;
    private CompositeConfiguration mConfiguration;
    private String mInsPathName, mCfgPathName, mLogPathName;
    private String mDSPathName, mRDBMSPathName, mGraphPathName;

    /**
     * Default constructor.
     */
    public AppMgr() {
        mIsPathsExplicit = false;
        mIsAlive = new AtomicBoolean(true);
        mPropertyMap = new HashMap<String, Object>();
        mInsPathName = System.getProperty("user.dir");
        mCfgPathName = String.format("%s%ccfg", mInsPathName, File.separatorChar);
        mLogPathName = String.format("%s%clog", mInsPathName, File.separatorChar);
        mDSPathName = String.format("%s%cds", mInsPathName, File.separatorChar);
        mRDBMSPathName = String.format("%s%crdb", mInsPathName, File.separatorChar);
        mGraphPathName = String.format("%s%cgdb", mInsPathName, File.separatorChar);
    }

    /**
     * Constructor suitable for use within Servlet Container where the default
     * paths for the application are difficult to derive automatically.
     *
     * @param aInsPathName Installation path name.
     * @param aCfgPathName Configuration path name.
     * @param aLogPathName Log file output path name.
     * @param aDSPathName Data source path name.
     */
    public AppMgr(String aInsPathName, String aCfgPathName, String aLogPathName, String aDSPathName) {
        mIsPathsExplicit = true;
        mIsAlive = new AtomicBoolean(true);
        mPropertyMap = new HashMap<String, Object>();
        if (StringUtils.isNotEmpty(aInsPathName))
            mInsPathName = aInsPathName;
        else
            mInsPathName = System.getProperty("user.dir");
        if (StringUtils.isNotEmpty(aCfgPathName))
            mCfgPathName = aCfgPathName;
        else
            mCfgPathName = String.format("%s%ccfg", mInsPathName, File.separatorChar);
        if (StringUtils.isNotEmpty(aLogPathName))
            mLogPathName = aLogPathName;
        else
            mLogPathName = String.format("%s%clog", mInsPathName, File.separatorChar);
        if (StringUtils.isNotEmpty(aDSPathName))
            mDSPathName = aDSPathName;
        else
            mDSPathName = String.format("%s%cds", mInsPathName, File.separatorChar);
        mRDBMSPathName = String.format("%s%crdb", mInsPathName, File.separatorChar);
        mGraphPathName = String.format("%s%cgdb", mInsPathName, File.separatorChar);
    }

    /**
     * Constructor suitable for use within Servlet Container where the default
     * paths for the application are difficult to derive automatically.
     *
     * @param aInsPathName Installation path name.
     * @param aCfgPathName Configuration path name.
     * @param aLogPathName Log file output path name.
     * @param aDSPathName Data source path name.
     * @param anRDBMSPathName RDBMS path name.
     * @param aGraphPathName Graph path name.
     */
    public AppMgr(String aInsPathName, String aCfgPathName, String aLogPathName, String aDSPathName,
            String anRDBMSPathName, String aGraphPathName) {
        mIsPathsExplicit = true;
        mIsAlive = new AtomicBoolean(true);
        mPropertyMap = new HashMap<String, Object>();
        if (StringUtils.isNotEmpty(aInsPathName))
            mInsPathName = aInsPathName;
        else
            mInsPathName = System.getProperty("user.dir");
        if (StringUtils.isNotEmpty(aCfgPathName))
            mCfgPathName = aCfgPathName;
        else
            mCfgPathName = String.format("%s%ccfg", mInsPathName, File.separatorChar);
        if (StringUtils.isNotEmpty(aLogPathName))
            mLogPathName = aLogPathName;
        else
            mLogPathName = String.format("%s%clog", mInsPathName, File.separatorChar);
        if (StringUtils.isNotEmpty(aDSPathName))
            mDSPathName = aDSPathName;
        else
            mDSPathName = String.format("%s%cds", mInsPathName, File.separatorChar);
        if (StringUtils.isNotEmpty(anRDBMSPathName))
            mRDBMSPathName = aGraphPathName;
        else
            mRDBMSPathName = String.format("%s%crdb", mInsPathName, File.separatorChar);
        if (StringUtils.isNotEmpty(aGraphPathName))
            mGraphPathName = aGraphPathName;
        else
            mGraphPathName = String.format("%s%cgdb", mInsPathName, File.separatorChar);
    }

    /**
     * Returns <i>true</i> if the task manager is active and not in the process
     * of shutting down and <i>false</i> otherwise.
     *
     * @return <i>true</i> or <i>false</i>
     */
    public boolean isAlive() {
        return mIsAlive.get();
    }

    /**
     * Assigns the boolean flag to the internally managed "is alive"
     * boolean value.  This method should only be called by the
     * shutdown handler method.
     *
     * @param aFlag Boolean flag.
     */
    public void setIsAliveFlag(boolean aFlag) {
        mIsAlive.set(aFlag);
    }

    /**
     * Returns <i>true</i> if the task manager is configured to use an internal
     * abort (a.k.a. JVM addShutdownHook method) handler.
     *
     * @return <i>true</i> or <i>false</i>
     */
    public boolean isAbortHandlerEnabled() {
        return mIsAbortHandlerEnabled;
    }

    /**
     * Enables/disables the use of an internal abort (a.k.a. JVM addShutdownHook
     * method) handler.
     *
     * @param anIsEnabled Boolean flag.
     */
    public void setAbortHandlerEnabledFlag(boolean anIsEnabled) {
        mIsAbortHandlerEnabled = anIsEnabled;
    }

    /**
     * Adds an application manager task to the internal queue of managed task
     * threads.
     *
     * @param aTask Application manager task.
     */
    public void addTask(Task aTask) {
        if (mTaskList == null)
            mTaskList = new ArrayList<Task>();
        if (aTask != null)
            mTaskList.add(aTask);
    }

    /**
     * Returns a {@link Task} matching the parameter run name.
     *
     * @param aName Run name of the task.
     * @return A {@link Task} or <i>null</i> if not found.
     */
    public Task getTaskByRunName(String aName) {
        if (mTaskList != null) {
            for (Task appTask : mTaskList) {
                if (appTask.getRunName().equals(aName))
                    return appTask;
            }
        }

        return null;
    }

    /**
     * Returns a <i>Task</i> matching the parameter test name.
     *
     * @param aName Test name of the task.
     * @return A {@link Task} or <i>null</i> if not found.
     */
    public Task getTaskByTestName(String aName) {
        if (mTaskList != null) {
            for (Task appTask : mTaskList) {
                if (appTask.getTestName().equals(aName))
                    return appTask;
            }
        }
        return null;
    }

    private String getInsPathName() {
        return mInsPathName;
    }

    private String deriveCfgPathName() throws NSException {
        String cfgFileName, cfgPathName, cfgPathFileName;

        String installPathName = getInsPathName();
        if (mCmdLine == null)
            cfgFileName = APP_PROPERTY_FILE_NAME;
        else
            cfgFileName = mCmdLine.getOptionValue("cfgfile", APP_PROPERTY_FILE_NAME);

        // Did we get an absolute path/file name to cfgfile?

        File cfgFile = new File(cfgFileName);
        if (cfgFile.exists())
            cfgPathName = FilenameUtils.getFullPathNoEndSeparator(cfgFileName);
        else {

            // Is our default path good enough to locate the property file?

            cfgPathFileName = String.format("%s%c%s", mCfgPathName, File.separatorChar, cfgFileName);
            cfgFile = new File(cfgPathFileName);
            if (cfgFile.exists())
                cfgPathName = FilenameUtils.getFullPathNoEndSeparator(cfgFile.getAbsolutePath());
            else {

                // Our we running from a maven source tree?

                cfgPathFileName = String.format("%s%csrc%cmain%cresources%c%s", installPathName, File.separatorChar,
                        File.separatorChar, File.separatorChar, File.separatorChar, cfgFileName);
                cfgFile = new File(cfgPathFileName);
                if (cfgFile.exists())
                    cfgPathName = FilenameUtils.getFullPathNoEndSeparator(cfgFile.getAbsolutePath());
                else {

                    // Last chance - are we running from a standard install area?

                    cfgPathFileName = String.format("cfg%c%s", File.separatorChar, cfgFileName);
                    cfgFile = new File(cfgPathFileName);
                    if (cfgFile.exists())
                        cfgPathName = FilenameUtils.getFullPathNoEndSeparator(cfgFile.getAbsolutePath());
                    else
                        throw new NSException("Unable to locate application properties file: " + cfgPathFileName);
                }
            }
        }

        mCfgPathName = cfgPathName;

        return mCfgPathName;
    }

    private String deriveCfgPathFileName() throws NSException {
        String cfgFileName, cfgPathFileName;

        if (mCmdLine == null)
            cfgFileName = APP_PROPERTY_FILE_NAME;
        else
            cfgFileName = mCmdLine.getOptionValue("cfgfile", APP_PROPERTY_FILE_NAME);

        File cfgFile = new File(cfgFileName);
        if (cfgFile.exists()) {
            cfgPathFileName = cfgFileName;
            mCfgPathName = FilenameUtils.getFullPathNoEndSeparator(cfgFile.getAbsolutePath());
        } else {
            cfgPathFileName = String.format("%s%c%s", deriveCfgPathName(), File.separatorChar, cfgFileName);
        }

        return cfgPathFileName;
    }

    private String deriveLogPathName() throws NSException {
        String logPathName;

        if (mCmdLine == null)
            logPathName = mLogPathName;
        else
            logPathName = mCmdLine.getOptionValue(APP_PROPERTY_LOG_PATH, mLogPathName);

        File logPathFile = new File(logPathName);
        if (logPathFile.exists())
            mLogPathName = logPathName;
        else {
            logPathName = String.format("%s%clog", mInsPathName, File.separatorChar);
            logPathFile = new File(logPathName);
            if (logPathFile.exists())
                mLogPathName = logPathName;
            else {

                // Are we within a servlet - if yes, then try a relative path.

                String catalinaHome = System.getProperty("catalina.home");
                if (StringUtils.isNotEmpty(catalinaHome)) {
                    logPathName = String.format("%s%clogs", catalinaHome, File.separatorChar);
                    logPathFile = new File(logPathName);
                    if (logPathFile.exists())
                        mLogPathName = logPathName;
                }
            }
        }

        return mLogPathName;
    }

    // Note: A DS path is optional - so we will not generate an exception if it is missing.

    private String deriveDSPathName() {
        String dsPathName;

        if (mCmdLine == null)
            dsPathName = mDSPathName;
        else
            dsPathName = mCmdLine.getOptionValue(APP_PROPERTY_DS_PATH, mDSPathName);

        File dsPathFile = new File(dsPathName);
        if (dsPathFile.exists())
            mDSPathName = dsPathName;
        else {
            dsPathName = String.format("%s%cds", mInsPathName, File.separatorChar);
            dsPathFile = new File(dsPathName);
            if (dsPathFile.exists())
                mDSPathName = dsPathName;
            else {

                // Our we running from a maven source tree?

                dsPathName = String.format("%s%csrc%cmain%cresources%cds", mInsPathName, File.separatorChar,
                        File.separatorChar, File.separatorChar, File.separatorChar);
                dsPathFile = new File(dsPathName);
                if (dsPathFile.exists())
                    mDSPathName = dsPathName;
                else {

                    // Are we within a servlet - if yes, then try a relative path.

                    String catalinaHome = System.getProperty("catalina.home");
                    if (StringUtils.isNotEmpty(catalinaHome)) {
                        dsPathName = String.format("..%c..%cds", File.separatorChar, File.separatorChar);
                        dsPathFile = new File(dsPathName);
                        if (dsPathFile.exists())
                            mDSPathName = dsPathName;
                    }
                }
            }
        }

        return mDSPathName;
    }

    // Note: An RDBMS path is optional - so we will not generate an exception if it is missing.

    private String deriveRDBMSPathName() {
        String rdbmsPathName;

        if (mCmdLine == null)
            rdbmsPathName = mRDBMSPathName;
        else
            rdbmsPathName = mCmdLine.getOptionValue(APP_PROPERTY_RDB_PATH, mRDBMSPathName);

        File dsPathFile = new File(rdbmsPathName);
        if (dsPathFile.exists())
            mRDBMSPathName = rdbmsPathName;
        else {
            rdbmsPathName = String.format("%s%crdb", mInsPathName, File.separatorChar);
            dsPathFile = new File(rdbmsPathName);
            if (dsPathFile.exists())
                mRDBMSPathName = rdbmsPathName;
            else {

                // Are we within a servlet - if yes, then try a relative path.

                String catalinaHome = System.getProperty("catalina.home");
                if (StringUtils.isNotEmpty(catalinaHome)) {
                    rdbmsPathName = String.format("..%c..%crdb", File.separatorChar, File.separatorChar);
                    dsPathFile = new File(rdbmsPathName);
                    if (dsPathFile.exists())
                        mRDBMSPathName = rdbmsPathName;
                }
            }
        }

        return mRDBMSPathName;
    }

    // Note: A Graph DB path is optional - so we will not generate an exception if it is missing.

    private String deriveGraphPathName() {
        String graphPathName;

        if (mCmdLine == null)
            graphPathName = mGraphPathName;
        else
            graphPathName = mCmdLine.getOptionValue(APP_PROPERTY_GDB_PATH, mGraphPathName);

        File dsPathFile = new File(graphPathName);
        if (dsPathFile.exists())
            mGraphPathName = graphPathName;
        else {
            graphPathName = String.format("%s%cgdb", mInsPathName, File.separatorChar);
            dsPathFile = new File(graphPathName);
            if (dsPathFile.exists())
                mGraphPathName = graphPathName;
            else {

                // Are we within a servlet - if yes, then try a relative path.

                String catalinaHome = System.getProperty("catalina.home");
                if (StringUtils.isNotEmpty(catalinaHome)) {
                    graphPathName = String.format("..%c..%cgdb", File.separatorChar, File.separatorChar);
                    dsPathFile = new File(graphPathName);
                    if (dsPathFile.exists())
                        mGraphPathName = graphPathName;
                }
            }
        }

        return mGraphPathName;
    }

    /**
     * Loads the default property files ("application.properties" and
     * "logback.xml") from the file system and assigns default application
     * properties.
     * <p>
     * <b>Note:</b>&nbsp;This method will establish a default 5 minute reloading
     * policy for the "application.properties" file.  Therefore, any
     * changes to this property file while the application is running
     * will be recognized within a 5 minute period.
     * </p>
     *
     * @throws NSException Typically thrown for I/O related issues.
     */
    public void loadPropertyFiles() throws NSException {
        String logFileName;

        try {

            // First, we will read our application properties.

            mConfiguration = new CompositeConfiguration();
            mConfiguration.setDelimiterParsingDisabled(true);
            mConfiguration.addConfiguration(new SystemConfiguration());
            mConfiguration.addConfiguration(new EnvironmentConfiguration());
            PropertiesConfiguration propertyCfg = new PropertiesConfiguration(deriveCfgPathFileName());
            FileChangedReloadingStrategy reloadingStrategy = new FileChangedReloadingStrategy();
            reloadingStrategy.setRefreshDelay(DateUtils.MILLIS_PER_MINUTE * 2L);
            propertyCfg.setReloadingStrategy(reloadingStrategy);
            mConfiguration.addConfiguration(propertyCfg);

            // Next, we will load our Logback properties file and configure our application logger.

            if (mCmdLine == null)
                logFileName = LOG_PROPERTY_FILE_NAME;
            else
                logFileName = mCmdLine.getOptionValue("logfile", LOG_PROPERTY_FILE_NAME);
            File logFile = new File(logFileName);
            if (!logFile.exists()) {
                String logPathFileName = String.format("%s%c%s", deriveCfgPathName(), File.separatorChar,
                        logFileName);
                logFile = new File(logPathFileName);
                if (logFile.exists())
                    logFileName = logPathFileName;
                else
                    throw new NSException("Unable to locate logging properties file: " + logFileName);
            }

            if (StringUtils.isEmpty(getString(APP_PROPERTY_INS_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_INS_PATH, getInsPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_CFG_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_CFG_PATH, deriveCfgPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_LOG_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_LOG_PATH, deriveLogPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_DS_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_DS_PATH, deriveDSPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_RDB_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_RDB_PATH, deriveRDBMSPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_GDB_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_GDB_PATH, deriveGraphPathName());

            LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            JoranConfigurator joranConfigurator = new JoranConfigurator();
            joranConfigurator.setContext(loggerContext);
            loggerContext.reset();
            joranConfigurator.doConfigure(logFileName);
        } catch (ConfigurationException e) {
            throw new NSException("Configuration parsing error: " + e.getMessage());
        } catch (JoranException e) {
            throw new NSException("Logback parsing error: " + e.getMessage());
        } catch (ClassCastException ignored) {

            /* Note that a WARNING related to java.lang.ClassCastException will trigger due to
            Smart GWT and NSD being dependent on different releases of the same logging
            framework.  http://www.slf4j.org/codes.html */

        }
    }

    /**
     * Initializes the application manager command line processing
     * subsystem using the arguments provided from the parent
     * application.
     *
     * @param aCmdLineArgs An array of command line arguments (typically
     *                     handed down from the application main method).
     *
     * @throws NSException Thrown when the command line arguments are invalid.
     */
    public void init(String[] aCmdLineArgs) throws NSException {
        if (mTaskList == null)
            mTaskList = new ArrayList<Task>();

        Options cliOptions = new Options();
        cliOptions.addOption("help", false, "Generates a command line usage summary.");
        cliOptions.addOption("logfile", true, "Identifies the application logging properties file name.");
        cliOptions.addOption("cfgfile", true, "Identifies the application configuration properties file name.");
        cliOptions.addOption("run", true, String
                .format("Identifies the task name to execute (use '%s' for all tasks).", CMDARG_RUNALL_TASKS));
        cliOptions.addOption("test", true, String
                .format("Identifies the test name to execute (use '%s' for all tests).", CMDARG_TESTALL_TASKS));

        CommandLineParser cliParser = new DefaultParser();
        try {

            // First, we will process our command line arguments.

            mCmdLine = cliParser.parse(cliOptions, aCmdLineArgs);
            if (mCmdLine.hasOption("help")) {
                HelpFormatter helpFormatter = new HelpFormatter();
                helpFormatter.printHelp(aCmdLineArgs[0], cliOptions);
            } else {
                if ((!mCmdLine.hasOption("run")) && (!mCmdLine.hasOption("test")))
                    throw new NSException("You specify a 'run' or 'test' name to start this application.");
                else {
                    if (mCmdLine.hasOption("run")) {
                        String taskName = mCmdLine.getOptionValue("run");
                        if (!taskName.equals(CMDARG_RUNALL_TASKS)) {
                            Task appTask = getTaskByRunName(taskName);
                            if (appTask == null)
                                throw new NSException("Name does not match list of known tasks to execute.");
                        }
                    } else {
                        String taskName = mCmdLine.getOptionValue("test");
                        if (!taskName.equals(CMDARG_TESTALL_TASKS)) {
                            Task appTask = getTaskByTestName(taskName);
                            if (appTask == null)
                                throw new NSException("Name does not match list of known tasks to test.");
                        }
                    }
                }
            }
        } catch (ParseException e) {
            throw new NSException("Command line parsing error: " + e.getMessage());
        }

        // Next, we will load our application properties

        loadPropertyFiles();
    }

    /**
     * Initializes the application manager property management
     * subsystem using the composite configuration parameter
     * instance.
     *
     * @param aConfiguration Apache Commons configuration instance.
     *
     * @throws NSException Thrown when the command line arguments are invalid.
     * @see <a href="http://commons.apache.org/configuration/">Apache Commons Configuration</a>
     */
    public void init(CompositeConfiguration aConfiguration) throws NSException {
        String logPathName;
        mConfiguration = aConfiguration;

        if (mIsPathsExplicit)
            logPathName = mLogPathName;
        else
            logPathName = deriveLogPathName();
        String logFileName = mConfiguration.getString("app.log_file", "logback.xml");
        File logFile = new File(logFileName);
        if (!logFile.exists()) {
            String logPathFileName = String.format("%s%c%s", logPathName, File.separatorChar, logFile.getName());
            logFile = new File(logPathFileName);
            if (logFile.exists())
                logFileName = logPathFileName;
            else
                return; // Does not exist - return without error.
        }
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        JoranConfigurator joranConfigurator = new JoranConfigurator();
        joranConfigurator.setContext(loggerContext);
        loggerContext.reset();
        try {
            joranConfigurator.doConfigure(logFileName);
        } catch (JoranException e) {
            throw new NSException("Logback parsing error: " + e.getMessage());
        }

        if (mIsPathsExplicit) {
            mConfiguration.addProperty(APP_PROPERTY_INS_PATH, mInsPathName);
            mConfiguration.addProperty(APP_PROPERTY_CFG_PATH, mCfgPathName);
            mConfiguration.addProperty(APP_PROPERTY_LOG_PATH, mLogPathName);
            mConfiguration.addProperty(APP_PROPERTY_DS_PATH, mDSPathName);
            mConfiguration.addProperty(APP_PROPERTY_RDB_PATH, mRDBMSPathName);
            mConfiguration.addProperty(APP_PROPERTY_GDB_PATH, mGraphPathName);
        } else {
            if (StringUtils.isEmpty(getString(APP_PROPERTY_INS_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_INS_PATH, getInsPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_CFG_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_CFG_PATH, deriveCfgPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_LOG_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_LOG_PATH, deriveLogPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_DS_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_DS_PATH, deriveDSPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_RDB_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_RDB_PATH, deriveRDBMSPathName());
            if (StringUtils.isEmpty(getString(APP_PROPERTY_GDB_PATH)))
                mConfiguration.addProperty(APP_PROPERTY_GDB_PATH, deriveGraphPathName());
        }
    }

    /**
     * Executes one or more application tasks based on the the run/test command
     * line argument.
     * <p>
     * <b>Note:</b>If the word "all" is specified for the run/test argument
     * parameter, then all tasks defined for this application manager
     * will be executed in parallel.  Otherwise, only the task that matches
     * the run/test name will be executed.
     * </p>
     *
     * @throws NSException Could be thrown by the executing task.
     */
    public void execute() throws NSException {
        Thread appThread;
        Logger appLogger = getLogger(this, "execute");

        appLogger.trace(LOGMSG_TRACE_ENTER);

        if (mTaskList == null)
            throw new NSException("The task list is undefined and cannot be executed.");

        if (mIsAbortHandlerEnabled) {
            Runtime osRuntime = Runtime.getRuntime();
            osRuntime.addShutdownHook(new TaskAbort(this, mTaskList));
        }

        // http://www.vogella.com/articles/JavaConcurrency/article.html

        if (mCmdLine.hasOption("run")) {
            String taskName = mCmdLine.getOptionValue("run");
            if (taskName.equals(CMDARG_RUNALL_TASKS)) {
                ArrayList<Thread> threadList = new ArrayList<Thread>();
                for (Task appTask : mTaskList) {
                    appTask.init(this);
                    appThread = new Thread(appTask);
                    threadList.add(appThread);
                    appThread.start();
                }

                // Next, we will wait for each task thread to complete.

                for (Thread thread : threadList) {
                    try {
                        thread.join();
                    } catch (InterruptedException e) {
                        appLogger.warn("Interrupted Thread: " + e.getMessage());
                    }
                }
            } else {
                Task appTask = getTaskByRunName(taskName);
                if (appTask != null) {
                    appTask.init(this);
                    appTask.run();
                }
            }
        } else {
            if (mCmdLine.hasOption("test")) {
                String taskName = mCmdLine.getOptionValue("test");
                if (taskName.equals(CMDARG_TESTALL_TASKS)) {
                    for (Task appTask : mTaskList) {
                        appTask.init(this);
                        appTask.test();
                    }
                } else {
                    Task appTask = getTaskByTestName(taskName);
                    if (appTask != null) {
                        appTask.init(this);
                        appTask.test();
                    }
                }
            }
        }

        appLogger.trace(LOGMSG_TRACE_DEPART);
    }

    /**
     * Shutdowns all active tasks associated with this application manager.
     */
    public void shutdown() {
        Logger appLogger = getLogger(this, "shutdown");

        appLogger.trace(LOGMSG_TRACE_ENTER);

        if (mTaskList != null) {
            for (Task appTask : mTaskList) {
                if (appTask.isAlive())
                    appTask.shutdown();
            }
        }

        appLogger.trace(LOGMSG_TRACE_DEPART);
    }

    /**
     * Returns the command line instance.  You would typically use
     * this method if your application requires specialized logic
     * for command line processing.
     *
     * @return Apache Commons Command Line instance.
     * @see <a href="http://commons.apache.org/cli/index.html">Apache Commons CLI</a>
     */
    public CommandLine getCommandLine() {
        return mCmdLine;
    }

    /**
     * Convenience method that wraps the property name and its value into
     * a {@link DataField} (assuming that one matches the field name
     * parameter).
     *
     * @param aFieldName Property name.
     *
     * @return Data field if the property name matches or <i>null</i>
     * otherwise.
     */
    public DataField getField(String aFieldName) {
        String fieldValue = mConfiguration.getString(aFieldName);
        if (StringUtils.isEmpty(fieldValue))
            return null;
        else
            return new DataTextField(aFieldName, StringUtils.EMPTY, fieldValue);
    }

    /**
     * Identifies whether the property parsing logic should recognize
     * multi-values (comma delimiter) when parsing values.  The default
     * is <i>true</i>.
     *
     * @param aFlag If <i>false</i> multi-value parsing is disabled.
     */
    public void setMultiValueParsingFlag(boolean aFlag) {
        mConfiguration.setDelimiterParsingDisabled(aFlag);
    }

    /**
     * Returns the composite configuration instance. You would typically use
     * this method if your application requires specialized logic for property
     * management.
     *
     * @return Apache Commons Configuration Composite instance.
     * @see <a href="http://commons.apache.org/configuration/">Apache Commons Configuration</a>
     */
    public CompositeConfiguration getConfiguration() {
        return mConfiguration;
    }

    /**
     * Returns a typed value for the property name identified.
     *
     * @param aName Name of the property.
     *
     * @return Value of the property.
     */
    public boolean getBoolean(String aName) {
        return mConfiguration.getBoolean(aName);
    }

    /**
     * Returns a typed value for the property name identified
     * or the default value (if unmatched).
     *
     * @param aName Name of the property.
     * @param aDefaultValue Default value to return if property
     *                      name is not matched.
     *
     * @return Value of the property.
     */
    public boolean getBoolean(String aName, boolean aDefaultValue) {
        return mConfiguration.getBoolean(aName, aDefaultValue);
    }

    /**
     * Returns a typed value for the property name identified.
     *
     * @param aName Name of the property.
     *
     * @return Value of the property.
     */
    public double getDouble(String aName) {
        return mConfiguration.getDouble(aName);
    }

    /**
     * Returns a typed value for the property name identified
     * or the default value (if unmatched).
     *
     * @param aName Name of the property.
     * @param aDefaultValue Default value to return if property
     *                      name is not matched.
     *
     * @return Value of the property.
     */
    public double getDouble(String aName, double aDefaultValue) {
        return mConfiguration.getDouble(aName, aDefaultValue);
    }

    /**
     * Returns a typed value for the property name identified.
     *
     * @param aName Name of the property.
     *
     * @return Value of the property.
     */
    public float getFloat(String aName) {
        return mConfiguration.getFloat(aName);
    }

    /**
     * Returns a typed value for the property name identified
     * or the default value (if unmatched).
     *
     * @param aName Name of the property.
     * @param aDefaultValue Default value to return if property
     *                      name is not matched.
     *
     * @return Value of the property.
     */
    public float getFloat(String aName, float aDefaultValue) {
        return mConfiguration.getFloat(aName, aDefaultValue);
    }

    /**
     * Returns a typed value for the property name identified.
     *
     * @param aName Name of the property.
     *
     * @return Value of the property.
     */
    public int getInt(String aName) {
        return mConfiguration.getInt(aName);
    }

    /**
     * Returns a typed value for the property name identified
     * or the default value (if unmatched).
     *
     * @param aName Name of the property.
     * @param aDefaultValue Default value to return if property
     *                      name is not matched.
     *
     * @return Value of the property.
     */
    public int getInt(String aName, int aDefaultValue) {
        return mConfiguration.getInt(aName, aDefaultValue);
    }

    /**
     * Returns a typed value for the property name identified.
     *
     * @param aName Name of the property.
     *
     * @return Value of the property.
     */
    public long getLong(String aName) {
        return mConfiguration.getLong(aName);
    }

    /**
     * Returns a typed value for the property name identified
     * or the default value (if unmatched).
     *
     * @param aName Name of the property.
     * @param aDefaultValue Default value to return if property
     *                      name is not matched.
     *
     * @return Value of the property.
     */
    public long getLong(String aName, long aDefaultValue) {
        return mConfiguration.getLong(aName, aDefaultValue);
    }

    /**
     * Returns a typed value for the property name identified.
     *
     * @param aName Name of the property.
     *
     * @return Value of the property.
     */
    public String getString(String aName) {
        return mConfiguration.getString(aName);
    }

    /**
     * Returns a typed value for the property name identified
     * or the default value (if unmatched).
     *
     * @param aName Name of the property.
     * @param aDefaultValue Default value to return if property
     *                      name is not matched.
     *
     * @return Value of the property.
     */
    public String getString(String aName, String aDefaultValue) {
        return mConfiguration.getString(aName, aDefaultValue);
    }

    /**
     * Sets the character that is used as a multi-value delimiter.
     *
     * @param aDelimiter Delimiter character (comma is the default).
     */
    public void setMultiValueDelimiter(char aDelimiter) {
        mConfiguration.setListDelimiter(aDelimiter);
    }

    /**
     * Return <i>true</i> if the property name represents multiple
     * values or <i>false</i> otherwise.
     *
     * @param aName Name of the property.
     *
     * @return <i>true</i> or <i>false</i>
     */
    @SuppressWarnings({ "RedundantIfStatement" })
    public boolean isPropertyMultiValue(String aName) {
        String[] valueList = mConfiguration.getStringArray(aName);
        if ((valueList != null) && (valueList.length > 1))
            return true;
        else
            return false;
    }

    /**
     * Returns an array of values for a multi-value property.
     *
     * @param aName Name of the property.
     * @return Array of property values.
     */
    public String[] getStringArray(String aName) {
        return mConfiguration.getStringArray(aName);
    }

    /**
     * Collapses a multi-value array into a single string
     * with each value separated by the multi-value
     * delimiter character.
     *
     * @param aName Name of the property.
     *
     * @return Collapsed string representing one or more values.
     */
    public String getStringArrayAsSingleValue(String aName) {
        if (isPropertyMultiValue(aName)) {
            boolean isFirst = true;
            StringBuilder stringBuilder = new StringBuilder();
            String[] multiValues = getStringArray(aName);
            for (String pValue : multiValues) {
                if (isFirst) {
                    isFirst = false;
                    stringBuilder.append(pValue);
                } else {
                    stringBuilder.append(mConfiguration.getListDelimiter());
                    stringBuilder.append(pValue);
                }
            }

            return stringBuilder.toString();
        } else
            return getString(aName);
    }

    /**
     * Writes an application identity summary to the output stream
     * associated with the logger instance.
     *
     * @param aLogger Output logger instance.
     * @see <a href="http://logback.qos.ch/">Logback Project</a>
     */
    public void writeIdentity(Logger aLogger) {
        String appBuild = "1";
        String appVersion = "1.0";
        String appName = "Undefined Application";
        String appDate = "Mon Jan 06 00:00:00 EDT 2017";
        String appDescription = "The application properties file did not provide a description.";

        if (mConfiguration != null) {
            appName = mConfiguration.getString("app.name", appName);
            appDate = mConfiguration.getString("app.date", appDate);
            appBuild = mConfiguration.getString("app.build", appBuild);
            appVersion = mConfiguration.getString("app.version", appVersion);
            appDescription = mConfiguration.getString("app.description", appDescription);
        }

        String appDetails = String.format("%s - %s [build %s] - %s", appName, appVersion, appBuild, appDate);
        aLogger.info(appDetails);
        aLogger.info(appDescription);
    }

    /**
     * Returns a logger instance matching the identity parameter.
     *
     * @param anIdentity An identity is usually a fully qualified
     *                   package.class name.
     *
     * @return An output logger instance.
     *
     * @see <a href="http://logback.qos.ch/">Logback Project</a>
     */
    public Logger getLogger(String anIdentity) {
        return LoggerFactory.getLogger(anIdentity);
    }

    /**
     * Returns a logger instance matching the class parameter.  The
     * name associated with the class instance is used for the logger
     * identity.
     *
     * @param aClass Class instance to base identity on.
     *
     * @return An output logger instance.
     *
     * @see <a href="http://logback.qos.ch/">Logback Project</a>
     */
    public Logger getLogger(Class<?> aClass) {
        return LoggerFactory.getLogger(aClass.getClass().getName());
    }

    /**
     * Returns a logger instance matching the class parameter.  The
     * name associated with the class instance and the method name
     * are used for the logger identity.
     *
     * @param aClass Class instance to base identity on.
     * @param aMethod Method name to base identity on.
     *
     * @return An output logger instance.
     * @see <a href="http://logback.qos.ch/">Logback Project</a>
     */
    public Logger getLogger(Object aClass, String aMethod) {
        return LoggerFactory.getLogger(aClass.getClass().getName() + "." + aMethod);
    }

    /**
     * Writes the collection of property names and their values to
     * the output logger instance.
     *
     * @param aLogger An output logger instance.
     *
     * @see <a href="http://logback.qos.ch/">Logback Project</a>
     */
    public void writeCfgProperties(Logger aLogger) {
        if (mConfiguration != null) {
            String keyName, keyValue;
            Iterator<String> keyList = mConfiguration.getKeys();
            while (keyList.hasNext()) {
                keyName = keyList.next();
                keyValue = mConfiguration.getString(keyName);
                if (StringUtils.isEmpty(keyValue))
                    keyValue = "undefined";
                aLogger.debug(String.format("%s = %s", keyName, keyValue));
            }
        }
    }

    /**
     * Add an application defined property to the application manager.
     * <p>
     * <b>Notes:</b>
     * </p>
     * <ul>
     *     <li>The goal of the AppMgr is to strike a balance between
     *     providing enough properties to adequately model application
     *     related data without overloading it.</li>
     *     <li>This method offers a mechanism to capture additional
     *     (application specific) properties that may be needed.</li>
     *     <li>Properties added with this method are transient and
     *     will not be persisted when saved.</li>
     * </ul>
     *
     * @param aName Property name (duplicates are not supported).
     * @param anObject Instance of an object.
     */
    public void addProperty(String aName, Object anObject) {
        if ((StringUtils.isNotEmpty(aName)) && (anObject != null))
            mPropertyMap.put(aName, anObject);
    }

    /**
     * Removes the property from the internally managed map.
     *
     * @param aName Property name.
     */
    public void removeProperty(String aName) {
        if (StringUtils.isNotEmpty(aName))
            mPropertyMap.remove(aName);
    }

    /**
     * Returns the object associated with the property name or
     * <i>null</i> if the name could not be matched.
     *
     * @param aName Name of the property.
     * @return Instance of an object.
     */
    public Object getProperty(String aName) {
        return mPropertyMap.get(aName);
    }

    /**
     * Removes all application defined properties assigned to
     * this application manager.
     */
    public void clearProperties() {
        mPropertyMap.clear();
    }
}