// Package 
package gov.va.med.fw.util;

// Java Classes
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedHashMap;

// Library Classes
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;



/**
 * Creates a Weblogic silent install domain creation script.
 *
 * @author Andrew Pach
 */
public class PrepareDomainCreationScript
{
    // Member variables
    private Map propertyInfoMap;
    private String unixTemplate;
    private String windowsTemplate;
    private String unixOutputFilename;
    private String windowsOutputFilename;
    private String environment;

    // Filename constants
    public static final String TEMPLATE_EXT = "_Template";
    public static final String UNIX_SCRIPT_EXT = ".sh";
    public static final String WINDOWS_SCRIPT_EXT = ".cmd";

    // Template constants
    public static final String WINDOWS_OUTPUT_FIRST_PREFIX = ">  %SOUT% ECHO";
    public static final String WINDOWS_OUTPUT_PREFIX = ">> %SOUT% ECHO";
    public static final String TAG_DELIMITER = "@";
    public static final String BASE_ITEM_KEY = "0";
    public static final String ENABLED = "Enabled";
    public static final String NAME = "Name";
    public static final String LOOKUP = "Lookup";
    public static final String CREATE = "create";
    public static final String FIND = "find";
    public static final String ASSIGN = "Assign";

    // Template keys
    public static final String KEY_SERVER = "Server";
    public static final String KEY_ADMIN_SERVER = "AdminServer";
    public static final String KEY_CLUSTER = "Cluster";
    public static final String KEY_MACHINE = "Machine";
    public static final String KEY_FILE_REALM = "FileRealm";
    public static final String KEY_REALM = "Realm";
    public static final String KEY_PASSWORD_POLICY = "PasswordPolicy";
    public static final String KEY_SECURITY_CONFIGURATION = "SecurityConfiguration";
    public static final String KEY_SECURITY = "Security";
    public static final String KEY_EMBEDDED_LDAP = "EmbeddedLDAP";
    public static final String KEY_JMS_CONNECTION_FACTORY = "JMSConnectionFactory";
    public static final String KEY_JMS_DISTRIBUTED_QUEUE = "JMSDistributedQueue";
    public static final String KEY_JMS_SERVER = "JMSServer";
    public static final String KEY_JMS_TEMPLATE = "JMSTemplate";
    public static final String KEY_JDBC_CONNECTION_POOL = "JDBCConnectionPool";
    public static final String KEY_JTA = "JTA";
    public static final String KEY_JDBC_TX_DATA_SOURCE = "JDBCTxDataSource";
    public static final String KEY_JMS_JDBC_STORE = "JMSJDBCStore";

    /**
     * The domain creation script tag used in the template to specify where the script should go.
     */
    public static final String DOMAIN_CREATION_SCRIPT_TAG = TAG_DELIMITER + "domainCreationScript" + TAG_DELIMITER;

    /**
     * The escape character used when specifying file redirection.
     */
    public static final String REDIRECT_ESCAPE = TAG_DELIMITER + "redirectEscape" + TAG_DELIMITER;

    // Script creation constants
    public static final String TEMPLATE_HOME = "TEMPLATE_HOME";
    public static final String TEMPLATE_FILENAME = "TEMPLATE_FILENAME";
    public static final String BEA_HOME = "BEA_HOME";
    public static final String FILE_SEPARATOR = "FILE_SEPARATOR";
    public static final String WINDOWS_FILE_SEPARATOR = "\\";
    public static final String UNIX_FILE_SEPARATOR = "/";
    public static final String CR = SystemUtils.LINE_SEPARATOR;
    public static final String HEADER_COMMENT =
        "/////////////////////////////////////////////////////////////////////////////////////////" + CR +
        "// This is the ESR Weblogic domain silent install script." + CR +
            "// This script was autogenerated by the ESR PrepareDomainCreationScript tool" + CR +
            "// and should not be modified." + CR +
            "//" + CR +
            "// Usage:" + CR +
            "//      @redirectEscape@<Windows@redirectEscape@> = config.cmd -mode=silent -silent_script=@redirectEscape@<silent script@redirectEscape@>" + CR +
            "//      @redirectEscape@<Unix@redirectEscape@>    = ./config.sh -mode=silent -silent_script=@redirectEscape@<silent script@redirectEscape@>" + CR +
            "//" + CR +
            "// Where:" + CR +
            "//      @redirectEscape@<silent script@redirectEscape@> should be the full path to the silent script." + CR +
            "/////////////////////////////////////////////////////////////////////////////////////////";

