001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.util;
020
021import org.jpos.q2.Q2;
022
023import java.io.File;
024import java.util.concurrent.LinkedBlockingQueue;
025import java.util.concurrent.ThreadPoolExecutor;
026import java.util.concurrent.TimeUnit;
027
028/**
029 * Serializes log file compression across all log listener instances
030 * through a single on-demand background worker.
031 *
032 * <p>The worker thread is created on demand, runs as a low-priority daemon,
033 * and exits after {@link #IDLE_TIMEOUT} milliseconds of inactivity.</p>
034 */
035public class LogCompressor {
036    private static final long IDLE_TIMEOUT = 30_000L;
037
038    private static volatile LogCompressor instance;
039    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
040      0,
041      1,
042      IDLE_TIMEOUT,
043      TimeUnit.MILLISECONDS,
044      new LinkedBlockingQueue<>(),
045      r -> {
046          Thread t = Thread.ofPlatform()
047            .daemon(true)
048            .name("log-compressor", 0)
049            .unstarted(r);
050          t.setPriority(Thread.NORM_PRIORITY - 1);
051          return t;
052      }
053    );
054
055    private LogCompressor() {
056        executor.allowCoreThreadTimeOut(true);
057    }
058
059    /**
060     * Returns the lazy-initialised singleton.
061     *
062     * @return the shared {@link LogCompressor}
063     */
064    public static LogCompressor getInstance() {
065        if (instance == null) {
066            synchronized (LogCompressor.class) {
067                if (instance == null)
068                    instance = new LogCompressor();
069            }
070        }
071        return instance;
072    }
073
074    /**
075     * Submits a compression task for {@code logFile} on the shared executor.
076     *
077     * @param logFile log file being compressed (used for diagnostic logging)
078     * @param task compression task to run
079     */
080    public void submit(File logFile, Runnable task) {
081        executor.execute(() -> {
082            try {
083                task.run();
084            } catch (Throwable t) {
085                Q2.getQ2().getLog().warn(
086                  String.format("error running log compression task for '%s'", logFile),
087                  t
088                );
089            }
090        });
091    }
092}