org.cleverbus.admin.services.log.LogEventParsingIterator.java Source code

Java tutorial

Introduction

Here is the source code for org.cleverbus.admin.services.log.LogEventParsingIterator.java

Source

/*
 * Copyright (C) 2015
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.cleverbus.admin.services.log;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import org.cleverbus.common.log.Log;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;

/**
 * Iterates over provided log files, parsing and returning {@link LogEvent}s on demand.
 */
class LogEventParsingIterator implements Iterator<LogEvent>, Closeable {
    private final LogParser parser;
    private final LogParserConfig config;
    private final Queue<File> logFiles;

    private LineIterator lineIterator;

    private LogEvent parsedEvent; // event to be returned by this iterator on next()
    private LogEvent preParsedEvent; // event that was preParsed from a single line, but might have more lines

    private List<String> groupKey = null;

    private int totalCount = 0; // events found total
    private int fileEventsFound = -1; // events per file, resets to 0 on new file
    private int groupCount = 0; // events in current group, resets to 0 on new group

    LogEventParsingIterator(LogParser parser, LogParserConfig config, Collection<File> logFiles)
            throws IOException {
        this.parser = parser;
        this.config = config;
        this.logFiles = new LinkedList<File>(logFiles);
        nextEvent();
    }

    /**
     * Ensures that either {@link #parsedEvent} is not null, or the end is reached.
     *
     * @throws IOException if there's a problem opening a new file, while advancing to the next event
     */
    private void nextEvent() throws IOException {
        if (totalCount >= config.getLimit()) {
            Log.debug("Reached {} events limit - stopping", config.getLimit());
            close();
            return;
        }

        if (parsedEvent != null) {
            return;
        }

        boolean haveMore = !reachedEnd();
        while (parsedEvent == null && haveMore) {
            haveMore = seekToNextEvent();
            if (preParsedEvent != null && config.getGroupBy() != null && config.getGroupLimit() != null) {
                // grouping enabled - check group:
                List<String> nextGroupKey = getGroupKey(preParsedEvent, config);
                if (!nextGroupKey.equals(groupKey)) {
                    groupKey = nextGroupKey; // this event starts a new group
                    groupCount = 1; // reset group counter
                } else if (groupCount >= config.getGroupLimit()) {
                    preParsedEvent = null; // discard the pre-parsed event, as its group is full
                } else {
                    groupCount++;
                }
            }
        }

        if (parsedEvent != null) {
            fileEventsFound++;
            totalCount++;
        }
    }

    /**
     * Advance to the position where next line is available,
     * regardless of whether it passes filter requirements or not.
     * On the way to this position any lines that are not log events
     * will be appended to the previous log event.
     * <p/>
     * If this call is successful, either {@link #parsedEvent} will be a new event,
     * or {@link #preParsedEvent} will not be null; or both.
     *
     * @return true, if there might be more events; false otherwise
     * @throws IOException if there's a problem opening a new file
     */
    private boolean seekToNextEvent() throws IOException {
        LogEvent nextEvent = config.createLogEvent();

        LineIterator iterator;
        while ((iterator = getLineIterator()) != null) {
            while (iterator.hasNext()) {
                // process the line:
                LogEvent event = parser.parseLine(iterator.next(), nextEvent, preParsedEvent, config);
                // check what to do next:
                if (preParsedEvent != null && event != preParsedEvent) {
                    // there is pre-parsed event, but the line was NOT appended to it
                    // => pre-parsed event will not get any more lines, it can be considered fully parsed
                    parsedEvent = preParsedEvent; // graduate pre-parsed event - it'll be the next event
                    preParsedEvent = event; // next event that was found (if any) is now pre-parsed
                    return true; // found full pre-parsed event => success
                } else if (event == nextEvent) {
                    // nextEvent is now pre-parsed, but previous pre-parsed event is null, so no event graduated
                    preParsedEvent = nextEvent;
                    return true;
                }
                // otherwise line was ignored or added to the pre-parsed event, nothing really changed
            }
        }
        return false; // failure
    }

    private boolean reachedEnd() throws IOException {
        return getLineIterator() == null;
    }

    /**
     * Returns a line iterator to process next/current file, opening the next file, if necessary.
     *
     * @return line iterator for the current/next file; or null if no files left
     */
    private LineIterator getLineIterator() throws IOException {
        if (lineIterator != null && lineIterator.hasNext()) {
            return lineIterator;
        }

        // discard current line iterator
        LineIterator.closeQuietly(lineIterator);
        lineIterator = null;

        if (fileEventsFound == 0) {
            Log.debug("No events in the last file, closing prematurely");
            close(); // optimize: last file had no events, no point continuing
        } else if (!logFiles.isEmpty()) {
            File file = logFiles.poll();
            Log.debug("Opening {}", file);
            lineIterator = FileUtils.lineIterator(file);
            fileEventsFound = 0; // restart per-file counter
        }

        return lineIterator;
    }

    private List<String> getGroupKey(LogEvent event, LogParserConfig config) {
        List<String> groupKey = new ArrayList<String>(config.getGroupBy().size());
        for (int propertyIndex = 0; propertyIndex < event.getPropertyCount(); propertyIndex++) {
            if (config.getGroupBy().contains(event.getPropertyNames().get(propertyIndex))) {
                groupKey.add(String.valueOf(event.getProperties()[propertyIndex]));
            }
        }
        return groupKey;
    }

    @Override
    public boolean hasNext() {
        return parsedEvent != null;
    }

    @Override
    public LogEvent next() {
        LogEvent result = parsedEvent;
        parsedEvent = null;

        try {
            nextEvent();
        } catch (IOException exc) {
            throw new IllegalStateException("Error prefetching next event", exc);
        }
        return result;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void close() {
        LineIterator.closeQuietly(lineIterator);
        lineIterator = null;
        logFiles.clear();
        Log.debug("Closed");
    }
}