All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fluxtion.server.plugin.connector.file.FileEventSource Maven / Gradle / Ivy

The newest version!
/*
 *
 *  * SPDX-FileCopyrightText: © 2024 Gregory Higgins 
 *  * SPDX-License-Identifier: AGPL-3.0-only
 *
 */

package com.fluxtion.server.plugin.connector.file;

import com.fluxtion.agrona.IoUtil;
import com.fluxtion.server.service.AbstractAgentHostedEventSourceService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;

@Log4j2
@SuppressWarnings("all")
public class FileEventSource extends AbstractAgentHostedEventSourceService {

    @Getter
    @Setter
    private String filename;
    private InputStream stream;
    private BufferedReader reader = null;
    private char[] buffer;
    private int offset = 0;
    @Getter
    @Setter
    private int batchSize;
    @Getter
    @Setter
    private boolean publishOnStart = false;
    @Getter
    @Setter
    private ReadStrategy readStrategy = ReadStrategy.COMMITED;
    private boolean tail = true;
    private boolean commitRead = true;
    private boolean latestRead = false;
    private final AtomicBoolean startComplete = new AtomicBoolean(false);

    private long streamOffset;
    private MappedByteBuffer commitPointer;
    private boolean once;

    public FileEventSource() {
        this(1024);
    }

    /* visible for testing */
    public FileEventSource(int initialBufferSize) {
        super("fileEventFeed");
        buffer = new char[initialBufferSize];
    }

    @Override
    public void start() {
        log.info("Starting FileEventSource");
    }

    @Override
    public void startComplete() {
        tail = readStrategy == ReadStrategy.COMMITED | readStrategy == ReadStrategy.EARLIEST | readStrategy == ReadStrategy.LATEST;
        once = !tail;
        commitRead = readStrategy == ReadStrategy.COMMITED;
        latestRead = readStrategy == ReadStrategy.LATEST | readStrategy == ReadStrategy.ONCE_LATEST;
        log.info("startComplete FileEventFeed tail:{} once:{}, commitRead:{} latestRead:{} readStrategy:{}", tail, once, commitRead, latestRead, readStrategy);

        File committedReadFile = new File(filename + ".readPointer");
        if (readStrategy == ReadStrategy.ONCE_EARLIEST | readStrategy == ReadStrategy.EARLIEST) {
            streamOffset = 0;
        } else if (committedReadFile.exists()) {
            commitPointer = IoUtil.mapExistingFile(committedReadFile, "committedReadFile_" + filename);
            streamOffset = commitPointer.getLong(0);
            log.info("{} reading committedReadFile:{}, streamOffset:{}", serviceName, committedReadFile.getAbsolutePath(), streamOffset);
        } else if (commitRead) {
            commitPointer = IoUtil.mapNewFile(committedReadFile, 1024);
            streamOffset = 0;
            log.info("{} creating committedReadFile:{}, streamOffset:{}", serviceName, committedReadFile.getAbsolutePath(), streamOffset);
        }

        startComplete.set(true);
        if (filename == null || filename.isEmpty()) {
            //throw an  error
        }
        connectReader();
        tail = true;
        if (publishOnStart) {
            log.info("publishOnStart: {}", publishOnStart);
            doWork();
        }
        log.info("startComplete - exit");
    }

    @Override
    public void onStart() {
        log.info("agent onStart FileEventFeed");
    }

