# P2JQueryStatisticsConfiguration.java

    /*
    ** Module   : P2JQueryStatisticsConfiguration.java
    ** Abstract : Configure P2JQueryLogger settings
    **
    ** Copyright (c) 2023-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 RAA 20230802 First revision with basic usage.
    ** 002 ICP 20240419 Added support for logging in a h2 database.
    **     ICP 20240813 Instead of using a FileHandler, the CentralLoggerFile is used now
    **                  to log the P2J queries.
    */

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

    import static com.goldencode.p2j.util.logging.LoggingUtil.resolveLogFile;

    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Supplier;
    import java.util.logging.*;

    import com.goldencode.p2j.directory.DirectoryService;
    import com.goldencode.p2j.main.ClientCore;
    import com.goldencode.p2j.util.Utils;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Class that stores the P2J query logging configurations per-database.
     */
    public class P2JQueryStatisticsConfiguration
    {
       /** Map that includes a configuration for each given database. */
       private static final Map<String, P2JQueryStatisticsConfiguration> configs = new HashMap<>();

       /** Logger. */
       private static final CentralLogger LOG = CentralLogger.get(P2JQueryStatisticsConfiguration.class);

       /** The log formatter. */
       private static final Formatter LOG_FORMATTER = new CentralLogFormatter();

       /** Global directory path, used if a local one does not exist. */
       private static final String GLOBAL_DIR_PATH = "persistence/p2j-query-logging";

       /** The type of logging intended. */
       private final LoggingType loggingType;

       /** The file logger, responsible for writing logs to files. */
       private static CentralLoggerFile fileLogger;

       /**
        * Flag that determines whether the log output should contain the stack trace or not.
        * The stack trace will contain the name of the file (and line number) from outside the P2J package.
        */
       private final boolean STACK_TRACING;

       /**
        * Flag that determines whether the log output should contain the location of the query or not.
        * It will contain the name of the file (and line number) from inside the P2J package.
        */
       private final boolean LOCATION;

       /** Display the results every "x" processed components. By default, the result will be shown
        *  every time a component is processed.
        */
       private final int PROCESSED_COMPONENTS;

       /** Only queries that are slower than this value will be logged. */
       private final double MIN_TIME_THRESHOLD;

       /** The file that will receive the logging output. */
       private final String OUTPUT_FILE;

       /** The file name for the h2 database that will receive the logging output . */
       private final String OUTPUT_DB;

       /** The user for the h2 database that will receive the logging output . */
       private final String USER_DB;

       /** The password for the h2 database that will receive the logging output . */
       private final String PASS_DB;

       /** The helper object to facilitate logging to H2 database*/
       private P2JLoggingDatabaseHelper dbHelper;

       /**
        * Constructor that sets the logging configuration per-database.
        *
        * @param   databaseName
        *          The name of the database in use.
        */
       private P2JQueryStatisticsConfiguration(String databaseName)
       {
          boolean enabled = false;
          boolean stackTracing = false;
          boolean location = false;
          boolean hasLocalConfiguration = false;
          boolean outputDbEnabled = false;
          boolean profilingEnabled = false;
          int processedComponents = 1;
          double minTime = 0;
          String outputFile = null;
          String defaultOutputFile = "p2j-query-logging_%db_" +
                                     LoggingUtil.SORTABLE_DATETIME_FORMAT +
                                     "_%uos_%pid_%as.log";
          String outputDb = null;
          String userDb = null;
          String passDb = null;
          String defaultOutputDb = "p2j_logging_%db_" +
                                   LoggingUtil.SORTABLE_DATETIME_FORMAT +
                                   "_%uos_%pid_%as";
          String defaultUserDb = "";
          String defaultPassDb = "";
          String directoryPath = null;
          String localDirectoryPath = "database/" + databaseName + "/p2j-query-logging";
          DirectoryService ds = DirectoryService.getInstance();

          if (ds != null)
          {
             try
             {
                if (!ds.bind())
                {
                   LOG.warning("P2J query logging is not enabled");
                }

                String isLocallyEnabled = Utils.getDirectoryNodeString(ds,
                                                                       localDirectoryPath + "/enabled",
                                                                       "NULL",
                                                                       false);
                switch (isLocallyEnabled.toUpperCase())
                {
                   case "NULL":
                      String globalDirectoryPath = GLOBAL_DIR_PATH + "/enabled";
                      enabled = Boolean.parseBoolean(Utils.getDirectoryNodeString(ds,
                                                                                  globalDirectoryPath,
                                                                                  "FALSE",
                                                                                  false));
                      break;
                   case "TRUE":
                      enabled = true;
                      hasLocalConfiguration = true;
                      break;
                   case "FALSE":
                      enabled = false;
                      hasLocalConfiguration = true;
                      break;
                   default:
                      throw new RuntimeException("Invalid value for attribute: enabled");
                }

                if (enabled)
                {
                   directoryPath = hasLocalConfiguration ? localDirectoryPath : GLOBAL_DIR_PATH;
                   profilingEnabled = Utils.getDirectoryNodeBoolean(ds,
                                                                    directoryPath + "/profiling",
                                                                    false,
                                                                    false);
                   if (profilingEnabled)
                   {
                      minTime = Utils.getDirectoryNodeDouble(ds,
                                                             directoryPath + "/min-time-threshold",
                                                             0,
                                                             false);
                   }
                   outputDbEnabled = Utils.getDirectoryNodeBoolean(ds,
                            directoryPath + "/output-to-db/enabled",
                            false,
                            false);
                   if (outputDbEnabled)
                   {
                   outputDb = Utils.getDirectoryNodeString(ds,
                                      directoryPath + "/output-to-db/db-name",
                                      defaultOutputDb,
                                      false);
                   userDb = Utils.getDirectoryNodeString(ds,
                                    directoryPath + "/output-to-db/db-user",
                                    defaultUserDb,
                                    false);
                   passDb = Utils.getDirectoryNodeString(ds,
                                    directoryPath + "/output-to-db/db-pass",
                                    defaultPassDb,
                                    false);
                   }
                   stackTracing = Utils.getDirectoryNodeBoolean(ds,
                                                                directoryPath + "/stack-tracing",
                                                                false,
                                                                false);
                   location = Utils.getDirectoryNodeBoolean(ds,
                                                            directoryPath + "/location",
                                                            false,
                                                            false);
                   processedComponents = Utils.getDirectoryNodeInt(ds,
                                                                   directoryPath + "/processed-components",
                                                                   1,
                                                                   false);
                   outputFile = Utils.getDirectoryNodeString(ds,
                                                             directoryPath + "/output-to-file",
                                                             defaultOutputFile,
                                                             false);
                }
             }
             finally
             {
                ds.unbind();
             }
          }

          MIN_TIME_THRESHOLD = minTime;
          STACK_TRACING = stackTracing;
          LOCATION = location;
          OUTPUT_FILE = outputFile;
          OUTPUT_DB = outputDb;
          USER_DB = userDb;
          PASS_DB = passDb;
          PROCESSED_COMPONENTS = processedComponents;

          if (!enabled)
          {
             loggingType = LoggingType.NONE;
             return;
          }

          loggingType = profilingEnabled ? LoggingType.PROFILING : LoggingType.LOGGING;

          Supplier<Long> processIdSupplier = () -> {
             try
             {
                System.loadLibrary("p2j");
                return ClientCore.getPid();
             }
             catch (Throwable t)
             {
                LOG.log(Level.FINE, "", t);
                return 0L;
             }
          };

          String formattedFilePath = resolveLogFile(OUTPUT_FILE,
                                                    processIdSupplier,
                                                    "server",
                                                    databaseName,
                                                    true);
          fileLogger = CentralLoggerFile.initialize("p2j_" + databaseName + "_logger", formattedFilePath, 0, 0);

          if (outputDbEnabled)
          {
             String formattedDBPath = resolveLogFile(OUTPUT_DB,
                                                     processIdSupplier,
                                                     "server",
                                                     databaseName,
                                                     true);
             dbHelper = P2JLoggingDatabaseHelper.getDbHelper(databaseName, formattedDBPath, USER_DB, PASS_DB);
          }
       }

       /**
        * Get the logging configuration of a specific database.
        *
        * @param   databaseName
        *          The name of the database in use.
        *
        * @return  P2JQueryStatisticsConfiguration
        *          If it exists, an already created configuration for that database, or a new one otherwise.
        */
       public static P2JQueryStatisticsConfiguration getConfiguration(String databaseName)
       {
          P2JQueryStatisticsConfiguration config = configs.get(databaseName);
          if (config != null)
          {
             return config;
          }

          config = new P2JQueryStatisticsConfiguration(databaseName);
          configs.put(databaseName, config);
          return config;
       }

       /**
        * Getter for the {@code loggingType} field.
        *
        * @return  LoggingType
        *          The type of logger.
        */
       public LoggingType getLoggingType()
       {
          return loggingType;
       }

       /**
        * Getter for the {@code STACK_TRACING} field.
        *
        * @return  boolean
        *          The value of {@code STACK_TRACING}.
        */
       public boolean isStackTracingEnabled()
       {
          return STACK_TRACING;
       }

       /**
        * Getter for the {@code LOCATION} field.
        *
        * @return  boolean
        *          The value of {@code LOCATION}.
        */
       public boolean isLocationEnabled()
       {
          return LOCATION;
       }

       /**
        * Getter for the {@code PROCESSED_COMPONENTS} field.
        *
        * @return  int
        *          The value of {@code PROCESSED_COMPONENTS}.
        */
       public int getProcessedComponents()
       {
          return PROCESSED_COMPONENTS;
       }

       /**
        * Getter for the {@code MIN_TIME_THRESHOLD} field.
        *
        * @return  double
        *          The value of {@code MIN_TIME_THRESHOLD}.
        */
       public double getMinTimeThreshold()
       {
          return MIN_TIME_THRESHOLD;
       }

       /**
        * Getter for the {@code dbHelper} field.
        *
        * @return  The value of {@code dbHelper}.
        */
       public P2JLoggingDatabaseHelper getDbHelper()
       {
          return dbHelper;
       }

       /**
        * Enum denoting what should happen with a statement: should it just be executed or the intention is to
        * log things about it?
        */
       public enum LoggingType
       {
          /** Statement should be executed without logging anything. */
          NONE,

          /** Logging is enabled. */
          LOGGING,

          /** More than just logging, the statement should also be profiled. */
          PROFILING;
       }

       /**
        * Getter for the {@code fileLogger} field.
        *
        * @return  The value of {@code fileLogger}.
        */
       public CentralLoggerFile getFileLogger()
       {
          return fileLogger;
       }
    }