    public static final String ENVIRONMENT_COMMENT = "// This script will create a domain for the following environment: ";

    public static final String READ_IN_DOMAIN_TEMPLATE =
        "/////////////////////////////////////////////////////////////////////////////////////////" + CR +
            "// Read in a domain template." + CR +
            "/////////////////////////////////////////////////////////////////////////////////////////" + CR +
            "read template from \"@TEMPLATE_HOME@@FILE_SEPARATOR@@TEMPLATE_FILENAME@\";";

    public static final String WRITE_OUT_DOMAIN =
        "/////////////////////////////////////////////////////////////////////////////////////////" + CR +
            "// Write out the domain." + CR +
            "/////////////////////////////////////////////////////////////////////////////////////////" + CR +
            "set OverwriteDomain \"true\";" + CR +
            "write domain to \"@BEA_HOME@@FILE_SEPARATOR@user_projects@FILE_SEPARATOR@domains@FILE_SEPARATOR@EdbDevDomain2\";";

    public static final String CLOSE_TEMPLATE =
        "/////////////////////////////////////////////////////////////////////////////////////////" + CR +
            "// Close domain template to indicate completion of work." + CR +
            "/////////////////////////////////////////////////////////////////////////////////////////" + CR +
            "close template;";

    /**
     * Constructs the domain creation script object.
     *
     * @param env The environment
     * @param setupDir The setup directory
     * @param propsFilenameSuffix The properties filename suffix
     * @param scriptFilenamePrefix The script filename prefix
     */
    public PrepareDomainCreationScript(String env, String setupDir, String propsFilenameSuffix,
        String scriptFilenamePrefix) throws Exception
    {
        // Perform initialization
        init(env, setupDir, propsFilenameSuffix, scriptFilenamePrefix);
    }

    /**
     * Performs initialization.
     *
     * @param env The environment
     * @param setupDir The setup directory
     * @param propsFilenameSuffix The properties filename suffix
     * @param scriptFilenamePrefix The script filename prefix
     *
     * @throws Exception
     */
    protected void init(String env, String setupDir, String propsFilenameSuffix,
        String scriptFilenamePrefix) throws Exception
    {
        // Ensure parameters are present
        Validate.notNull(env, "Environment must be specified.");
        Validate.notNull(setupDir, "Setup directory must be specified.");
        Validate.notNull(propsFilenameSuffix, "Properties filename suffix must be specified.");
        Validate.notNull(scriptFilenamePrefix, "Script filename prefix must be specified.");

        // Assign script output filenames (e.g. setup/installESR.sh)
        setUnixOutputFilename(setupDir + File.separator + scriptFilenamePrefix +
            UNIX_SCRIPT_EXT);
        setWindowsOutputFilename(setupDir + File.separator + scriptFilenamePrefix +
            WINDOWS_SCRIPT_EXT);

        // Store the environment
        setEnvironment(env);

        // Load the assign the properties
        String propsFilename = setupDir + File.separator + env + "." + propsFilenameSuffix;
        LinkedHashMap propertyMap = loadProperties(propsFilename);
        setPropertyInfoMap(processProperties(propertyMap));

        // Load and assign the unix and windows templates from files (e.g. setup/installESR_Template.sh)
        String unixTemplateFilename = setupDir + File.separator + scriptFilenamePrefix +
            TEMPLATE_EXT + UNIX_SCRIPT_EXT;
        String windowsTemplateFilename = setupDir + File.separator + scriptFilenamePrefix +
            TEMPLATE_EXT + WINDOWS_SCRIPT_EXT;
        setUnixTemplate(FileUtils.readFileToString(new File(unixTemplateFilename), null));
        setWindowsTemplate(FileUtils.readFileToString(new File(windowsTemplateFilename), null));
    }

    /**
     * Loads the properties while maintaining their order.
     * @param propsFilename The property filename
     * @return A linked hash map of the properties
     */
    protected LinkedHashMap loadProperties(String propsFilename) throws IOException
    {
        // Initialize the map
        LinkedHashMap map = new LinkedHashMap();
        FileReader fr = null;
        try {	        	
	        fr = new FileReader(propsFilename);
	        // Read all lines of the file
	        BufferedReader reader = new BufferedReader(fr);
	        String line = reader.readLine();
	        while (line != null)
	        {
	            // Trim the current line
	            line = line.trim();
	
	            // Process this line if it has data and isn't a comment
	            if ((StringUtils.isNotBlank(line)) && (line.charAt(0) != '#'))
	            {
	                // Ensure we have a valid property line
	                int index = line.indexOf('=');
	                if ((index < 1) || (index >= line.length() - 1))
	                {
	                    throw new RuntimeException("Invalid property line found: " + line);
	                }
	
	                // Place the property in a map
	                map.put(line.substring(0, index), line.substring(index+1, line.length()));
	            }
	
	            // Read the next line
	            line = reader.readLine();
	        }
        } finally {
        	if (fr != null) {
        		IOUtils.closeQuietly(fr);
        	}
        }
        // Return the loaded properties
        return map;
    }