    @SuppressWarnings("all")
    @Override
    public int doWork() {
        if (!tail) {
            return 0;
        }
        try {
            if (connectReader() == null) {
                return 0;
            }
            log.debug("doWork FileEventFeed");

            ArrayList records = null;

            int nread;
            while (reader.ready()) {
                tail = !once;
                Scanner scanner = new Scanner(reader);
                nread = reader.read(buffer, offset, buffer.length - offset);
                log.trace("Read {} bytes from {}", nread, getFilename());

                if (nread > 0) {
                    offset += nread;
                    String line;
                    boolean foundOneLine = false;
                    do {
                        line = extractLine();
                        if (line != null) {
                            foundOneLine = true;
                            log.trace("Read a line from {} line:'{}'", getFilename(), line);
                            if (records == null) {
                                records = new ArrayList<>();
                                if (latestRead) {
                                    records.add(line);
                                }
                            }
                            if (latestRead) {
                                if (records.isEmpty()) {
                                    records.add(line);
                                }
                                records.set(0, line);
                            } else {
                                records.add(line);
                            }

                            if (records.size() >= batchSize & !latestRead) {
                                var recordBatch = records;
                                records = new ArrayList<>();
                                output.publish(recordBatch);
                                log.info("publish batch:{}", recordBatch);
                                if (commitRead) {
                                    commitPointer.force();
                                }
                            }
                        }
                    } while (line != null);

                    if (latestRead & foundOneLine & !once) {
                        log.info("publish latest:{}", records);
                        output.publish(records);
                    } else if (foundOneLine && records.size() > 0) {
                        output.publish(records);
                        log.info("publish batch:{}", records);
                        records.clear();
                        if (commitRead) {
                            commitPointer.force();
                        }
                    }

                    if (!foundOneLine && offset == buffer.length) {
                        char[] newbuf = new char[buffer.length * 2];
                        System.arraycopy(buffer, 0, newbuf, 0, buffer.length);
                        log.info("Increased buffer from {} to {}", buffer.length, newbuf.length);
                        buffer = newbuf;
                    }
                }
            }

            return records == null ? 0 : records.size();

        } catch (IOException e) {
            try {
                reader.close();
            } catch (IOException ex) {

            }
            try {
                stream.close();
            } catch (IOException ex) {

            }
            reader = null;
            stream = null;
        }
        return 0;
    }

    @Override
    public void stop() {
        log.trace("Stopping");
        try {
            if (stream != null) {
                stream.close();
                log.trace("Closed input stream");
            }
        } catch (IOException e) {
            log.error("Failed to close FileStreamSourceTask stream: ", e);
        } finally {
            if (commitPointer != null) {
                commitPointer.force();
                IoUtil.unmap(commitPointer);
            }
        }
    }

    @Override
    public void tearDown() {
        super.tearDown();
    }

    private Reader connectReader() {
        if (startComplete.get() & stream == null && filename != null && !filename.isEmpty()) {
            try {
                stream = Files.newInputStream(Paths.get(filename));
                log.info("Found previous offset, trying to skip to file offset {}", streamOffset);
                long skipLeft = streamOffset;
                while (skipLeft > 0) {
                    try {
                        long skipped = stream.skip(skipLeft);
                        skipLeft -= skipped;
                    } catch (IOException e) {
                        log.error("Error while trying to seek to previous offset in file {}: ", filename, e);
                        //TODO log error and stop
                    }
                }
                log.info("Skipped to offset {}", streamOffset);
                reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
                log.info("Opened {} for reading", getFilename());
            } catch (NoSuchFileException e) {
                log.warn("Couldn't find file {} for FileStreamSourceTask, sleeping to wait for it to be created", getFilename());
            } catch (IOException e) {
                log.error("Error while trying to open file {}: ", filename, e);
                throw new RuntimeException(e);
            }
        }
        return reader;
    }

    private String extractLine() {
        int until = -1, newStart = -1;
        for (int i = 0; i < offset; i++) {
            if (buffer[i] == '\n') {
                until = i;
                newStart = i + 1;
                break;
            } else if (buffer[i] == '\r') {
                // We need to check for \r\n, so we must skip this if we can't check the next char
                if (i + 1 >= offset)
                    return null;

                until = i;
                newStart = (buffer[i + 1] == '\n') ? i + 2 : i + 1;
                break;
            }
        }

        if (until != -1) {
            String result = new String(buffer, 0, until);
            System.arraycopy(buffer, newStart, buffer, 0, buffer.length - newStart);
            offset = offset - newStart;
            streamOffset += newStart;
            if (commitRead) {
                commitPointer.putLong(0, streamOffset);
            }
            return result;
        } else {
            return null;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy