/***********************************************************************
 * 
 *  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 java.io.*;
import java.util.*;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import org.apache.log4j.Logger;

/**
 * SvcConfig Class.
 * <p>
 * This class obtains and maintains the service configuration.
 * 
 */
public final class SvcConfig
{
   private static final Logger m_log = Logger.getLogger(SvcConfig.class);

   // Well known service configuration settings
   //
   // The LifetimeShorter value is the value by which token lifetime
   // values are shorten when specified to clients to make sure that
   // the clients detect token expirations before issuing the tokens
   // to a service for authentication purposes.
   public final static String SessionTokenLifetime = "SessionTokenLifetime";
   public final static String LifetimeShorter = "LifetimeShorter";
   public final static String IdentityAbstractionConfigFile = "IAConfigFile";
   public final static String ConfigFolderPath = "ConfigFolderPath";
   public final static String AppRootPath = "AppRootPath";
   public final static String ReconfigureInterval = "ReconfigureInterval";
   public final static String SigningKeyAliasName = "SigningKeyAliasName";
   public final static String SigningKeyPassword = "SigningKeyPassword";

   // Default configuration values
   public final static String DefaultSessionTokenLifetimeValue = "43200";    // Seconds
   public final static String DefaultLifetimeShorterValue = "5";             // Seconds
   public final static String DefaultReconfigureIntervalValue = "60";        // Seconds
   public final static String DefaultSigningKeyAliasNameValue = "signingKey";
   public final static String DefaultSigningKeyPasswordValue = "secret";

   /**
    * RealmsInfo object for public use.
    */
   public RealmsInfo m_realmsInfo = null;

   // Minimum configuration values
   private final static int MinimumTokenLifetimeValue = 360;         // Seconds
   private final static int MinimumLifetimeShorterValue = 5;         // Seconds
   private final static int MinimumLifetimeShorterDifferential = 15;
   private final static int MinimumReconfigureIntervalValue = 30;    // Seconds

   private static final String m_svcSettingsFileName = "svc.settings";
   private final Map<String,String> m_svcSettingsMap;

   /**
    * Constructor.
    *
    * @param appRootPath   Path to application root.
    * @param svcConfigPath Path to the service configuration.
    * @throws Exception
    */
   public SvcConfig(String appRootPath, String svcConfigPath) throws Exception
   {
      m_log.debug("Constructor()-");
      m_log.debug("Constructor()- SvcConfigPath = " + svcConfigPath);

      // Create a map to keep track of the service settings
      m_svcSettingsMap = new HashMap<String, String>();

      FileInputStream inStream = null;
      try
      {
         // Get an input stream to services settings file
         File settingsFile = new File(svcConfigPath, m_svcSettingsFileName);
         inStream = new FileInputStream(settingsFile);

         // Parse the file
         XMLReader xr = XMLReaderFactory.createXMLReader();
         SettingsFileSAXHandler handler = new SettingsFileSAXHandler(m_svcSettingsMap);
         xr.setContentHandler(handler);
         xr.setErrorHandler(handler);

         InputSource source = new InputSource(inStream);
         xr.parse(source);

         // Add the application and config folder path settings to our map
         m_svcSettingsMap.put(AppRootPath, appRootPath);
         m_svcSettingsMap.put(ConfigFolderPath, svcConfigPath);

         // Do some sanity checking
         int tokenLifetime;
         try
         {
            tokenLifetime = Integer.valueOf(getSetting(SessionTokenLifetime)).intValue();
            if (tokenLifetime < MinimumTokenLifetimeValue)
            {
               m_log.debug("Constructor()- Configured token lifetime too small, defaulting to "
                           + Integer.toString(MinimumTokenLifetimeValue) + " seconds");
               tokenLifetime = MinimumTokenLifetimeValue;

               // Update the map with the new value for the setting
               m_svcSettingsMap.put(SessionTokenLifetime, Integer.toString(tokenLifetime));
            }
         }
         catch (NumberFormatException e)
         {
            m_log.debug("Constructor()- Invalid configured token lifetime value, defaulting to "
                        + Integer.toString(MinimumTokenLifetimeValue) + " seconds");
            tokenLifetime = MinimumTokenLifetimeValue;

            // Update the map with the new value for the setting
            m_svcSettingsMap.put(SessionTokenLifetime, Integer.toString(tokenLifetime));
         }

         int lifetimeShorter;
         try
         {
            lifetimeShorter = Integer.valueOf(getSetting(LifetimeShorter)).intValue();
            if (lifetimeShorter < MinimumLifetimeShorterValue)
            {
               m_log.debug("Constructor()- Configured lifetime shorter too small, defaulting to "
                           + Integer.toString(MinimumLifetimeShorterValue) + " seconds");
               lifetimeShorter = MinimumLifetimeShorterValue;

               // Update the map with the new value for the setting
               m_svcSettingsMap.put(LifetimeShorter, Integer.toString(lifetimeShorter));
            }
         }
         catch (NumberFormatException e)
         {
            m_log.debug("Constructor()- Invalid configured lifetime shorter value, defaulting to "
                        + Integer.toString(MinimumLifetimeShorterValue) + " seconds");
            lifetimeShorter = MinimumLifetimeShorterValue;

            // Update the map with the new value for the setting
            m_svcSettingsMap.put(LifetimeShorter, Integer.toString(lifetimeShorter));
         }

         if (lifetimeShorter > tokenLifetime
             || (tokenLifetime - lifetimeShorter) < MinimumLifetimeShorterDifferential)
         {
            m_log.debug("Constructor()- Invalid lifetime shorter value, defaulting to "
                        + Integer.toString(MinimumLifetimeShorterValue) + " seconds");

            // Update the map with the new value for the setting
            m_svcSettingsMap.put(LifetimeShorter, Integer.toString(MinimumLifetimeShorterValue));
         }

         int reconfigureInterval;
         try
         {
            reconfigureInterval = Integer.valueOf(getSetting(ReconfigureInterval)).intValue();
            if (reconfigureInterval != 0
                && reconfigureInterval < MinimumReconfigureIntervalValue)
            {
               m_log.debug("Constructor()- Configured reconfigure interval too small, defaulting to "
                           + Integer.toString(MinimumReconfigureIntervalValue) + " seconds");

               // Update the map with the new value for the setting
               m_svcSettingsMap.put(ReconfigureInterval, Integer.toString(MinimumReconfigureIntervalValue));
            }
         }
         catch (NumberFormatException e)
         {
            m_log.debug("Constructor()- Invalid configured reconfigured interval value, defaulting to "
                        + Integer.toString(MinimumReconfigureIntervalValue) + " seconds");

            // Update the map with the new value for the setting
            m_svcSettingsMap.put(ReconfigureInterval, Integer.toString(MinimumReconfigureIntervalValue));
         }

         // Lets create a RealmsInfo object for public use
         m_realmsInfo = new RealmsInfo(getSetting(IdentityAbstractionConfigFile));
      }
      catch (SAXException e)
      {
        m_log.warn("Constructor()- Parse exception: " + e.toString());
        throw new Exception("SvcConfig()- svc.settings format error", e);
      }
      catch (SecurityException e)
      {
         m_log.warn("Constructor()- SecurityException caught while accessing " + svcConfigPath +  File.separator + m_svcSettingsFileName + " Exception=" + e.toString());
         throw new Exception("SvcConfig()- Not able to access file", e);
      }
      catch (FileNotFoundException e)
      {
         m_log.warn("Constructor()- File " + svcConfigPath +  File.separator + m_svcSettingsFileName + " not found");
         throw new Exception("SvcConfig()- File not found", e);
      }
      catch (IOException e)
      {
         m_log.warn("Constructor()- IOException caught while trying to read " + svcConfigPath +  File.separator + m_svcSettingsFileName + " Exception=" + e.toString());
         throw new Exception("SvcConfig()- Read error", e);
      }
      finally
      {
         if (inStream != null)
         {
            try
            {
               inStream.close();
            }
            catch (IOException e)
            {
               // Do nothing
            }
         }
      }
   }