    /**
     * Entry point to process the properties specified in the properties file.
     *
     * @param inputPropertyMap the input properties stored in an ordered map
     *
     * @return The map of configuration element keys to the list of PropertyInfo configuration
     * objects for that key.
     */
    protected Map processProperties(LinkedHashMap inputPropertyMap)
    {
        // Create the return property info map
        LinkedHashMap propInfoMap = new LinkedHashMap();

        LinkedHashMap keyMap = new LinkedHashMap();

        // Iterate through the property names and determine the unique keys (e.g. Cluster, Server, etc.)
        // and store the list of properties that begin with each key in a map.
        for (Iterator iterator = inputPropertyMap.keySet().iterator(); iterator.hasNext();)
        {
            // Get a property key
            String propertyName = (String)iterator.next();
            String propertyKey = propertyName;
            String propertyValue = (String)inputPropertyMap.get(propertyName);

            // All properties should have a "." that separates the property key from the property attribute
            // (e.g. Cluster.Name ==> Cluster Property Key & Name Attribute)
            int index = propertyName.indexOf(".");
            if ((index > 0) && (index < propertyName.length()-1))
            {
                propertyKey = propertyName.substring(0, index);
            }
            else
            {
                throw new RuntimeException("Illegal property found: '" + propertyName +
                    "'.  Properties must be in the form of A.B, A.B.C, A.#.B or A.#.B.C.");
            }

            LinkedHashMap subMap = (LinkedHashMap)keyMap.get(propertyKey);
            if (subMap == null)
            {
                subMap = new LinkedHashMap();
                keyMap.put(propertyKey, subMap);
            }

            // Add this property to the list associated with the property key
            subMap.put(propertyName, propertyValue);
        }

        // Process each key
        for (Iterator iterator = keyMap.keySet().iterator(); iterator.hasNext();)
        {
            String key = (String)iterator.next();
            propInfoMap.put(key, getPropertyInfoList(key, (Map)keyMap.get(key)));
        }

        // Return the property info map
        return propInfoMap;
    }

