/***********************************************************************
 *
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 *
 *  To contact Novell about this file by physical or electronic mail,
 *  you may find current contact information at www.novell.com.
 *
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/

import java.util.Properties;
import java.util.Enumeration;
import java.io.*;

/**
 *
 * Class for configuring and unconfiguring an ATS under windows.
 *
 */
public final class AtsConfigurator
{
   private static final String usage =
      "usage: AtsConfigurator -op -file propertiesFilePath [-logfolder logFolderPath]\n\n" +
      "   where:\n" +
      "      -op - Corresponds to one of the following operations:\n" +
      "         -c - Configure the ATS installation.\n" +
      "         -u - Unconfigure the ATS installation.\n" +
      "      -file - Path to the properties file\n" +
      "      -logfolder - Path to the log folder\n" +
      "   The following properties must be present in the file:\n" +
      "     ATS_INSTALL_DIR - The location of the ATS Install Directory.\n" +
      "     COMPUTERNAME - The computer name.\n" +
      "     ATS_JAVA_HOME - Java home.\n" +
      "     TOMCAT_HOME - Tomcat install base directory.\n" +
      "     HOSTNAME - Name of the host.\n" +
      "     IAREALMS_FILE_PATH - Path to the iaRealms file to be used by the service.\n" +
      "   The following properties are optional:\n" +
      "     TOMCAT5 - Location of the tomcat5 executable.\n" +
      "     KEYSTORE_PATH - Location of keystore that should be used by connector.\n" +
      "     KEYSTORE_PASS - Password for keystore specified via KEYSTORE_PATH.\n" +
      "     KEYSTORE_ALG - Algorithm (SunX509 or IbmX509) for keystore specified via KEYSTORE_PATH.\n";

   // Error codes
   final static int ERROR_NO_ERROR                    = 0;
   final static int ERROR_EXEC_FAILED                 = -1;
   final static int ERROR_INVALID_ARGUMENT            = -2;
   final static int ERROR_MISSING_INSTALL_DIR         = -4;
   final static int ERROR_INSTALL_DIR_NOT_A_DIR       = -5;
   final static int ERROR_MISSING_PROPERTIES_FILE     = -7;
   final static int ERROR_UNABLE_TO_READ_PROPERTIES   = -8;
   final static int ERROR_REQUIRED_PROPERTY_MISSING   = -11;
   final static int ERROR_EXEC_INTERRUPTED            = -12;
   final static int ERROR_IO_EXCEPTION                = -13;
   final static int ERROR_EXCEPTION                   = -14;
   final static int ERROR_UNABLE_TO_OPEN_TEMPLATE     = -15;
   final static int ERROR_FILEWRITER_CREATE_FAILED    = -16;
   final static int ERROR_OUTPUT_COPY_FAILED          = -17;
   final static int ERROR_JAR_COPY_FAILED             = -18;
   final static int ERROR_ADD_FILE_TO_JAR_FAILED      = -19;
   final static int ERROR_WAR_TEMPLATE_FILE_MISSING   = -20;
   final static int ERROR_NEW_JAR_CANNOT_BE_REPLACED  = -21;
   final static int ERROR_CREATE_WAR_FOS_FAILED       = -22;
   final static int ERROR_OPEN_JAR_TEMPLATE_FAILED    = -23;
   final static int ERROR_JOS_FLUSH_FAILED            = -24;
   final static int ERROR_JOS_CLOSE_FAILED            = -25;
   final static int ERROR_FOS_CLOSE_FAILED            = -26;
   final static int ERROR_JFTEMPLATE_CLOSE_FAILED     = -27;
   final static int ERROR_MISSING_KEYSTORE_PROPERTY   = -28;
   final static int ERROR_INVALID_TOMCAT_SERVER_XML_FORMAT = -29;

   // Required properties
   final static String INSTALL_DIR_PROPERTY  = "ATS_INSTALL_DIR";
   final static String COMPUTERNAME_PROPERTY  = "COMPUTERNAME";
   final static String TOMCAT_HOME_PROPERTY  = "TOMCAT_HOME";
   final static String JAVA_HOME_PROPERTY  = "ATS_JAVA_HOME";
   final static String HOSTNAME_PROPERTY  = "HOSTNAME";
   final static String IAREALMS_FILE_PROPERTY  = "IAREALMS_FILE_PATH";

