001package com.credibledoc.substitution.doc.module.substitution;
002
003import com.credibledoc.combiner.date.DateService;
004import com.credibledoc.combiner.log.buffered.LogBufferedReader;
005import com.credibledoc.combiner.log.buffered.LogFileReader;
006import com.credibledoc.combiner.node.file.NodeFile;
007import com.credibledoc.combiner.tactic.Tactic;
008import com.credibledoc.substitution.core.exception.SubstitutionRuntimeException;
009import com.credibledoc.substitution.doc.module.substitution.application.Substitution;
010import lombok.RequiredArgsConstructor;
011import org.springframework.stereotype.Service;
012
013import javax.inject.Inject;
014import java.io.File;
015import java.text.SimpleDateFormat;
016import java.util.Date;
017import java.util.regex.Matcher;
018import java.util.regex.Pattern;
019
020/**
021 * Implementation of the {@link Tactic}
022 * for the {@link Substitution}.
023 */
024@Service
025@RequiredArgsConstructor(onConstructor = @__(@Inject))
026public class SubstitutionTactic implements Tactic {
027
028    /**
029     * RegEx of a date in a {@link Substitution} log line,
030     * for example <pre>29.09.2018 22:53:42.494|https-jsse-nio-15443-exec-1...</pre>
031     */
032    private static final String PATTERN_DATE_STRING =
033        "\\d\\d\\.\\d\\d\\.\\d\\d\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d\\d\\d";
034
035    /**
036     * Compiled {@value #PATTERN_DATE_STRING} value
037     */
038    private static final Pattern PATTERN_DATE = Pattern.compile(PATTERN_DATE_STRING);
039
040    /**
041     * {@link Pattern} of date and time of {@link Substitution}, for example
042     * for example <pre>29.09.2018 22:53:42.494|https-jsse-nio-15443-exec-1...</pre>
043     */
044    private static final String DATE_FORMAT_STRING = "dd.MM.yyyy HH:mm:ss.SSS";
045    private static final String ONE_SPACE = " ";
046    private static final int THREAD_NAME_INDEX = 35;
047    private static final String PIPE = "|";
048
049    private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT_STRING);
050
051    @Override
052    public Date findDate(File file) {
053        try (LogBufferedReader logBufferedReader = new LogBufferedReader(new LogFileReader(file))) {
054            String line = logBufferedReader.readLine();
055            while (line != null) {
056                Date date = parseDateFromLine(line);
057                if (date != null) {
058                    return date;
059                }
060                line = logBufferedReader.readLine();
061            }
062            return null;
063        } catch (Exception e) {
064            throw new SubstitutionRuntimeException("Cannot find date. File: " + file.getAbsolutePath(), e);
065        }
066    }
067
068    @Override
069    public Date findDate(String line, NodeFile nodeFile) {
070        return parseDateFromLine(line);
071    }
072
073    @Override
074    public boolean containsDate(String line) {
075        if (line == null) {
076            return false;
077        }
078        return PATTERN_DATE.matcher(line).find();
079    }
080
081    @Override
082    public String parseDateStingFromLine(String line) {
083        if (line == null) {
084            return null;
085        }
086        return findDateString(line);
087    }
088
089    /**
090     * Parse a {@link Date} from a log line.
091     *
092     * @param line    for example
093     *                <pre>29.09.2018 22:53:42.494|https-jsse-nio-15443-exec-1...</pre>
094     *                for {@link Substitution}
095     * @return a parsed {@link Date}, for example 13.04.2018 07:27:41.462
096     * or 'null' if the line is null or the date cannot be found.
097     */
098    private Date parseDateFromLine(String line) {
099        if (line == null) {
100            return null;
101        }
102        try {
103            String clientDateString = findDateString(line);
104            if (clientDateString != null) {
105                return simpleDateFormat.parse(clientDateString);
106            }
107
108            return null;
109        } catch (Exception e) {
110            throw new SubstitutionRuntimeException("Cannot parse a date from the line: " + line, e);
111        }
112    }
113
114    /**
115     * Parse a {@link Date} from a log line.
116     *
117     * @param line for example
118     * <pre>29.09.2018 22:53:42.494|https-jsse-nio-15443-exec-1...</pre>
119     * for {@link Substitution}
120     * @return a parsed {@link Date}, for example 13.04.2018 07:27:41.462 or 'null' if the line is 'null' or the date cannot be found.
121     */
122    private String findDateString(String line) {
123        int maxLength = line.length() > 90 ? 90 : line.length();
124        String dateString = null;
125        Matcher matcher = PATTERN_DATE.matcher(line.substring(0, maxLength));
126        if (matcher.find()) {
127            dateString = matcher.group();
128        }
129        return dateString;
130    }
131
132
133    /**
134     * Examples:
135     * <pre>05.11.2018 08:37:08.100|https-jsse-nio-15443-exec-10|DEBUG|...</pre>
136     * <pre>19.12.2018 06:17:56.761 http-nio-8080-exec-305 INFO  [1212121]...</pre>
137     */
138    @Override
139    public String findThreadName(String line) {
140        int beginIndex = line.indexOf(PIPE);
141        int endIndex = line.indexOf(PIPE, beginIndex + 1);
142
143        if (isPipeSeparator(line)) {
144            if (beginIndex != -1 && endIndex != -1) {
145                return line.substring(beginIndex + PIPE.length(), endIndex);
146            }
147        } else {
148            int firstSpaceIndex = line.indexOf(ONE_SPACE);
149            int secondSpaceIndex = line.indexOf(ONE_SPACE, firstSpaceIndex + 1);
150            int thirdSpaceIndex = line.indexOf(ONE_SPACE, secondSpaceIndex + 1);
151            return line.substring(secondSpaceIndex + 1, thirdSpaceIndex);
152        }
153
154        return null;
155    }
156
157    private boolean isPipeSeparator(String line) {
158        return line.substring(0, THREAD_NAME_INDEX).contains(PIPE);
159    }
160
161    @Override
162    public Date findDate(String line) {
163        return DateService.getInstance().parseDateTimeFromLine(line, simpleDateFormat, PATTERN_DATE, 35);
164    }
165}