content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.carbondata.common.logging.impl;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.InterruptedIOException;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.Iterator;
  import java.util.Locale;
  import java.util.Map.Entry;
  import java.util.TreeMap;
  
  import org.apache.log4j.Level;
  import org.apache.log4j.RollingFileAppender;
  import org.apache.log4j.helpers.CountingQuietWriter;
  import org.apache.log4j.helpers.LogLog;
  import org.apache.log4j.spi.LoggingEvent;
  
  /**
   * Copied from log4j to remove the hard coding for the file name from it Copied
   * form log4j and modified for renaming files
   */
  public class ExtendedRollingFileAppender extends RollingFileAppender {
  
    private static final String DATE_FORMAT_FOR_TRANSFER = "yyyy-MM-dd'_'HH-mm-ss";
    protected int currentLevel = Level.FATAL_INT;
    /**
     * Now in log file rolling(after file size
     * exceeded the threshold) and deletion (after file count exceeded the file
     * count threshold) it will print log message
     */
  
    private long nextRollover = 0;
    private volatile boolean cleanupInProgress = false;
  
    /**
     * Total number of files at any point of time should be Backup number of
     * files + current file
     */
    private static void cleanLogs(final String startName, final String folderPath,
        int maxBackupIndex) {
      final String fileStartName = startName.toLowerCase(Locale.US);
      // Delete the oldest file, to keep Windows happy.
      File file = new File(folderPath);
  
      if (file.exists()) {
        File[] files = file.listFiles(f ->
            !f.isDirectory() && f.getName().toLowerCase(Locale.US).startsWith(fileStartName));
  
        if (null == files) {
          return;
        }
  
        int backupFiles = files.length - 1;
  
        if (backupFiles <= maxBackupIndex) {
          return;
        }
  
        // Sort the file based on its name.
        TreeMap<String, File> sortedMap = new TreeMap<>();
        for (File file1 : files) {
          sortedMap.put(file1.getName(), file1);
        }
  
        // Remove the first log file from map. it will be <startName>.log
        // itself which will be backed up in rollover
        sortedMap.remove(sortedMap.firstKey());
  
        Iterator<Entry<String, File>> it = sortedMap.entrySet().iterator();
        Entry<String, File> temp;
  
        // After clean up the files should be maxBackupIndex -1 number of
        // files. Because one more backup file
        // will be created after this method call is over
        while (it.hasNext() && backupFiles > maxBackupIndex) {
          temp = it.next();
          File deleteFile = temp.getValue();
          // Delete the file
          // After deletion of log file it
          // will print the log message in ReportService.log
          if (deleteFile.delete()) {
            backupFiles--;
          } else {
            LogLog.error("Couldn't delete file :: " + deleteFile.getPath());
          }
        }
      }
    }
  
    /**
     * Copied from log4j to remove hardcoding of file name
     */
    public void rollOver() {
      File target;
      File file = new File(fileName);
  
      String fileStartName = file.getName();
      int dotIndex = fileStartName.indexOf('.');
  
      if (dotIndex != -1) {
        fileStartName = fileStartName.substring(0, dotIndex);
      }
      final String startName = fileStartName;
      final String folderPath = file.getParent();
  
      if (qw != null) {
        long size = ((CountingQuietWriter) qw).getCount();
        LogLog.debug("rolling over count=" + size);
        // if operation fails, do not roll again until
        // maxFileSize more bytes are written
        nextRollover = size + maxFileSize;
      }
  
      LogLog.debug("maxBackupIndex=" + maxBackupIndex);
  
      boolean renameSucceeded = true;
  
      // If maxBackups <= 0, then there is no file renaming to be done.
      if (maxBackupIndex > 0) {
        DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_FOR_TRANSFER);
  
        StringBuilder buffer = new StringBuilder();
        String extension = "";
        if (fileName.contains(".")) {
          extension = fileName.substring(fileName.lastIndexOf("."));
          buffer.append(fileName, 0, fileName.lastIndexOf("."));
        } else {
          buffer.append(fileName);
        }
        buffer.append("_").append(dateFormat.format(new Date())).append(extension);
        // Rename fileName to fileName.1
        target = new File(buffer.toString());
  
        this.closeFile(); // keep windows happy.
  
        LogLog.debug("Renaming file " + file + " to " + target);
        renameSucceeded = file.renameTo(target);
  
        //
        // if file rename failed, reopen file with append = true
        //
        if (!renameSucceeded) {
          try {
            this.setFile(fileName, true, bufferedIO, bufferSize);
          } catch (InterruptedIOException e) {
            Thread.currentThread().interrupt();
          } catch (IOException e) {
            LogLog.error("setFile(" + fileName + ", true) call failed.", e);
          }
        }
      }
  
      //
      // if all renames were successful, then
      //
      if (renameSucceeded) {
        try {
          // This will also close the file. This is OK since multiple
          // close operations are safe.
          this.setFile(fileName, false, bufferedIO, bufferSize);
          nextRollover = 0;
        } catch (InterruptedIOException e) {
          Thread.currentThread().interrupt();
        } catch (IOException e) {
          LogLog.error("setFile(" + fileName + ", false) call failed.", e);
        }
      }
  
      // Do clean up finally
      if (!cleanupInProgress) {
        cleanUpLogs(startName, folderPath);
      }
    }
  
    private void cleanUpLogs(final String startName, final String folderPath) {
      if (maxBackupIndex > 0) {
        // Clean the logs files
        Runnable r = new Runnable() {
  
          public void run() {
            synchronized (ExtendedRollingFileAppender.class) {
              cleanupInProgress = true;
              try {
                cleanLogs(startName, folderPath, maxBackupIndex);
              } catch (Throwable e) {
                // ignore any error
                LogLog.error("Cleaning logs failed", e);
              } finally {
                cleanupInProgress = false;
              }
            }
          }
        };
  
        Thread t = new Thread(r);
        t.start();
      }
    }
  
    protected void subAppend(LoggingEvent event) {
      if (event.getLevel().toInt() <= currentLevel) {
        super.subAppend(event);
        if (fileName != null && qw != null) {
          long size = ((CountingQuietWriter) qw).getCount();
          if (size >= maxFileSize && size >= nextRollover) {
            rollOver();
          }
        }
      }
    }
  
  }
  