   // Optional properties
   final static String TOMCAT5_PROPERTY  = "TOMCAT5";
   final static String KEYSTORE_PATH_PROPERTY = "KEYSTORE_PATH";
   final static String KEYSTORE_PASS_PROPERTY = "KEYSTORE_PASS";
   final static String KEYSTORE_ALG_PROPERTY = "KEYSTORE_ALG";

   final static String[] m_requiredProperties = {
           INSTALL_DIR_PROPERTY,
           COMPUTERNAME_PROPERTY,
           TOMCAT_HOME_PROPERTY,
           JAVA_HOME_PROPERTY,
           HOSTNAME_PROPERTY,
           IAREALMS_FILE_PROPERTY};

   // Other constants
   final static String LOG_FILE_NAME = "AtsConfigurator.log";

   // Configured properties
   static Properties  m_properties = null;

   // InstallLog variables
   static String      m_logFolderPath = "\\temp";
   static File        m_logFile = null;
   static FileWriter  m_logFileWriter = null;

   /**
    * Invoke external command.
    *
    * @param commandArray  Array of command parameters.
    * @return              Return code.
    */
   static int invokeExternalCommand(String[] commandArray)
   {
      int rc;

      log("AtsConfigurator.invokeExternalCommand()- Start");

      try
      {
         Process p = Runtime.getRuntime().exec(commandArray);
         try
         {
            int r = p.waitFor();
            if (r == 0)
            {
               rc = ERROR_NO_ERROR;
            }
            else
            {
               log("Command " + commandArray[0] + " returned status: " + r);
               rc = ERROR_EXEC_FAILED;
            }
         }
         catch (InterruptedException ie)
         {
            rc = ERROR_EXEC_INTERRUPTED;
         }
      }
      catch (Exception e)
      {
         rc = AtsConfigurator.ERROR_EXEC_FAILED;
      }

      log("AtsConfigurator.invokeExternalCommand()- End, rc= ", rc);

      return rc;
   }

   /**
    * Try to fill in missing properties from system properties.
    */
   static void tryToFillInMissingProperties()
   {
      log("AtsConfigurator.tryToFillInMissingProperties()- Start");

      for (int i = 0; i < m_requiredProperties.length; i++)
      {
         String propertyKey = m_requiredProperties[i];
         if (!m_properties.containsKey(propertyKey))
         {
            AtsConfigurator.log("Trying to fill in property: " + propertyKey);

            String propertyValue = System.getProperty(propertyKey);
            if (propertyValue != null)
            {
               log("  Filling in property using system property value: " + propertyValue);
               m_properties.put(propertyKey, propertyValue);
            }
            else
            {
               log("  Unable to fill in property " + propertyKey);
            }
         }
      }

      log("AtsConfigurator.tryToFillInMissingProperties()- End");
   }

   /**
    * Verify that the properties required have been specified.
    *
    * @return     Return code.
    */
   static int verifyRequiredPropertiesSpecified()
   {
      int rc = ERROR_NO_ERROR;

      log("AtsConfigurator.verifyRequiredPropertiesSpecified()- Start");

      for (int i = 0; i < m_requiredProperties.length; i++)
      {
         if (!m_properties.containsKey(m_requiredProperties[i]))
         {
            log("Required property " + m_requiredProperties[i] + " missing");
            rc = ERROR_REQUIRED_PROPERTY_MISSING;
            break;
         }
      }

      log("AtsConfigurator.verifyRequiredPropertiesSpecified()- End, rc= ", rc);

      return rc;
   }

