/***********************************************************************
 *
 *  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>
 *
 ***********************************************************************/

package com.novell.casa.authtoksvc;

import org.w3c.dom.Node;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Document;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xml.serialize.XMLSerializer;
import org.apache.xml.serialize.OutputFormat;
import org.xml.sax.InputSource;

import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;

/**
 *
 * Class for editing the connector settings in the Tomcat server.xml file.
 *
 */
public final class TomcatConnectorEditor
{
   private static final String usage =
      "usage: TomcatConnectorEditor -file propertiesFilePath\n\n" +
      "   where:\n" +
      "      -file - Path the the properties file\n" +
      "   The following properties must be present in the file:\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";

   // Required properties
   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 = {
         KEYSTORE_PATH_PROPERTY,
         KEYSTORE_PASS_PROPERTY,
         KEYSTORE_ALG_PROPERTY};

   // Configured properties
   private static Properties  m_properties = null;


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

      for (int i = 0; i < m_requiredProperties.length; i++)
      {
         if (!m_properties.containsKey(m_requiredProperties[i]))
         {
            System.err.println("TomcatConnectorEditor.verifyRequiredPropertiesSpecified()- Required property " + m_requiredProperties[i] + " missing");
            rc = -1;
            break;
         }
      }

      return rc;
   }

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

      // 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())
         {
            System.err.println("TomcatConnectorEditor.processProperties()- Properties file " + configFilePath + " does not exist");
            rc = -1;
         }
         else
         {
            // Read the properties from the file
            in = new FileInputStream(fileProperties);
            m_properties = new Properties();
            m_properties.load(in);
         }
      }
      catch (Exception e)
      {
         System.err.println("TomcatConnectorEditor.processProperties()- Error reading properties");
         rc = -1;
      }
      finally
      {
         try
         {
            if (in != null)
               in.close();
         }
         catch (Exception e)
         {
            // Do nothing
         }
      }

      // Continue if no errors detected
      if (rc == 0)
      {
         // Validate that we have the required properties
         rc = verifyRequiredPropertiesSpecified();
      }

      return rc;
   }

   /**
    * Find first child node with matching node name.
    *
    * @param parentNode    Parent node.
    * @param nodeName      Name of node.
    * @return              Child node found or null.
    */
   static private Node findChildNode(Node parentNode, String nodeName)
   {
      Node retNode = null;
      Node childNode;
      Node nextNode = parentNode.getFirstChild();
      while ((childNode = nextNode) != null)
      {
         // Set next before we change anything
         nextNode = childNode.getNextSibling();

         // Handle child by node type
         if (childNode.getNodeType() == Node.ELEMENT_NODE)
         {
            // Check if this is the element node wanted
            if (childNode.getNodeName().equalsIgnoreCase(nodeName))
            {
               // Found the node
               retNode = childNode;
               break;
            }
         }
      }

      return retNode;
   }

   /**
    * Update the Setup all of the needed relocatable files.
    *
    * @return     Return code (0 == Success).
    */
   static private int updateTomcatServerXML()
   {
      int rc = 0;

      // We have all of the information necessary to update the connector in the server.xml
      // file, proceed.
      FileInputStream inStream = null;
      FileOutputStream outStream = null;
      try
      {
         File serverXmlFile = new File("/srv/www/casaats/conf/server-sun.xml"); // tbd - This can be enhanced so that we load this path from the properties file
         inStream = new FileInputStream(serverXmlFile);

         // Pull the server.xml file into a DOM
         DOMParser parser = new DOMParser();
         parser.parse(new InputSource(inStream));
         Document doc = parser.getDocument();

         Node docElement = doc.getDocumentElement();
         if (docElement != null)
         {
            Node serviceElement = findChildNode(docElement, "service");
            if (serviceElement != null)
            {
               Node connectorElement = findChildNode(serviceElement, "connector");
               if (connectorElement != null)
               {
                  NamedNodeMap connectorAttributesMap = connectorElement.getAttributes();
                  if (connectorAttributesMap != null)
                  {
                     int   attributeSetFailures = 0;

                     Node keystoreFileAttributeNode = connectorAttributesMap.getNamedItem("keystoreFile");
                     if (keystoreFileAttributeNode != null)
                     {
                        keystoreFileAttributeNode.setNodeValue((String) m_properties.get(KEYSTORE_PATH_PROPERTY));
                     }
                     else
                     {
                        System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- keystoreFile attribute not configured for connector");
                        attributeSetFailures ++;
                        rc = -1;
                     }

                     Node keystorePassAttributeNode = connectorAttributesMap.getNamedItem("keystorePass");
                     if (keystorePassAttributeNode != null)
                     {
                        keystorePassAttributeNode.setNodeValue((String) m_properties.get(KEYSTORE_PASS_PROPERTY));
                     }
                     else
                     {
                        System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- keystorePass attribute not configured for connector");
                        attributeSetFailures ++;
                        rc = -1;
                     }

                     Node algorithmAttributeNode = connectorAttributesMap.getNamedItem("algorithm");
                     if (algorithmAttributeNode != null)
                     {
                        algorithmAttributeNode.setNodeValue((String) m_properties.get(KEYSTORE_ALG_PROPERTY));
                     }
                     else
                     {
                        System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- algorithm attribute not configured for connector");
                        attributeSetFailures ++;
                        rc = -1;
                     }

                     if (attributeSetFailures == 0)
                     {
                        outStream = new FileOutputStream(serverXmlFile);
                        OutputFormat format = new OutputFormat(doc);
                        XMLSerializer serializer = new XMLSerializer(outStream, format);
                        serializer.serialize(doc.getDocumentElement());
                     }
                  }
                  else
                  {
                     System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- Failed to obtain connector attributes map");
                     rc = -1;
                  }
               }
               else
               {
                  System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- Failed to obtain connector node");
                  rc = -1;
               }
            }
            else
            {
               System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- Failed to obtain service node");
               rc = -1;
            }
         }
         else
         {
            System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- Failed to obtain document element");
            rc = -1;
         }
      }
      catch (Exception e)
      {
         System.err.println("TomcatConnectorEditor.updateTomcatServerXML()- Exception: " + e.getMessage());
         rc = -1;
      }
      finally
      {
         try
         {
            // Close input stream if necessary
            if (inStream != null)
               inStream.close();

            // Flush and close output stream if necessary
            if (outStream != null)
            {
               outStream.flush();
               outStream.close();
            }
         }
         catch (Exception e)
         {
            // Do nothing
         }
      }

      return rc;
   }

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

      // 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
         {
            argumentsError = true;
         }
      }

      // Proceed based on the specified parameters
      if (argumentsError == false)
      {
         if (filePath != null)
         {
            System.err.println("Dealing with properties file: " + filePath);

            // Process the properties
            if (processProperties(filePath) == 0)
            {
               // The properties were successfully processed, now update the
               // server.xml file.
               if (updateTomcatServerXML() == 0)
               {
                  opPerformed = true;
               }
            }
         }
         else
         {
            argumentsError = true;
         }
      }

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

      // Set the exit code appropriatedly
      if (opPerformed)
         System.exit(0);
      else
         System.exit(1);
   }
}
