LegacyLogManagerClientServiceImpl.java

/*
** Module   : LegacyLogManagerClientServiceImpl.java
** Abstract : LOG-MANAGER client service.
**
** Copyright (c) 2023-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 GBB 20230607 Initial setup.
** 002 GBB 20240311 Disable permanent file lock.
** 003 ICP 20240722 Modified shutdown hook registration.
*/
/*
** 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.util;

import com.goldencode.p2j.main.OrderedShutdownHooks;
import com.goldencode.p2j.util.logging.*;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;
import java.util.logging.*;

/** LOG-MANAGER client service. */
public class LegacyLogManagerClientServiceImpl
implements LegacyLogManagerClientService
{
   /** Logger. */
   private static final CentralLogger LOG = CentralLogger.get(LegacyLogManagerClientServiceImpl.class);

   /** The file path. */
   private String filePath;

   /** The output stream to write to the log file. */
   private FileOutputStream fileOutputStream;

   /** The file channel associated with the output stream. */
   private FileChannel channel;

   /** The lock to the file channel, when rotation is enabled. */
   private FileLock rotationLock;

   /**
    * Default public constructor for LegacyLogManagerClientServiceImpl. Adds a JVM shutdown hook to close  
    * the log file and release the lock.
    */
   public LegacyLogManagerClientServiceImpl()
   {
      OrderedShutdownHooks.registerShutdownHook(2, new Thread(this::close, getClass().getSimpleName()));
   }
   
   /**
    * Returns a flag to indicate if the file path is writable.
    *
    * @param     filePath
    *            The file path to check for.
    *
    * @return    <code>true</code> if the folder is writable, <code>false</code> otherwise.
    */
   @Override
   public boolean isWritable(String filePath)
   {
      return LegacyLogManagerImpl.isWritable(new File(filePath));
   }
   
   /**
    * Receives the clean file path from configs and returns the path modified with rotation number, next in 
    * the sequence based on the existing files in the folder.
    *
    * @param     logFileConfig
    *            The clean file path from configs.
    * @param     logThreshold
    *            The size of the file before rolling over.
    *
    * @return    The modified file path with the rotation number.
    */
   @Override
   public String makeSequencedLogFile(String logFileConfig, int logThreshold)
   {
      return LegacyLogManagerImpl.makeSequencedLogFile(logFileConfig, logThreshold).getPath();
   }
   
   /**
    * Opens the file for writing.
    *
    * @param     filePath
    *            The file path.
    *
    * @return    <code>true</code> if successful, <code>false</code> otherwise.
    */
   @Override
   public boolean open(String filePath)
   {
      if (filePath == null)
      {
         if (this.filePath != null)
         {
            close();
         }
         return true;
      }
      if (filePath.equals(this.filePath))
      {
         return true;
      }
      try
      {
         this.fileOutputStream = new FileOutputStream(filePath, true);
         this.channel = fileOutputStream.getChannel();
         this.filePath = filePath;
      }
      catch (IOException e)
      {
         LOG.log(Level.WARNING, e, "Couldn't open file %s.", filePath);
         return false;
      }
      return true;
   }
   
   /**
    * Writes the log record.
    *
    * @param     logRecord
    *            The log record.
    *
    * @return    <code>true</code> if successful, <code>false</code> otherwise.
    */
   @Override
   public boolean write(String logRecord)
   {
      if (fileOutputStream == null)
      {
         return false;
      }
      try
      {
         ByteBuffer byteBuffer = ByteBuffer.wrap(logRecord.getBytes());
         if (rotationLock != null)
         {
            channel.write(byteBuffer);
            return true;
         }
         FileLock lock = channel.lock();
         channel.write(byteBuffer);
         lock.release();
         return true;
      }
      catch (Exception e)
      {
         LOG.log(Level.WARNING, e, "Couldn't write log %s to file %s.", logRecord, filePath);
         return false;
      }
   }
   
   /**
    * Closes the file.
    *
    * @return    <code>true</code> if successful, <code>false</code> otherwise.
    */
   @Override
   public boolean close()
   {
      try
      {
         if (fileOutputStream != null)
         {
            fileOutputStream.flush();
            fileOutputStream.close();
         }
         if (rotationLock != null && rotationLock.isValid())
         {
            rotationLock.release();
         }
         filePath = null;
         channel = null;
         fileOutputStream = null;
      }
      catch (IOException e)
      {
         LOG.log(Level.WARNING, e, "Couldn't close log output stream for file %s.", filePath);
         return false;
      }
      return true;
   }
   
   /**
    * Clears the content of the currently open log file. If rotation is enabled, deletes all files in the 
    * sequence.
    *
    * @return    <code>true</code> if successful, <code>false</code> otherwise.
    */
   @Override
   public boolean clear()
   {
      try
      {
         if (rotationLock == null)
         {
            channel.truncate(0);
         }
         else
         {
            String lastFileName = filePath;
            close();
            return LegacyLogManagerImpl.deleteSequence(new File(lastFileName));
         }
      }
      catch (IOException e)
      {
         LOG.log(Level.WARNING, e, "Couldn't clear the file %s.", filePath);
         return false;
      }
      return true;
   }
   
   /**
    * Removes the sequenced log files with the same name in the same folder that exceed the max count of
    * numLogFiles, while preserving the current log file and of its predecessors.
    *
    * @param     filePath
    *            The log record.
    * @param     numLogFiles
    *            The log record.
    */
   @Override
   public void cleanupRotation(String filePath, int numLogFiles)
   {
      LegacyLogManagerImpl.cleanupRotation(new File(filePath), numLogFiles);
   }
   
   /**
    * Writes multiple log record. Used with state transfer.
    *
    * @param     logManagerRecords
    *            The log records.
    */
   public void write(List<String> logManagerRecords)
   {
      if (logManagerRecords == null || logManagerRecords.size() == 0)
      {
         return;
      }
      if (fileOutputStream == null)
      {
         LOG.log(Level.WARNING, "No file open. LOG-MANAGER logs discarded.");
         return;
      }
      FileLock lock = null;
      try
      {
         if (rotationLock == null)
         {
            lock = channel.lock();
         }
         for (String logRecord : logManagerRecords)
         {
            channel.write(ByteBuffer.wrap(logRecord.getBytes()));
         }
      }
      catch (Exception e)
      {
         LOG.log(Level.WARNING, e, "Couldn't write LOG-MANAGER logs to file %s.", filePath);
      }
      finally
      {
         if (lock != null && lock.isValid())
         {
            try
            {
               lock.release();
            }
            catch (IOException ignored)
            {
            }
         }
      }
   }
}