com.logsniffer.reader.support.FormattedTextReader.java Source code

Java tutorial

Introduction

Here is the source code for com.logsniffer.reader.support.FormattedTextReader.java

Source

/*******************************************************************************
 * logsniffer, open source tool for viewing, monitoring and analysing log data.
 * Copyright (c) 2015 Scaleborn UG, www.scaleborn.com
 *
 * logsniffer 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.
 *
 * logsniffer 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 com.logsniffer.reader.support;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.logsniffer.fields.FieldBaseTypes;
import com.logsniffer.model.LogEntry;
import com.logsniffer.reader.FormatException;

/**
 * Reader based on patterns.
 * 
 * @author mbok
 * 
 */
public abstract class FormattedTextReader extends AbstractPatternLineReader<Matcher> {
    private static final Logger logger = LoggerFactory.getLogger(FormattedTextReader.class);

    public abstract static class Specifier implements Cloneable {
        private int minWidth;
        private int maxWidth;
        private String modifier;
        private final String specifierKey;
        private String fieldName;

        public Specifier(final String specifierKey) {
            super();
            this.specifierKey = specifierKey;
        }

        /**
         * @return the minWidth
         */
        public int getMinWidth() {
            return minWidth;
        }

        /**
         * @param minWidth
         *            the minWidth to set
         */
        public void setMinWidth(final int minWidth) {
            this.minWidth = minWidth;
        }

        /**
         * @return the maxWidth
         */
        public int getMaxWidth() {
            return maxWidth;
        }

        /**
         * @param maxWidth
         *            the maxWidth to set
         */
        public void setMaxWidth(final int maxWidth) {
            this.maxWidth = maxWidth;
        }

        /**
         * @return the modifier
         */
        public String getModifier() {
            return modifier;
        }

        /**
         * @param modifier
         *            the modifier to set
         */
        public void setModifier(final String modifier) {
            this.modifier = modifier;
        }

        protected abstract String getRegex() throws FormatException;

        protected abstract void set(LogEntry entry, String match) throws FormatException;

        protected abstract FieldBaseTypes getFieldType();

        public final String getSpecifierKey() {
            return specifierKey;
        }

        /**
         * @return the attributeName
         */
        public String getFieldName() {
            return fieldName != null ? fieldName : specifierKey;
        }

        /**
         * Adapts the lengthPattern by length modifiers and returns it. If
         * length modifiers aren't specified the withoutLengthPattern is
         * returned if given otherwise the unchanged lengthPattern.
         * 
         * @param lengthPattern
         * @param withoutLengthPattern
         * @return
         */
        protected String adaptRegexByLength(final String lengthPattern, final String withoutLengthPattern) {
            if (maxWidth > 0 && maxWidth == minWidth) {
                return lengthPattern + "{" + maxWidth + "}";
            } else if (maxWidth > 0) {
                return lengthPattern + "{" + Math.max(0, minWidth) + "," + maxWidth + "}";
            } else if (minWidth > 0) {
                return lengthPattern + "{" + minWidth + ",}";
            }
            return withoutLengthPattern != null ? withoutLengthPattern : lengthPattern;
        }

        @Override
        public Specifier clone() throws CloneNotSupportedException {
            return (Specifier) super.clone();
        }
    }

    /**
     * Specifier for arbitrary unknown specifiers.
     * 
     * @author mbok
     * 
     */
    public static class ArbitraryTextSpecifier extends Specifier {
        private final boolean greedy;

        public ArbitraryTextSpecifier(final String specifierKey, final boolean greedy) {
            super(specifierKey);
            this.greedy = greedy;
        }

        @Override
        protected String getRegex() {
            return adaptRegexByLength(".", ".*") + (greedy ? "" : "?");
        }

        @Override
        protected void set(final LogEntry entry, final String match) throws FormatException {
            entry.put(getFieldName(), match);
        }

        @Override
        protected FieldBaseTypes getFieldType() {
            return FieldBaseTypes.STRING;
        }
    }

    /**
     * Specifier to ignore.
     * 
     * @author mbok
     * 
     */
    public static final class IgnoreSpecifier extends Specifier {
        public IgnoreSpecifier(final String specifierKey) {
            super(specifierKey);
        }

        @Override
        protected String getRegex() {
            return null;
        }

        @Override
        protected void set(final LogEntry entry, final String match) throws FormatException {
            // NOP
        }

        @Override
        protected FieldBaseTypes getFieldType() {
            return null;
        }
    }

    private static final Pattern SPECIFIER_PATTERN = Pattern
            .compile("%(-?(\\d+))?(\\.(\\d+))?([a-zA-Z])(\\{([^\\}]+)\\})?");

    @JsonProperty
    @NotEmpty
    private String formatPattern;

    @JsonProperty
    private Map<String, String> specifiersFieldMapping = new HashMap<String, String>();

    protected Specifier[] parsingSpecifiers;
    protected Pattern parsingPattern;

    /**
     * 
     * @return the supported specifiers
     */
    protected abstract Specifier[] createSupportedSpecifiers();