   /**
    * Returns the value associated with the specified setting.
    *
    * @param settingName   Name of the setting.
    * @return              Value of the specified setting.
    * @throws Exception
    */
   public final String getSetting(String settingName) throws Exception
   {
      // Try to find the setting in our map
      String value = m_svcSettingsMap.get(settingName);
      if (value == null)
      {
         m_log.debug("getSetting()- Did not find setting " + settingName);

         // The setting is not in our map, check if it is one to
         // which we have defaults.
         if (settingName.equalsIgnoreCase(SessionTokenLifetime))
         {
            value = DefaultSessionTokenLifetimeValue;
            m_log.debug("getSetting()- Assigning default value " + value);

            // Add the key to the map so that it can be found quicker next time
            m_svcSettingsMap.put(SessionTokenLifetime, DefaultSessionTokenLifetimeValue);
         }
         else if (settingName.equalsIgnoreCase(LifetimeShorter))
         {
            value = DefaultLifetimeShorterValue;
            m_log.debug("getSetting()- Assigning default value " + value);

            // Add the key to the map so that it can be found quicker next time
            m_svcSettingsMap.put(LifetimeShorter, DefaultLifetimeShorterValue);
         }
         else if (settingName.equalsIgnoreCase(ReconfigureInterval))
         {
            value = DefaultReconfigureIntervalValue;
            m_log.debug("getSetting()- Assigning default value " + value);

            // Add the key to the map so that it can be found quicker next time
            m_svcSettingsMap.put(ReconfigureInterval, DefaultReconfigureIntervalValue);
         }
         else if (settingName.equalsIgnoreCase(SigningKeyAliasName))
         {
            value = DefaultSigningKeyAliasNameValue;
            m_log.debug("getSetting()- Assigning default value " + value);

            // Add the key to the map so that it can be found quicker next time
            m_svcSettingsMap.put(SigningKeyAliasName, DefaultSigningKeyAliasNameValue);
         }
         else if (settingName.equalsIgnoreCase(SigningKeyPassword))
         {
            value = DefaultSigningKeyPasswordValue;
            m_log.debug("getSetting()- Assigning default value " + value);

            // Add the key to the map so that it can be found quicker next time
            m_svcSettingsMap.put(SigningKeyPassword, DefaultSigningKeyPasswordValue);
         }
         else if (settingName.equalsIgnoreCase(IdentityAbstractionConfigFile))
         {
            m_log.debug("getSetting()- Mandatory setting " + IdentityAbstractionConfigFile + " not set");
            throw new Exception("Missing mandatory configuration setting");
         }
      }
      else
      {
         m_log.debug("getSetting()- Found setting " + settingName);
         m_log.debug("getSetting()- Setting value = " + value);
      }

      return value;
   }
}