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 java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.OutputStream;
024import java.util.concurrent.Executors;
025import java.util.concurrent.ScheduledExecutorService;
026import java.util.concurrent.Semaphore;
027import java.util.concurrent.TimeUnit;
028
029/**
030 * {@link OutputStream} that buffers writes and periodically flushes the
031 * accumulated bytes as a single {@link LogEvent} after a configurable delay.
032 */
033public class LogEventOutputStream extends OutputStream implements LogSource, Runnable {
034    private ByteArrayOutputStream baos = new ByteArrayOutputStream();
035    private Logger logger;
036    private String realm;
037    private ScheduledExecutorService logService;
038    private Semaphore lock = new Semaphore(1);
039    private volatile LogEvent evt;
040    private long delay;
041
042    /** Default constructor. */
043    public LogEventOutputStream() {
044        super();
045        baos = new ByteArrayOutputStream();
046        logService = Executors.newScheduledThreadPool(1);
047    }
048
049    /**
050     * Constructs a stream that flushes the buffered bytes as log events on the
051     * configured logger/realm after {@code delay} ms of inactivity.
052     *
053     * @param logger destination logger
054     * @param realm logger realm
055     * @param delay flush delay in milliseconds
056     */
057    public LogEventOutputStream(Logger logger, String realm, long delay) {
058        this();
059        this.logger = logger;
060        this.realm = realm;
061        this.delay = delay;
062    }
063
064    @Override
065    public void write(int b) throws IOException {
066        if (b == '\n') {
067            try {
068                lock.acquire();
069                if (evt == null) {
070                    evt = new LogEvent(this, "");
071                    logService.schedule(this, delay, TimeUnit.MILLISECONDS);
072                }
073                evt.addMessage(baos.toString());
074                baos = new ByteArrayOutputStream();
075            } catch (InterruptedException ignored) {
076            } finally {
077                lock.release();
078            }
079        } else {
080            baos.write(b);
081        }
082    }
083
084    @Override
085    public void setLogger(Logger logger, String realm) {
086        this.logger = logger;
087        this.realm = realm;
088    }
089
090    @Override
091    public String getRealm() {
092        return realm;
093    }
094
095    @Override
096    public Logger getLogger() {
097        return logger;
098    }
099
100    @Override
101    public void run() {
102        LogEvent event = null;
103        if (evt != null) {
104            try {
105                lock.acquire();
106                event = evt;
107                evt = null;
108            } catch (InterruptedException ignore) {
109            } finally {
110                lock.release();
111            }
112            if (event != null)
113                Logger.log(event);
114        }
115    }
116
117    @Override
118    public void close() throws IOException {
119        super.close();
120        try {
121            lock.acquire();
122            logService.shutdown();
123        } catch (InterruptedException ignore) {
124        } finally {
125            lock.release();
126        }
127    }
128}