   /**
    * Process properties.
    *
    * @return     Return code.
    */
   static int processProperties(String configFilePath)
   {
      int rc = ERROR_NO_ERROR;

      log("AtsConfigurator.processProperties()- Start");

      // Obtain configuration properties from config file
      InputStream in = null;
      try
      {
         File fileProperties = new File(configFilePath);

         // Make sure the properties file can be found
         if (!fileProperties.exists())
         {
            rc = ERROR_MISSING_PROPERTIES_FILE;
         }
         else
         {
            // Read the properties from the file
            in = new FileInputStream(fileProperties);
            m_properties = new Properties();
            m_properties.load(in);
         }
      }
      catch (Exception e)
      {
         rc = ERROR_UNABLE_TO_READ_PROPERTIES;
      }
      finally
      {
         try
         {
            if (in != null)
               in.close();
         }
         catch (Exception e)
         {
            // Do nothing
         }
      }

      // Continue if no errors detected
      if (rc == ERROR_NO_ERROR)
      {
         tryToFillInMissingProperties();

         // Log the properties obtained
         Enumeration e = m_properties.propertyNames();
         while (e.hasMoreElements())
         {
            String key = (String) e.nextElement();
            String value = (String) m_properties.get(key);
            AtsConfigurator.log("Property " + key + " = " + value);
         }

         // Validate that we have the required properties
         if (ERROR_NO_ERROR == (rc = verifyRequiredPropertiesSpecified()))
         {
            // Verify that the install directory specified exists
            File fileInstallDir = new File((String) m_properties.get(INSTALL_DIR_PROPERTY));

            // Make sure the install dir can be found
            if (!fileInstallDir.exists())
            {
               rc = ERROR_MISSING_INSTALL_DIR;
            }
            else
            {
               // Make sure the install dir is a directory
               if (!fileInstallDir.isDirectory())
               {
                  rc = ERROR_INSTALL_DIR_NOT_A_DIR;
               }
            }
         }
      }

      log("AtsConfigurator.processProperties()- End, rc= ", rc);

      return rc;
   }

   /**
    * Log error code.
    *
    * @param err  Error code.
    */
   static void log(int err)
   {
      log(null, err);
   }

   /**
    * Log string with error code.
    *
    * @param s    Log string.
    * @param err  Error code.
    */
   static void log(String s, int err)
   {
      String rcMessage;

      switch (err)
      {
         case ERROR_NO_ERROR:
            rcMessage = "No error";
            break;
         case ERROR_EXEC_FAILED:
            rcMessage = "Execute command failed";
            break;
         case ERROR_MISSING_INSTALL_DIR:
            rcMessage = "Missing install dir";
            break;
         case ERROR_INSTALL_DIR_NOT_A_DIR:
            rcMessage = "Install dir is not a dir";
            break;
         case ERROR_MISSING_PROPERTIES_FILE:
            rcMessage = "Property file not found";
            break;
         case ERROR_UNABLE_TO_READ_PROPERTIES:
            rcMessage = "Unable to read property file";
            break;
         case ERROR_REQUIRED_PROPERTY_MISSING:
            rcMessage = "Required property is missing";
            break;
         case ERROR_EXEC_INTERRUPTED:
            rcMessage = "Execution interrupted";
            break;
         case ERROR_UNABLE_TO_OPEN_TEMPLATE:
            rcMessage = "Unable to open template file";
            break;
         case ERROR_FILEWRITER_CREATE_FAILED:
            rcMessage = "FileWriter create failed";
            break;
         case ERROR_OUTPUT_COPY_FAILED:
            rcMessage = "Output copy failed";
            break;
         case ERROR_IO_EXCEPTION:
            rcMessage = "IO Exception";
            break;
         case ERROR_JAR_COPY_FAILED:
            rcMessage = "Failed to copy Jar";
            break;
         case ERROR_ADD_FILE_TO_JAR_FAILED:
            rcMessage = "Failed to add file to Jar";
            break;
         case ERROR_WAR_TEMPLATE_FILE_MISSING:
            rcMessage = "Template War file missing";
            break;
         case ERROR_NEW_JAR_CANNOT_BE_REPLACED:
            rcMessage = "Cannot create new Jar";
            break;
         case ERROR_CREATE_WAR_FOS_FAILED:
            rcMessage = "Failed to create War File Output Stream";
            break;
         case ERROR_OPEN_JAR_TEMPLATE_FAILED:
            rcMessage = "Failed to open Template War file";
            break;
         case ERROR_JOS_FLUSH_FAILED:
            rcMessage = "Jar Output Stream Flush failed";
            break;
         case ERROR_JOS_CLOSE_FAILED:
            rcMessage = "Jar Output Stream Close failed";
            break;
         case ERROR_FOS_CLOSE_FAILED:
            rcMessage = "File Output Stream Close failed";
            break;
         case ERROR_JFTEMPLATE_CLOSE_FAILED:
            rcMessage = "War Template Input Stream Close failed";
            break;
         case ERROR_EXCEPTION:
            rcMessage = "Exception";
            break;
         case ERROR_MISSING_KEYSTORE_PROPERTY:
            rcMessage = "Missing Keystore related property";
            break;
         case ERROR_INVALID_TOMCAT_SERVER_XML_FORMAT:
            rcMessage = "Invalid tomcat server.xml format";
            break;
         default:
            rcMessage = "Unknown error";
            break;
      }

      if (s != null)
         log(s + rcMessage);
      else
         log(rcMessage);
   }