    /**
     * @param formatPattern
     *            the formatPattern to set
     */
    @Override
    protected void init() throws FormatException {
        super.init();
        if (parsingPattern == null) {
            // Only if not yet parsed
            if (formatPattern != null) {
                final ArrayList<Specifier> specs = new ArrayList<Specifier>();
                final StringBuilder parsingPatternStr = new StringBuilder();
                final Matcher m = SPECIFIER_PATTERN.matcher(formatPattern);
                int leftPos = 0;
                while (m.find()) {
                    if (m.start() > leftPos) {
                        parsingPatternStr.append(Pattern.quote(formatPattern.substring(leftPos, m.start())));
                    }
                    leftPos = m.end();
                    final String minWidthStr = m.group(2);
                    final String maxWidthStr = m.group(4);
                    final String specName = m.group(5);
                    final String specModifier = m.group(7);
                    int minWidth = -1;
                    if (minWidthStr != null && minWidthStr.length() > 0) {
                        minWidth = Integer.parseInt(minWidthStr);
                    }
                    int maxWidth = -1;
                    if (maxWidthStr != null && maxWidthStr.length() > 0) {
                        maxWidth = Integer.parseInt(maxWidthStr);
                    }
                    Specifier spec = null;
                    for (final Specifier specTest : createSupportedSpecifiers()) {
                        if (specTest.getSpecifierKey().equals(specName)) {
                            spec = specTest;
                            break;
                        }
                    }
                    if (spec == null) {
                        logger.debug(
                                "Format specifier {} in pattern '{}' is unknown and will be parsed as simple text pattern",
                                specName, formatPattern);
                        spec = new ArbitraryTextSpecifier(specName, false);
                    } else if (spec instanceof IgnoreSpecifier) {
                        logger.debug("Format specifier '{}' in pattern '{}' is ignored", specName, formatPattern);
                        continue;
                    }
                    spec.setMaxWidth(maxWidth);
                    spec.setMinWidth(minWidth);
                    spec.setModifier(specModifier);
                    parsingPatternStr.append("(");
                    parsingPatternStr.append(spec.getRegex());
                    parsingPatternStr.append(")");
                    spec.fieldName = specifiersFieldMapping.get(specName);
                    if (StringUtils.isBlank(spec.fieldName)) {
                        spec.fieldName = specName;
                    }
                    specs.add(spec);
                }
                parsingPatternStr.append(Pattern.quote(formatPattern.substring(leftPos)));
                parsingPattern = Pattern.compile(parsingPatternStr.toString());
                parsingSpecifiers = specs.toArray(new Specifier[specs.size()]);
                logger.debug("Prepared parsing pattern '{}' for log4j conversion pattern: {}", parsingPattern,
                        formatPattern);
            } else {
                parsingSpecifiers = null;
                parsingPattern = null;
            }
        }
    }

    @Override
    protected ReadingContext<Matcher> getReadingContext() throws FormatException {
        // Cloning specifiers for thread-safety
        final Specifier[] localParsingSpecifiers = new Specifier[parsingSpecifiers.length];
        for (int i = 0; i < parsingSpecifiers.length; i++) {
            try {
                localParsingSpecifiers[i] = parsingSpecifiers[i].clone();
            } catch (final CloneNotSupportedException e) {
                throw new FormatException("Failed to clone specifier: " + parsingSpecifiers[i], e);
            }
        }
        return new ReadingContext<Matcher>() {
            @Override
            public Matcher matches(final String line) {
                final Matcher m = parsingPattern.matcher(line);
                return m.matches() ? m : null;
            }

            @Override
            public void fillAttributes(final LogEntry entry, final Matcher ctx) throws FormatException {
                int specNumber = 1;
                for (final Specifier spec : localParsingSpecifiers) {
                    final String match = ctx.group(specNumber++);
                    spec.set(entry, match);
                }
            }
        };
    }

    /**
     * @return the formatPattern
     */
    public String getFormatPattern() {
        return formatPattern;
    }

    /**
     * @param formatPattern
     *            the formatPattern to set
     */
    public void setFormatPattern(final String formatPattern) {
        this.formatPattern = formatPattern;
        this.parsingPattern = null;
    }

    @Override
    protected String getPatternInfo() {
        return formatPattern;
    }

    /**
     * @return the specifiersFieldMapping
     */
    public Map<String, String> getSpecifiersFieldMapping() {
        return specifiersFieldMapping;
    }

    /**
     * @param specifiersFieldMapping
     *            the specifiersFieldMapping to set
     */
    public void setSpecifiersFieldMapping(final Map<String, String> specifiersFieldMapping) {
        this.specifiersFieldMapping = specifiersFieldMapping;
        this.parsingPattern = null;
    }

    @Override
    public LinkedHashMap<String, FieldBaseTypes> getFieldTypes() throws FormatException {
        init();
        final LinkedHashMap<String, FieldBaseTypes> fields = super.getFieldTypes();
        if (parsingSpecifiers != null) {
            for (final Specifier s : parsingSpecifiers) {
                fields.put(s.getFieldName(), s.getFieldType());
            }
        }
        return fields;
    }

}