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;
   }
}