Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.postfix; import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import mitm.common.util.Check; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.mutable.MutableInt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class used for parsing postfix log files. * * Note: this class is NOT thread safe * * @author Martijn Brinkers * */ public class PostfixLogParser { private final static Logger logger = LoggerFactory.getLogger(PostfixLogParser.class); private static final String DATE_PART = "(\\w+\\s+\\d{1,2}\\s+\\d+(?::\\d+){2})"; private static final String HOSTNAME_PART = "\\S+"; private static final String PROCESS_PART = "postfix/(\\w+)\\[\\d+\\]\\s*:"; private static final String QUEUE_ID_PART = "([0-9a-fA-F]+):?\\s+(.*)"; /* * Pattern for matching a postfix log line */ protected static final Pattern POSTFIX_LOG_PATTERN = Pattern.compile( "\\s*" + DATE_PART + "\\s*" + HOSTNAME_PART + "\\s*" + PROCESS_PART + "\\s*" + QUEUE_ID_PART + "\\s*"); /* * Indexes for groups from POSTFIX_LOG_PATTERN regular expression */ private static final int QUEUE_GROUP_INDEX = 3; private static final int INFO_GROUP_INDEX = 4; /* * Pattern for a corrupt active queue (can happen when postsuper -d is called for an item in the active queueu) */ public static final Pattern POSTFIX_LOG_CORRUPT_ACTIVE_QUEUE_PATTERN = Pattern .compile("\\s*" + DATE_PART + "\\s*" + HOSTNAME_PART + "\\s*" + PROCESS_PART + "\\s*" + "warning: qmgr_active_" + ".*(?:id|remove) " + QUEUE_ID_PART + "\\s*"); /* * Pattern used to match the lines we are interested in. If null all lines will match */ private final Pattern searchPattern; /* * Map that storing an association between queue ID and the log lines associated with that queue ID. * This map 'temporarily' stores a log item until all log lines belonging to that queue id are found. */ private Map<String, SortableLogItem> activeItems; /* * If true parsing will be stopped (can be set when enough items have been found) */ private boolean stopParsing; /* * Current item index which will be used to ensure that the items are ordered in the same * order as in the log file or when the queue ID was created */ private int index; /* * Event handler interface */ private static interface MatchingItemHandler { public void onMatchingItem(SortableLogItem logItem); } /* * event handler called when a new item is ready */ private MatchingItemHandler matchingItemHandler; private static class SortableLogItem extends PostfixLogItem implements Comparable<SortableLogItem> { private final int index; private SortableLogItem(String queueID, int index) { super(queueID); this.index = index; } public int getIndex() { return index; } @Override public int compareTo(SortableLogItem o) { return index - o.getIndex(); } } public PostfixLogParser(Pattern searchPattern) { this.searchPattern = searchPattern; } public PostfixLogParser() { this(null); } private int newIndex() { return index++; } private void logItemComplete(SortableLogItem logItem) { if (logItem == null || logItem.getLines() == null) { logger.warn("Missing log lines."); return; } boolean match = true; if (searchPattern != null) { match = false; /* * Step through all log lines and see whether we have a match */ for (String line : logItem.getLines()) { Matcher matcher = searchPattern.matcher(line); if (matcher.find()) { match = true; break; } } } if (match) { Check.notNull(matchingItemHandler, "matchingItemHandler"); matchingItemHandler.onMatchingItem(logItem); } } private void parseLine(String line) { Matcher matcher = POSTFIX_LOG_PATTERN.matcher(line); boolean match = matcher.matches(); boolean corrupt = false; if (!match) { /* * Check if it's a corrupt queue ID line (qmgr_active_corrupt) */ matcher = POSTFIX_LOG_CORRUPT_ACTIVE_QUEUE_PATTERN.matcher(line); match = matcher.matches(); if (match) { corrupt = true; } } if (match) { String queueID = matcher.group(QUEUE_GROUP_INDEX); /* * It's a line containing a queue ID. Check if we already have one stored */ SortableLogItem logItem = activeItems.get(queueID); if (logItem == null) { logItem = new SortableLogItem(queueID, newIndex()); activeItems.put(queueID, logItem); } logItem.getLines().add(line); String logInfo = matcher.group(INFO_GROUP_INDEX); /* * If the message was removed we are done with this message */ if ("removed".equals(logInfo) || "requeued".equals(logInfo) || corrupt) { /* * The message has been removed so all the log lines for this queue ID have * been stored and we can remove it from the activeItems map. */ activeItems.remove(queueID); logItemComplete(logItem); } } else { /* * Add the non matching line with a null queue ID */ SortableLogItem logItem = new SortableLogItem(null, newIndex()); logItem.getLines().add(line); logItemComplete(logItem); } } private void parseLog(Reader log) throws IOException { LineNumberReader lineReader = new LineNumberReader(log); index = 0; stopParsing = false; activeItems = new HashMap<String, SortableLogItem>(); String line; while (!stopParsing && (line = lineReader.readLine()) != null) { parseLine(line); } /* * If stopParsing was false we came to the end of the log. There can however still be items * in activeItems. This happens when a message is still in one of the postfix queues * (message is not yet delivered). We will therefore 'force finish' the items still active * until stopParsing */ for (SortableLogItem logItem : activeItems.values()) { if (stopParsing) { break; } logItemComplete(logItem); } } public List<PostfixLogItem> getLogItems(Reader log, final int startIndex, final int maxItems) throws IOException { if (startIndex < 0) { throw new IllegalArgumentException("startIndex must be >= 0"); } if (maxItems <= 0) { throw new IllegalArgumentException("maxItems must be > 0"); } final List<SortableLogItem> logItems = new LinkedList<SortableLogItem>(); final MutableInt index = new MutableInt(0); matchingItemHandler = new MatchingItemHandler() { @Override public void onMatchingItem(SortableLogItem logItem) { if (index.intValue() >= startIndex) { logItems.add(logItem); } index.increment(); if ((index.intValue() - startIndex) == maxItems) { stopParsing = true; } if ((index.intValue() - startIndex) > maxItems) { throw new IllegalStateException("Parsing should have been stopped."); } } }; parseLog(log); /* * Sort the items on the index to make sure that the items are ordered in the same order * as the log entries. Items with queue ID are ordered on the first time the queue ID * was present */ Collections.sort(logItems); return new ArrayList<PostfixLogItem>(logItems); } public int getLogCount(Reader log) throws IOException { final MutableInt count = new MutableInt(0); matchingItemHandler = new MatchingItemHandler() { @Override public void onMatchingItem(SortableLogItem logItem) { count.increment(); } }; parseLog(log); return count.intValue(); } public List<String> getRawLogItems(Reader log, final int startIndex, final int maxItems) throws IOException { if (startIndex < 0) { throw new IllegalArgumentException("startIndex must be >= 0"); } if (maxItems <= 0) { throw new IllegalArgumentException("maxItems must be > 0"); } List<String> logLines = new LinkedList<String>(); LineNumberReader lineReader = new LineNumberReader(log); int index = 0; String line; while ((line = lineReader.readLine()) != null) { line = StringUtils.trimToNull(line); if (line == null) { continue; } if (searchPattern == null) { if (index >= startIndex) { logLines.add(line); } index++; } else { /* * Search pattern is given. We *must* always check if there is a match * before doing anything else */ Matcher matcher = searchPattern.matcher(line); if (matcher.find()) { if (index >= startIndex) { logLines.add(line); } index++; } } if (logLines.size() > maxItems) { break; } } return logLines; } public int getRawLogCount(Reader log) throws IOException { LineNumberReader lineReader = new LineNumberReader(log); int count = 0; String line; while ((line = lineReader.readLine()) != null) { line = StringUtils.trimToNull(line); if (line == null) { continue; } if (searchPattern != null) { Matcher matcher = searchPattern.matcher(line); if (matcher.find()) { count++; } } else { count++; } } return count; } }