   /**
    * Log string.
    *
    * @param s    Log string.
    */
   static void log(String s)
   {
      try
      {
         if (m_logFileWriter != null)
            m_logFileWriter.write(s + "\r\n");
      }
      catch (IOException ioe)
      {
         // Do nothing
      }
   }

   /**
   *  Applications Entry Point.
   *
   * @param args  Arguments.
   */
   public static void main(String[] args)
   {
      String op = null;
      boolean opPerformed = false;
      boolean argumentsError = false;
      String filePath = null;
      int rc = ERROR_NO_ERROR;

      try
      {
         // Process the command line arguments
         for (int i = 0; i < args.length; i++)
         {
            // Proceed based on the command
            if (args[i].equalsIgnoreCase("-file"))
            {
               // The next argument should contain the filepath
               if (args.length > (i + 1))
               {
                  filePath = args[i + 1];
                  i++;
               }
               else
               {
                  argumentsError = true;
                  break;
               }
            }
            else if (args[i].equalsIgnoreCase("-logfolder"))
            {
               // The next argument should contain the path to the log folder
               if (args.length > (i + 1))
               {
                  // Overwrite the default
                  m_logFolderPath = args[i + 1];
                  i++;
               }
               else
               {
                  argumentsError = true;
                  break;
               }
            }
            else if (args[i].equalsIgnoreCase("-c"))
            {
               // List operation requested
               if (op == null)
               {
                  op = "configure";
               }
               else
               {
                  argumentsError = true;
                  break;
               }
            }
            else if (args[i].equalsIgnoreCase("-u"))
            {
               // List operation requested
               if (op == null)
               {
                  op = "unconfigure";
               }
               else
               {
                  argumentsError = true;
                  break;
               }
            }
         }

         // Get ready to log
         File logFolder = new File(m_logFolderPath);
         if (!logFolder.exists())
         {
            // Create the log folder
            logFolder.mkdirs();
            m_logFile = new File(logFolder.getAbsolutePath() + "\\" + LOG_FILE_NAME);
            m_logFileWriter = new FileWriter(m_logFile);
         }
         else
         {
            if (logFolder.isDirectory())
            {
               m_logFile = new File(logFolder.getAbsolutePath() + "\\" + LOG_FILE_NAME);
               m_logFileWriter = new FileWriter(m_logFile);
            }
         }

         // Proceed based on the specified parameters
         if (argumentsError == false)
         {
            // Verify that the necessary input parameters where specified
            if (filePath != null && op != null)
            {
               // Process the property file
               if (ERROR_NO_ERROR == (rc = processProperties(filePath)))
               {
                  // Proceed based on the operation requested
                  if (op.compareTo("configure") == 0)
                  {
                     Configure c = new Configure(m_properties);
                     rc = c.m_rc;
                  }
                  else if (op.compareTo("unconfigure") == 0)
                  {
                     Unconfigure u = new Unconfigure(m_properties);
                     rc = u.m_rc;
                  }
                  else
                  {
                     log("Unsupported operation: " + op);
                     argumentsError = true;
                  }
               }
               else
               {
                  argumentsError = true;
               }
            }
            else
            {
               argumentsError = true;
               log("Not all required parameters specified");
            }
         }
      }
      catch (Exception e)
      {
         rc = ERROR_EXCEPTION;
         log("Exception" + e.getMessage());
      }
      finally
      {
         // Close the log file writer if necessary
         if (m_logFileWriter != null)
         {
            try
            {
               m_logFileWriter.flush();
               m_logFileWriter.close();
            }
            catch (Exception e)
            {
               // Do nothing
            }
         }
      }

      // Display the usage string if we encountered an error with the
      // command line arguments.
      if (argumentsError)
      {
         System.out.print(usage);
         rc = ERROR_INVALID_ARGUMENT;
      }

      // Set the exit code
      System.exit(rc);
   }
}
