# Settings.java

    /*
    ** Module   : Settings.java
    ** Abstract : Configuration information required for the ORM framework
    **
    ** Copyright (c) 2019-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description---------------------------------
    ** 001 ECF 20191001 Created initial version. ORM layer configuration information.
    ** 002 IAS 20200914 Re-work (de)serialization
    ** 003 AIL 20200918 Made Settings use the dynamically computed URL for per-session databases.
    ** 004 AIL 20201016 Increased searching interval for getAllForCategory.
    ** 005 IAS 20201219 Added 'CPSTREAM' import task argument
    ** 006 IAS 20220712 Added admin login/pwd values.
    ** 007 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 008 RAA 20240723 Added removeSettings method.
    ** 009 OM  20240909 Improved Database API.
    ** 010 SP  20250416 Used a CaseInsensitiveLinkedHashMap to store the config.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero 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 Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.persist.orm;

    import static com.goldencode.util.NativeTypeSerializer.*;
    import java.io.*;
    import java.util.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.util.*;

    /**
     * ORM settings specified by an application's directory configuration, or programmatically. Each
     * setting is associated with a string key. Keys may be qualified by their sub-category (e.g.,
     * {@code connection}). Qualifiers are delimited by a dot ({@code .}).
     */
    public final class Settings
    implements Externalizable
    {
       /** Database dialect key */
       public static final String DATABASE_NAME = "database_name";

       /** Database dialect key */
       public static final String DIALECT = "dialect";

       /** Connection URL key */
       public static final String URL = "connection.url";

       /** Connection user name key */
       public static final String USER = "connection.username";

       /** Connection password key */
       public static final String PASSWORD = "connection.password";

       /** Connection uadmin name key */
       public static final String ADMIN = "connection.admin_username";

       /** Connection password key */
       public static final String ADMIN_PASSWORD = "connection.admin_password";

       /** table dump file codepage, optional */
       public static final String CPSTREAM = "dump.cpstream";

       /** Connection driver class key */
       public static final String DRIVER = "connection.driver";

       /** JDBC fetch size key */
       public static final String FETCH_SIZE = "jdbc.fetch_size";

       /** JDBC batch size key */
       public static final String BATCH_SIZE = "jdbc.batch_size";

       /** Release connections key */
       public static final String RELEASE_CONNECTIONS = "releaseConnections";

       /** Map of all configured settings, by database identifier */
       private static final Map<Database, Settings> all = new HashMap<>();

       /**Logger */
       public static final CentralLogger LOG = CentralLogger.get(Settings.class);

       /** Configuration map for a particular database */
       private Map<String, Object> config = new CaseInsensitiveLinkedHashMap<>();

       /** Flag to indicate if the settings are made for a temporary database. */
       private boolean temporary = false;

       /**
        * Retrieve the settings for a particular database.
        *
        * @param   database
        *          Database identifier.
        *
        * @return  Settings for the specified database.
        *
        * @throws  PersistenceException
        *          if no settings are found for the given database identifier.
        */
       public static Settings get(Database database)
       throws PersistenceException
       {
          Settings settings = all.get(database);

          if (settings == null)
          {
             throw new PersistenceException("No settings found for database " + database);
          }

          return settings;
       }

       /**
        * Remove the settings that were stored for the given database.
        *
        * @param   db
        *          The database for which to remove the settings.
        */
       public static void removeSettings(Database db)
       {
          all.remove(db);
       }

       /**
        * Default constructor. Does not store this object and as such is meant for short-lived
        * instances.
        */
       public Settings()
       {
       }

       /**
        * Construct an instance of this class and store it by the given database for retrieval
        * later. The object is initially empty.
        *
        * @param   database
        *          Database with which these settings are associated.
        */
       public Settings(Database database)
       {
          // no synchronization; assume all invocations occur on same thread at server startup
          if (all.put(database, this) != null)
          {
             LOG.warning("Duplicate database Settings for " + database);
          }

          this.temporary = database.isTemporary();
       }

       /**
        * Construct an instance of this class and store it by the given database for retrieval
        * later. The object is initially empty.
        *
        * @param   database
        *          Database with which these settings are associated.
        * @param   props
        *          A set of properties that will be copied into the newly created @Settings@ object.
        */
       public Settings(Database database, Properties props)
       {
          // no synchronization; assume all invocations occur on same thread at server startup
          if (all.put(database, this) != null)
          {
             LOG.warning("Duplicate database Settings for " + database);
          }

          for (Map.Entry<Object, Object> entry : props.entrySet())
          {
             config.put(entry.getKey().toString(), entry.getValue());
          }
       }

       /**
        * Retrieve the setting with the given key, as a string.
        *
        * @param   key
        *          Setting key.
        * @param   defaultValue
        *          Default value if there is no setting with the given key. Set to {@code null} to
        *          indicate no default should be used.
        *
        * @return  The setting, if it exists. Otherwise, return the default value. If there is no
        *          setting with the given key and {@code defaultValue} is {@code null}, return
        *          {@code null}.
        *
        * @throws  ClassCastException
        *          if the setting exists, but is not a {@code String}.
        * @throws  NullPointerException
        *          if {@code key} is {@code null}.
        */
       public String getString(String key, String defaultValue)
       {
          // if the database is temporary, make sure the URL is retrieved using the TemporaryDatabaseManager
          // as the temporary databases are physically per-session (so the URL is dynamic).
          if (temporary && key.equals(Settings.URL))
          {
             Settings settings = TemporaryDatabaseManager.getMyTempDbSettings();
             String url = settings.getString(Settings.URL, null);
             return url != null ? url : defaultValue;
          }

          String value = (String) config.get(key);

          return value != null || defaultValue == null ? value : defaultValue;
       }

       /**
        * Retrieve the setting with the given key, as an integer.
        *
        * @param   key
        *          Setting key.
        * @param   defaultValue
        *          Default value if there is no setting with the given key. Set to {@code null} to
        *          indicate no default should be used.
        *
        * @return  The setting, if it exists. Otherwise, return the default value. If there is no
        *          setting with the given key and {@code defaultValue} is {@code null}, return
        *          {@code null}.
        *
        * @throws  ClassCastException
        *          if the setting exists, but is not an {@code Integer}.
        * @throws  NullPointerException
        *          if {@code key} is {@code null}.
        */
       public Integer getInteger(String key, Integer defaultValue)
       {
          Integer value = (Integer) config.get(key);

          return value != null || defaultValue == null ? value : defaultValue;
       }

       /**
        * Retrieve the setting with the given key, as a boolean.
        *
        * @param   key
        *          Setting key.
        * @param   defaultValue
        *          Default value if there is no setting with the given key. Set to {@code null} to
        *          indicate no default should be used.
        *
        * @return  The setting, if it exists. Otherwise, return the default value. If there is no
        *          setting with the given key and {@code defaultValue} is {@code null}, return
        *          {@code null}.
        *
        * @throws  ClassCastException
        *          if the setting exists, but is not a {@code Boolean}.
        * @throws  NullPointerException
        *          if {@code key} is {@code null}.
        */
       public Boolean getBoolean(String key, Boolean defaultValue)
       {
          Boolean value = (Boolean) config.get(key);

          return value != null || defaultValue == null ? value : defaultValue;
       }

       /**
        * Store a setting, overwriting any previous setting with the same key.
        *
        * @param   key
        *          Setting key.
        * @param   value
        *          Setting value.
        *
        * @return  {@code true} if the setting was newly stored; {@code false} if a setting already
        *          exists with the given key (the previous setting is still overwritten in this case).
        */
       public boolean put(String key, Object value)
       {
          return config.put(key, value) == null;
       }

       /**
        * Get a map of the sub-set of settings whose keys are prefixed by the given qualifier.
        *
        * @param   qualifier
        *          Qualifier for the keys of the settings desired. If the qualifier does not match
        *          any setting key, an empty map is returned.
        *
        * @return  A submap of the settings whose keys are prefixed by {@code qualifier}, if any.
        */
       public Map<String, Object> getAllForCategory(String qualifier)
       {
          Map<String, Object> result = new CaseInsensitiveLinkedHashMap<>();
          String fromKey = qualifier + '.';
          String toKey = qualifier + ".~";
          for (Map.Entry<String, Object> entry : config.entrySet())
          {
             String key = entry.getKey();
             boolean inRange = key.compareToIgnoreCase(fromKey) >= 0 && key.compareToIgnoreCase(toKey) <= 0;
             if (inRange)
             {
                result.put(key, entry.getValue());
             }
          }
          return result;
       }

       /**
        * Replacement for the default object writing method.
        *
        * @param    out
        *           The output destination to which fields will be saved.
        *
        * @throws   IOException
        *           In case of I/O errors.
        */
       @Override
       public void writeExternal(ObjectOutput out) throws IOException
       {
          writeMap(out, config, ObjectOutput::writeUTF, ObjectOutput::writeObject);
       }

       /**
        * Replacement for the default object reading method.
        *
        * @param    in
        *           Input source from which fields will be restored.
        *
        * @throws   IOException
        *           In case of I/O errors.
        * @throws   ClassNotFoundException
        *           If payload can't be instantiated.
        */
       @Override
       public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
       {
          config = readMap(in, CaseInsensitiveLinkedHashMap::new, ObjectInput::readUTF, ObjectInput::readObject);
       }
    }