    /**
     * Gets a list of PropertyInfo objects for the specified key given the propertyMap
     * for that key.
     *
     * @param componentKey The component element key to get the PropertyInfo list for.
     * @param propertyMap The map of properties for the specified component key.
     *
     * @return The list of PropertyMap configuration objects.
     */
    protected List getPropertyInfoList(String componentKey, Map propertyMap)
    {
        LinkedHashMap indexMap = new LinkedHashMap();

        // Flag to keep track whether we have multiple components or not
        boolean multiComponent = false;

        // Iterate through all the properties that start with the property key
        for (Iterator iterator = propertyMap.keySet().iterator(); iterator.hasNext();)
        {
            // Get one property
            String propertyKey = (String)iterator.next();
            String propertyValue = (String)propertyMap.get(propertyKey);

            // Split the property into tokens split by the "." character
            String[] tokens = propertyKey.split("\\.");

            // Parse the tokens
            int index = 1;

            // Determine the item index (0 is for single components or base configuration elements whereas
            // a number greater than 0 is for multiple components).
            String itemIndex =  BASE_ITEM_KEY;
            if (NumberUtils.isDigits(tokens[index]))
            {
                multiComponent = true;
                itemIndex = tokens[index];
                index++;

                // Ensure our base index isn't being used for multi-components.
                if (itemIndex.equals(BASE_ITEM_KEY))
                {
                    throw new RuntimeException("0 is not a valid property index.  Valid values must start at 1.");
                }
            }

            // Assume there are no children (i.e. the next token is the attribute)
            String childKey = null;
            String attribute = tokens[index];
            index++;

            // If we have an additional token, then the attribute is really the child and the next token
            // is the attribute
            if (index < tokens.length)
            {
                childKey = attribute;
                attribute = tokens[index];
            }

            // Get the property info for this index
            PropertyInfo info = (PropertyInfo)indexMap.get(itemIndex);
            if (info == null)
            {
                info = new PropertyInfo();
                indexMap.put(itemIndex, info);
            }

            if (childKey == null)
            {
                // We just have a direct attribute so store it
                Map attrMap = info.getAttributeMap();
                attrMap.put(attribute, propertyValue);
            }
            else
            {
                // We have a child key so get/add a child propertyInfo object
                Map childMap = info.getChildMap();
                PropertyInfo childInfo = (PropertyInfo)childMap.get(childKey);
                if (childInfo == null)
                {
                    childInfo = new PropertyInfo();
                    childMap.put(childKey, childInfo);
                }

                // Process the child attribute if it's not the "Enabled" one
                // (i.e. "Enabled" isn't an actual attribute so skip it since all we had to do
                // was create the child key for the component which we already did above).
                if (!attribute.equals(ENABLED))
                {
                    // Store the child attribute
                    Map childAttrMap = childInfo.getAttributeMap();
                    childAttrMap.put(attribute, propertyValue);
                }
            }
        }

        List infoList = new ArrayList();
        if (multiComponent)
        {
            // For a multi-component, we need add any "base" attributes onto each of the non-base components.

            // Get the base information
            PropertyInfo baseInfo = (PropertyInfo)indexMap.get(BASE_ITEM_KEY);

            // Only overlay base data if any exists (e.g. some Server.attributes exist)
            if (baseInfo != null)
            {
                // Iterate through the base attributes
                Map baseAttributeMap = baseInfo.getAttributeMap();
                for (Iterator iterator = baseAttributeMap.keySet().iterator(); iterator.hasNext();)
                {
                    // Get a base attribute
                    String baseAttribute = (String)iterator.next();
                    String baseValue = (String)baseAttributeMap.get(baseAttribute);

                    // Iterate through each component
                    for (Iterator indexIter = indexMap.keySet().iterator(); indexIter.hasNext();)
                    {
                        // Get the index and only process non-base indices
                        String index = (String)indexIter.next();
                        if (!index.equals(BASE_ITEM_KEY))
                        {
                            // Get the matching attribute for the component
                            PropertyInfo indexInfo = (PropertyInfo)indexMap.get(index);
                            Map attributeMap = indexInfo.getAttributeMap();
                            String indexAttribute = (String)attributeMap.get(baseAttribute);

                            // If the component doesn't have the attribute defined, add it.
                            // Otherwise, leave the component's version which takes precedence.
                            if (indexAttribute == null)
                            {
                                attributeMap.put(baseAttribute, baseValue);
                            }
                        }
                    }
                }

                // Get the base child map
                Map baseChildMap = baseInfo.getChildMap();

                // Iterate through each base child
                for (Iterator iterator = baseChildMap.keySet().iterator(); iterator.hasNext();)
                {
                    // Get the property info for the base child (e.g. Log)
                    String baseChildKey = (String)iterator.next();
                    PropertyInfo baseChildInfo = (PropertyInfo)baseChildMap.get(baseChildKey);

                    // Iterate through each component
                    for (Iterator indexIter = indexMap.keySet().iterator(); indexIter.hasNext();)
                    {
                        // Get the index and only process non-base indices
                        String index = (String)indexIter.next();
                        if (!index.equals(BASE_ITEM_KEY))
                        {
                            // Get the property info (e.g. Log) of the child for the component
                            PropertyInfo indexInfo = (PropertyInfo)indexMap.get(index);
                            Map childMap = indexInfo.getChildMap();
                            PropertyInfo indexChildInfo = (PropertyInfo)childMap.get(baseChildKey);

                            // If the component doesn't already have the child, create a new one
                            if (indexChildInfo == null)
                            {
                                indexChildInfo = new PropertyInfo();
                                childMap.put(baseChildKey, indexChildInfo);
                            }

                            // Iterate through the base child attributes
                            Map baseChildAttributeMap = baseChildInfo.getAttributeMap();
                            for (Iterator baseChildIterator = baseChildAttributeMap.keySet().iterator();
                                baseChildIterator.hasNext();)
                            {
                                // Get a base child attribute
                                String baseChildAttribute = (String)baseChildIterator.next();
                                String baseChildValue = (String)baseChildAttributeMap.get(baseChildAttribute);

                                // Process the child attribute if it's not the "Enabled" one
                                // (i.e. "Enabled" isn't an actual attribute so skip it since all we had to do
                                // was create the child key for the component which we already did above).
                                if (!baseChildAttribute.equals(ENABLED))
                                {
                                    // Get the matching attribute for the child component
                                    Map indexChildAttributeMap = indexChildInfo.getAttributeMap();
                                    String indexChildAttribute = (String)indexChildAttributeMap.get(baseChildAttribute);

                                    // If the component doesn't have the child attribute defined, add it.
                                    // Otherwise, leave the component's version which takes precedence.
                                    if (indexChildAttribute == null)
                                    {
                                        indexChildAttributeMap.put(baseChildAttribute, baseChildValue);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            // Iterate through each non-base component and add it to the return list
            for (Iterator indexIter = indexMap.keySet().iterator(); indexIter.hasNext();)
            {
                // Get the index and only process non-base indices
                String index = (String)indexIter.next();
                if (!index.equals(BASE_ITEM_KEY))
                {
                    // Add the component property info to the return list
                    infoList.add(indexMap.get(index));
                }
            }
        }
        else
        {
            // Only add the base item for a single component
            infoList.add(indexMap.get(BASE_ITEM_KEY));
        }

        // Return the info list
        return infoList;
    }

    /**
     * Creates the script.
     */
    public void create() throws Exception
    {
        // Get the domain creation script
        String domainCreationScript = getDomainCreationScript();

        // Create the unix and windows scripts
        String unixScript = replaceAll(getUnixTemplate(), DOMAIN_CREATION_SCRIPT_TAG,
            getUnixDomainCreationScript(domainCreationScript));
        String windowsScript = replaceAll(getWindowsTemplate(), DOMAIN_CREATION_SCRIPT_TAG,
            getWindowsDomainCreationScript(domainCreationScript));

        // Write out the domain creation scripts
        FileUtils.writeStringToFile(new File(getUnixOutputFilename()), unixScript, null);
        FileUtils.writeStringToFile(new File(getWindowsOutputFilename()), windowsScript, null);
    }

    /**
     * Gets the domain creation script formatted as a Windows script.
     *
     * @param domainCreationScript The generic domain creation script
     *
     * @return The windows formatted domain creation script
     */
    protected String getWindowsDomainCreationScript(String domainCreationScript)
    {
        // Update the script variables for windows
        domainCreationScript = updateScriptVariables(domainCreationScript, true);

        // Create an output buffer
        StringBuffer buffer = new StringBuffer();

        // Iterate through each line in the script
        String[] lines = domainCreationScript.split(CR);
        for (int i = 0; i < lines.length; i++)
        {
            // Get the line
            String line = lines[i];

            // Prepend the prefix to each line
            if (i == 0)
            {
                buffer.append(WINDOWS_OUTPUT_FIRST_PREFIX);
            }
            else
            {
                buffer.append(CR);
                buffer.append(WINDOWS_OUTPUT_PREFIX);
            }

            // Add a "." to empty lines or just a space for non-empty lines
            if (StringUtils.isEmpty(line))
            {
                buffer.append(".");
            }
            else
            {
                buffer.append(" ");
            }

            // Append the line
            buffer.append(line);
        }

        // Return the final script
        return buffer.toString();
    }

    /**
     * Gets the domain creation script formatted as a Unix script.
     *
     * @param domainCreationScript The generic domain creation script
     *
     * @return The unix formatted domain creation script
     */
    protected String getUnixDomainCreationScript(String domainCreationScript)
    {
        // Update the script variables for windows
        domainCreationScript = updateScriptVariables(domainCreationScript, false);

        // No further formatting is necessary
        return domainCreationScript;
    }

    /**
     * Updates the script variables for a specific environment.
     *
     * @param script The script with variable tags present
     * @param windows True for a Windows version or false for a Unix version
     *
     * @return The updated script with variables substituted
     */
    protected String updateScriptVariables(String script, boolean windows)
    {
        // Setup environment specific prefixes and suffixes
        String prefix = "${";
        String suffix = "}";
        String redirectEscape = "";
        String fileSeparator = UNIX_FILE_SEPARATOR;
        if (windows)
        {
            prefix = "%";
            suffix = "%";
            fileSeparator = WINDOWS_FILE_SEPARATOR;
            redirectEscape = "^";
        }

        // Make the variable substitutions
        script = replaceAll(script, TAG_DELIMITER + TEMPLATE_HOME + TAG_DELIMITER, prefix + TEMPLATE_HOME + suffix);
        script = replaceAll(script, TAG_DELIMITER + TEMPLATE_FILENAME + TAG_DELIMITER, prefix + TEMPLATE_FILENAME + suffix);
        script = replaceAll(script, TAG_DELIMITER + BEA_HOME + TAG_DELIMITER, prefix + BEA_HOME + suffix);
        script = replaceAll(script, TAG_DELIMITER + FILE_SEPARATOR + TAG_DELIMITER, fileSeparator);
        script = replaceAll(script, REDIRECT_ESCAPE, redirectEscape);

        // Return the environment specific script with variables updated.
        return script;
    }

    /**
     * Gets the core domain creation script.
     *
     * @return the domain creation script.
     */
    protected String getDomainCreationScript()
    {
        // Build the script
        StringBuffer buffer = new StringBuffer();
        buffer.append(HEADER_COMMENT);
        buffer.append(CR).append(CR);
        buffer.append(ENVIRONMENT_COMMENT).append(getEnvironment());
        buffer.append(CR).append(CR);
        buffer.append(READ_IN_DOMAIN_TEMPLATE);

        // Process each property one at a time
        Map infoMap = getPropertyInfoMap();

        buffer.append(getScriptForKey(KEY_SERVER, (List)infoMap.get(KEY_ADMIN_SERVER), false));
        buffer.append(getScriptForKey(KEY_CLUSTER, (List)infoMap.get(KEY_CLUSTER), true));
        buffer.append(getScriptForKey(KEY_SERVER, (List)infoMap.get(KEY_SERVER), true));
        buffer.append(getScriptForKey(KEY_MACHINE, (List)infoMap.get(KEY_MACHINE), true));
        buffer.append(getScriptForKey(KEY_FILE_REALM, (List)infoMap.get(KEY_FILE_REALM), true));
        buffer.append(getScriptForKey(KEY_REALM, (List)infoMap.get(KEY_REALM), true));
        buffer.append(getScriptForKey(KEY_PASSWORD_POLICY, (List)infoMap.get(KEY_PASSWORD_POLICY), true));
        buffer.append(getScriptForKey(KEY_SECURITY_CONFIGURATION, (List)infoMap.get(KEY_SECURITY_CONFIGURATION), true));
        buffer.append(getScriptForKey(KEY_SECURITY, (List)infoMap.get(KEY_SECURITY), true));
        buffer.append(getScriptForKey(KEY_EMBEDDED_LDAP, (List)infoMap.get(KEY_EMBEDDED_LDAP), true));
        buffer.append(getScriptForKey(KEY_JMS_CONNECTION_FACTORY, (List)infoMap.get(KEY_JMS_CONNECTION_FACTORY), true));
        buffer.append(getScriptForKey(KEY_JMS_DISTRIBUTED_QUEUE, (List)infoMap.get(KEY_JMS_DISTRIBUTED_QUEUE), true));
        buffer.append(getScriptForKey(KEY_JMS_SERVER, (List)infoMap.get(KEY_JMS_SERVER), true));
        buffer.append(getScriptForKey(KEY_JMS_TEMPLATE, (List)infoMap.get(KEY_JMS_TEMPLATE), true));
        buffer.append(getScriptForKey(KEY_JDBC_CONNECTION_POOL, (List)infoMap.get(KEY_JDBC_CONNECTION_POOL), true));
        buffer.append(getScriptForKey(KEY_JTA, (List)infoMap.get(KEY_JTA), true));
        buffer.append(getScriptForKey(KEY_JDBC_TX_DATA_SOURCE, (List)infoMap.get(KEY_JDBC_TX_DATA_SOURCE), true));
        buffer.append(getScriptForKey(KEY_JMS_JDBC_STORE, (List)infoMap.get(KEY_JMS_JDBC_STORE), true));

        buffer.append(CR).append(CR);
        buffer.append(WRITE_OUT_DOMAIN);
        buffer.append(CR).append(CR);
        buffer.append(CLOSE_TEMPLATE);

        // Return the script
        return buffer.toString();
    }

    /**
     * Creates and returns the configuration script for an individual key
     * @param key The key for the script piece to create
     * @param propertyInfoList The list of property info object for the script to create
     * @param createElement If true, the configuration element will be created.  If false, it will be found
     * @return The configuration script for the specified key
     */
    protected String getScriptForKey(String key, List propertyInfoList, boolean createElement)
    {
        // Create a buffer to hold the script
        StringBuffer buffer = new StringBuffer();

        if ((propertyInfoList != null) && (propertyInfoList.size() > 0))
        {
            // Determine the type of operation (i.e. create or find)
            String creationOperation = CREATE;
            if (!createElement)
            {
                creationOperation = FIND;
            }

            // Iterate through all the components
            for (int i=0; i < propertyInfoList.size(); i++)
            {
                // Get the attribute map
                PropertyInfo propertyInfo = (PropertyInfo)propertyInfoList.get(i);
                Map attributeMap = propertyInfo.getAttributeMap();

                // Get the required element name
                String elementName = (String)attributeMap.get(NAME);
                if (elementName == null)
                {
                    throw new RuntimeException("Key '" + key + "' does not have a required " + NAME + " attribute.");
                }

                // Get the lookup name.  If it is not present, use the name.
                String lookupName = (String)attributeMap.get(LOOKUP);
                if (lookupName == null)
                {
                    // If no lookup name was present, default to the name
                    lookupName = elementName;
                }

                // Define the lookup variable
                String lookupVar = key + "_" + lookupName;

                // Add the create/find statement
                buffer.append(CR).append(CR).append("// Configure ").append(key).append(" ").append(elementName);
                buffer.append(CR).append(creationOperation).append(" ").append(key).append(" \"").append(lookupName).append("\" as ").append(lookupVar).append(";");

                // Iterate through the attributes
                for (Iterator attrIterator = attributeMap.keySet().iterator(); attrIterator.hasNext();)
                {
                    String attributeKey = (String)attrIterator.next();
                    String attributeValue = (String)attributeMap.get(attributeKey);
                    if (((!attributeKey.equals(NAME)) || (!createElement)) && (!attributeKey.equals(ASSIGN)) &&
                        (!attributeKey.equals(LOOKUP)))
                    {
                        // Add the set statement
                        buffer.append(CR).append("set ").append(lookupVar).append(".").append(attributeKey).append(" \"").append(attributeValue).append("\";");
                    }
                }

                // Iterate through the children
                Map childMap = propertyInfo.getChildMap();
                for (Iterator childIter = childMap.keySet().iterator(); childIter.hasNext();)
                {
                    // Get the child property info
                    String childKey = (String)childIter.next();
                    PropertyInfo childInfo = (PropertyInfo)childMap.get(childKey);

                    // Assign the child element name
                    String childElementName = lookupVar + "_" + childKey;

                    // Add the create statement for the child
                    buffer.append(CR).append(CREATE).append(" ").append(key).append(".").append(childKey).append(" \"").append(elementName).append(".").append(elementName).append("\" as ").append(childElementName).append(";");

                    // Get the child attribute map
                    Map childAttributeMap = childInfo.getAttributeMap();

                    // Iterate through the child attributes
                    for (Iterator childAttrIterator = childAttributeMap.keySet().iterator(); childAttrIterator.hasNext();)
                    {
                        String childAttributeKey = (String)childAttrIterator.next();
                        String childAttributeValue = (String)childAttributeMap.get(childAttributeKey);

                        // Add the set statement
                        buffer.append(CR).append("set ").append(childElementName).append(".").append(childAttributeKey).append(" \"").append(childAttributeValue).append("\";");
                    }
                }

                // See if there is an assignment that needs to take place.
                String assignmentKey = (String)attributeMap.get(ASSIGN);
                if (StringUtils.isNotBlank(assignmentKey))
                {
                    // Get the main property info map
                    Map infoMap = getPropertyInfoMap();

                    // Get the list of elements for the key
                    List targetElementList = (List)infoMap.get(assignmentKey);

                    // Ensure we only have 1 element
                    if ((targetElementList == null) || (targetElementList.size() > 1))
                    {
                        throw new RuntimeException("The \"" + key + "\" configuration assignment " +
                        "refers to a target key of \"" + assignmentKey + "\" which either doesn't exist " +
                        "or contains more than 1 index.");
                    }

                    // Get the name of the target.
                    PropertyInfo targetPropertyInfo = (PropertyInfo)targetElementList.get(0);
                    Map targetAttributeMap = targetPropertyInfo.getAttributeMap();
                    String assignmentName = (String)targetAttributeMap.get(NAME);

                    // Ensure we have a target name
                    if (StringUtils.isBlank(assignmentName))
                    {
                        throw new RuntimeException("The \"" + key + "\" configuration assignment " +
                        "refers to a target key of \"" + assignmentKey + "\" that does not have a \"" +
                        NAME + "\" property defined.");
                    }

                    // Add an assign statement
                    buffer.append(CR).append("assign ").append(key).append(" \"").append(lookupName).append("\" to ").append(assignmentKey).append(" \"").append(assignmentName).append("\";");
                }
            }
        }

        // Return the script
        return buffer.toString();
    }

    /**
     * A convenient replaceAll method that does not take into consideration regular expressions.
     *
     * @param buffer the string that has characters that need to be replaced.
     * @param oldValue The old value that needs replacing.
     * @param newValue The new value that will replace the old values
     * .
     * @return The updated string with all values replaced.
     */
    protected String replaceAll(String buffer, String oldValue, String newValue)
    {
        // If the buffer or the old value are empty or the new value is null, just return it since there's nothing to replace
        if ((StringUtils.isEmpty(buffer)) || (StringUtils.isEmpty(oldValue)) || (newValue == null))
        {
            return buffer;
        }

        for (int index = 0; index > -1;)
        {
            // See if we have a match and if so, replace the old value with the new value
            index = buffer.indexOf(oldValue);
            if (index > -1)
            {
                // Update the buffer with the old value replaced with the new value
                buffer = (index == 0 ? "" : buffer.substring(0, index)) +
                    newValue + (index + oldValue.length() >= buffer.length() ? "" :
                    buffer.substring(index + oldValue.length(), buffer.length()));
            }
        }

        // Return the updated buffer
        return buffer;
    }

    public Map getPropertyInfoMap()
    {
        return propertyInfoMap;
    }

    public void setPropertyInfoMap(Map propertyInfoMap)
    {
        this.propertyInfoMap = propertyInfoMap;
    }

    public String getUnixTemplate()
    {
        return unixTemplate;
    }

    public void setUnixTemplate(String unixTemplate)
    {
        this.unixTemplate = unixTemplate;
    }

    public String getWindowsTemplate()
    {
        return windowsTemplate;
    }

    public void setWindowsTemplate(String windowsTemplate)
    {
        this.windowsTemplate = windowsTemplate;
    }

    public String getUnixOutputFilename()
    {
        return unixOutputFilename;
    }

    public void setUnixOutputFilename(String unixOutputFilename)
    {
        this.unixOutputFilename = unixOutputFilename;
    }

    public String getWindowsOutputFilename()
    {
        return windowsOutputFilename;
    }

    public void setWindowsOutputFilename(String windowsOutputFilename)
    {
        this.windowsOutputFilename = windowsOutputFilename;
    }

    public String getEnvironment()
    {
        return environment;
    }

    public void setEnvironment(String environment)
    {
        this.environment = environment;
    }

    /**
     * Main method that prepares the domain creation script.
     *
     * @param args The program arguments.  See program usage for details.
     */
    public static void main(String args[])
    {
        // Check number of parameters
        if (args.length < 4)
        {
            System.out.println("Usage: PrepareDomainCreationScript" +
                " <environment> <setup_directory> " +
                "<properties_filename_suffix> <script_filename_prefix>" + CR +
                "       (e.g. PrepareDomainCreationScript local setup domain.properties " +
                "installESR");
            System.exit(0);
        }

        // Parse arguments
        String env = args[0];
        String setupDir = args[1];
        String propFilenameSuffix = args[2];
        String scriptFilenamePrefix = args[3];

        // Create the script
        try
        {
            PrepareDomainCreationScript script = new PrepareDomainCreationScript(
                env, setupDir, propFilenameSuffix, scriptFilenamePrefix);
            script.create();
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
}

/**
 * Named inner class that defines property information for a specific configuration element Key
 * (e.g. for each Server).  This class contains two properties: an attribute map that specifies
 * the list of property attributes for the key and a child map that maps a child key to another
 * PropertyInfo object for all the child property attributes.  Note that a child PropertyMap
 * will not contain any additional children.
 */
class PropertyInfo
{
    LinkedHashMap attributeMap = new LinkedHashMap();
    LinkedHashMap childMap = new LinkedHashMap();

    // Default constructor
    public PropertyInfo()
    {
    }

    public LinkedHashMap getAttributeMap()
    {
        return attributeMap;
    }

    public void setAttributeMap(LinkedHashMap attributeMap)
    {
        this.attributeMap = attributeMap;
    }

    public LinkedHashMap getChildMap()
    {
        return childMap;
    }

    public void setChildMap(LinkedHashMap childMap)
    {
        this.childMap = childMap;
    }
}