/*
* PatientView
*
* Copyright (c) Worth Solutions Limited 2004-2013
*
* This file is part of PatientView.
*
* PatientView 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.
* PatientView 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 PatientView in a file
* titled COPYING. If not, see <http://www.gnu.org/licenses/>.
*
* @package PatientView
* @link http://www.patientview.org
* @author PatientView <info@patientview.org>
* @copyright Copyright (c) 2004-2013, Worth Solutions Limited
* @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0
*/
package org.patientview.monitoring;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.util.StopWatch;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* Logs the number of files that XML Import needs to process
* <p/>
* Monitors those files to see if the XML Import is stalled or not working properly
*
* @author Deniz Ozger
*/
public final class ImportMonitor {
// Timings and limitations
private static final int FREQUENCY_OF_LOGGING_IMPORT_FILE_COUNTS_IN_MINUTES = 1;
/**
* Should always be equal to monitoringFrequencyInMinutes /
* FREQUENCY_OF_LOGGING_IMPORT_FILE_COUNTS_IN_MINUTES. Thus; it is calculated in runtime.
*/
private static int numberOfLinesToRead = -1;
// Import data file format
private static final String RECORD_DATA_DELIMITER = ",";
private static final String RECORD_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static final int DATE_POSITION_IN_RECORD = 0;
private static final String COUNT_LOG_FILENAME_FORMAT = "yyyy-MM-dd";
private static final String COMMENT_PREFIX = "#";
// Class constants
private static final String PROJECT_PROPERTIES_FILE = "patientview.properties";
private static final Logger LOGGER = LoggerFactory.getLogger(ImportMonitor.class);
private static final int LINE_FEED = 0xA;
private static final int CARRIAGE_RETURN = 0xD;
private static final int SECONDS_IN_MINUTE = 60;
private static final int MILLISECONDS = 1000;
private static final int HASH_SEED_17 = 17;
private static final int HASH_SEED_31 = 31;
private ImportMonitor() {
}
public static void main(String[] args) {
int importFileCheckCount = 0;
while (true) {
LOGGER.info("******** Import Logger & Monitor wakes up ********");
int monitoringFrequencyInMinutes = Integer.parseInt(getProperty("importerMonitor.frequency.minutes"));
numberOfLinesToRead = monitoringFrequencyInMinutes / FREQUENCY_OF_LOGGING_IMPORT_FILE_COUNTS_IN_MINUTES;
LOGGER.info("Import file counts will be logged every {} minutes, whereas a "
+ "health check will be done every {} minutes. Each monitoring will check the last {} lines "
+ "of the log", new Object[]{FREQUENCY_OF_LOGGING_IMPORT_FILE_COUNTS_IN_MINUTES,
monitoringFrequencyInMinutes, numberOfLinesToRead});
StopWatch sw = new StopWatch();
sw.start();
importFileCheckCount = importFileCheckCount + FREQUENCY_OF_LOGGING_IMPORT_FILE_COUNTS_IN_MINUTES;
/**
* Get the folders that will be monitored
*/
List<FolderToMonitor> foldersToMonitor = getFoldersToMonitor();
/**
* Count the number of files in these folders
*/
setTheNumberOfCurrentFilesToFolderObjects(foldersToMonitor);
/**
* Log counts to a file
*/
logNumberOfFiles(foldersToMonitor);
/**
* If it is time, check the overall monitor stability as well
*/
if (importFileCheckCount == numberOfLinesToRead) {
monitorImportProcess(foldersToMonitor);
importFileCheckCount = 0;
} else {
LOGGER.info("Next monitoring will happen in {} minutes",
numberOfLinesToRead - importFileCheckCount);
}
sw.stop();
LOGGER.info("ImportMonitor ends, it took {} (mm:ss)",
new SimpleDateFormat("mm:ss").format(sw.getTotalTimeMillis()));
/**
* Sleep for (frequency - execution time) seconds
*/
long maxTimeToSleep = FREQUENCY_OF_LOGGING_IMPORT_FILE_COUNTS_IN_MINUTES * SECONDS_IN_MINUTE * MILLISECONDS;
long executionTime = sw.getTotalTimeMillis();
long timeToSleep = maxTimeToSleep - executionTime;
// if execution time is more than max time to sleep, then sleep for the max time
if (timeToSleep < 0) {
timeToSleep = maxTimeToSleep;
}
LOGGER.info("ImportMonitor will now sleep for {} (mm:ss)",
new SimpleDateFormat("mm:ss").format(timeToSleep));
try {
Thread.sleep(timeToSleep);
} catch (InterruptedException e) {
LOGGER.error("Import Monitor could not sleep: ", e); // possible insomnia
System.exit(0);
}
}
}
/**
* Counts the number of files in folders and sets those values to folder objects
*/
private static void setTheNumberOfCurrentFilesToFolderObjects(List<FolderToMonitor> foldersToMonitor) {
for (FolderToMonitor folderToMonitor : foldersToMonitor) {
folderToMonitor.setCurrentNumberOfFiles(getNumberOfFilesInDirectory(
folderToMonitor.getPathIncludingName()));
}
}
private static void monitorImportProcess(List<FolderToMonitor> foldersToMonitor) {
/**
* Read some lines from the file
*/
List<String> lines = getLastNLinesOfFile(numberOfLinesToRead);
/**
* Make sure all lines have the same number of folders, and that matches folders to monitor now
*/
if (doNumberOfFoldersInLogFileAndPropertiesFileMatch(lines, foldersToMonitor)) {
/**
* Convert them to meaningful objects
*/
List<CountRecord> countRecords = getCountRecordsFromLines(lines, foldersToMonitor);
/**
* Check the records to see if files are static or they exceed the limit
*/
if (areThereEnoughDataToMonitor(countRecords)) {
List<FolderToMonitor> foldersThatHaveStaticFiles = getFoldersWhoseNumberOfFilesAreStatic(countRecords);
List<FolderToMonitor> foldersWhoseNumberOfFilesExceedTheirLimits =
getFoldersWhoseNumberOfFilesExceedTheirLimits(countRecords);
if (foldersThatHaveStaticFiles.size() > 0 || foldersWhoseNumberOfFilesExceedTheirLimits.size() > 0) {
/**
* Send an email if there are problems with the importer
*/
sendAWarningEmail(foldersThatHaveStaticFiles, foldersWhoseNumberOfFilesExceedTheirLimits,
countRecords);
} else {
LOGGER.info("Importer appears to be working fine.");
}
}
} else {
LOGGER.error("Skipping monitoring folders as number of folders in log file and number of folders "
+ "defined in properties file do not match.");
}
}
private static boolean doNumberOfFoldersInLogFileAndPropertiesFileMatch(List<String> logLines,
List<FolderToMonitor> foldersToMonitor) {
boolean folderSizeInLinesMatch;
int lastFolderSizeInLine = -1;
/**
* Check if lines in log file have the same number of folders
*/
if (logLines.size() > 0) {
lastFolderSizeInLine = getNumberOfFoldersInLogLine(logLines.get(0));
}
for (String line : logLines) {
if (StringUtils.isBlank(line)) {
LOGGER.info("Empty record is encountered, this might be the first line");
} else {
int currentNumberOfFoldersInThisLogLine = getNumberOfFoldersInLogLine(line);
folderSizeInLinesMatch = lastFolderSizeInLine == currentNumberOfFoldersInThisLogLine;
if (!folderSizeInLinesMatch) {
LOGGER.warn("Folders in properties file and log file do not match. If some folders were "
+ "added/removed recently, then this may be the reason. Folder count of previous line "
+ " is {}, whereas another line's count is {} ({})",
new Object[]{currentNumberOfFoldersInThisLogLine, lastFolderSizeInLine, line});
return false;
} else {
lastFolderSizeInLine = getNumberOfFoldersInLogLine(line);
}
}
}
/**
* Compare the folder count in log lines with the count of folders that we will monitor now
*/
return foldersToMonitor.size() == lastFolderSizeInLine;
}
/**
* Returns the number of folders defined in a line of log.
* <p/>
* If log file looks like 1985-07-03,1,2,3 then it returns 3
*/
private static int getNumberOfFoldersInLogLine(String line) {
String[] recordDataSections = line.split(RECORD_DATA_DELIMITER);
if (recordDataSections != null) {
// first part is date, and the last part is comment sections, so subtract 2 from the total number
return recordDataSections.length - 2;
} else {
return 0;
}
}
/**
* Lists the folders to be monitored, which are defined in properties files
*/
private static List<FolderToMonitor> getFoldersToMonitor() {
List<FolderToMonitor> foldersToMonitor = new ArrayList<FolderToMonitor>();
List<String> propertyNames = getPropertyNamesStartingWith("importerMonitor.directory.path");
FolderToMonitor folderToMonitor;
for (String propertyName : propertyNames) {
folderToMonitor = new FolderToMonitor();
String[] propertyNameParts = propertyName.split("\\.");
if (propertyNameParts != null && propertyNameParts.length > 0) {
try {
folderToMonitor.setId(Integer.parseInt(propertyNameParts[propertyNameParts.length - 1]));
folderToMonitor.setPathIncludingName(getProperty(propertyName));
folderToMonitor.setMaxNumberOfFiles(Integer.parseInt(getProperty(
"importerMonitor.directory.maxNumberOfFiles." + folderToMonitor.getId())));
} catch (NumberFormatException e) {
LOGGER.error("Could not retrieve property value {}, possible faulty property name definition",
propertyName, e);
}
if (!foldersToMonitor.contains(folderToMonitor)) {
foldersToMonitor.add(folderToMonitor);
}
}
}
Collections.sort(foldersToMonitor, FolderToMonitor.Order.ById.descending());
return foldersToMonitor;
}
/**
* Appends the current time and file counts to importer data file
*/
private static void logNumberOfFiles(List<FolderToMonitor> foldersToMonitor) {
File countFile = getTodaysCountFile();
try {
FileOutputStream fileOutStream = new FileOutputStream(countFile, true); // append data
String countRecord = getCountDataToWriteToFile(foldersToMonitor);
fileOutStream.write(countRecord.getBytes());
LOGGER.info("Appended this line: \"{}\" to count file {}", countRecord, countFile.getAbsolutePath());
fileOutStream.flush();
fileOutStream.close();
} catch (IOException e) {
LOGGER.error("Could not persist number of files in folders", e);
}
}
/**
* Returns the log file that will be used today - there is a rotation in log files
*/
private static File getTodaysCountFile() {
SimpleDateFormat dateTimeFormat = new SimpleDateFormat(COUNT_LOG_FILENAME_FORMAT);
String fileName = getProperty("importer.data.file.name") + dateTimeFormat.format(new Date());
return new File(getProperty("importer.data.file.directory.path") + "/" + fileName);
}
/**
* Returns the record that needs to be appended to importer data file
*/
private static String getCountDataToWriteToFile(List<FolderToMonitor> foldersToMonitor) {
SimpleDateFormat dateTimeFormat = new SimpleDateFormat(RECORD_DATE_FORMAT);
String countData = "\n" + dateTimeFormat.format(new Date());
/**
* Append counts
*/
for (FolderToMonitor folderToMonitor : foldersToMonitor) {
countData = countData + RECORD_DATA_DELIMITER + folderToMonitor.getCurrentNumberOfFiles();
}
/**
* Append folder names for reference
*/
countData = countData + "," + COMMENT_PREFIX + "Folders:";
for (FolderToMonitor folderToMonitor : foldersToMonitor) {
countData = countData + folderToMonitor.getName() + "-";
}
return countData;
}
/**
* Returns number of files in a directory, excluding hidden files of unix environment and directories
*/
private static int getNumberOfFilesInDirectory(String directoryPath) {
File[] files = new File(directoryPath).listFiles();
int count = 0;
if (files != null) {
for (File file : files) {
if (file.isFile() && !file.getName().startsWith(".")) { // no no no, we don't want any hidden files
count++;
}
}
}
LOGGER.info("Number of files in directory {} is {}", directoryPath, count);
return count;
}
/**
* Checks if there is enough data (file counts) in log file for monitoring
*/
private static boolean areThereEnoughDataToMonitor(List<CountRecord> countRecords) {
if (countRecords.size() < numberOfLinesToRead) {
LOGGER.info("There are not enough data (only {} lines) to monitor. There should be at least {} lines "
+ "for Import monitor to process.",
countRecords.size(), numberOfLinesToRead);
return false;
}
return true;
}
/**
* Checks if the number of files in given directories are static over a certain period of time
*/
private static List<FolderToMonitor> getFoldersWhoseNumberOfFilesAreStatic(List<CountRecord> countRecords) {
List<FolderToMonitor> foldersWhoseFilesAreStatic = new ArrayList<FolderToMonitor>();
/**
* First, treat all folders with files as static
*/
for (CountRecord countRecord : countRecords) {
for (FolderToMonitor folderToMonitor : countRecord.getFoldersToMonitor()) {
if (folderToMonitor.getCurrentNumberOfFiles() > 0
&& !foldersWhoseFilesAreStatic.contains(folderToMonitor)) {
foldersWhoseFilesAreStatic.add(folderToMonitor);
}
}
}
/**
* What we have in log files is a matrix (x,y) where x denotes folders and y denotes number of files in
* that folder. It is advised that you see the log file before continuing.. The data is stored in a
* primitive file instead of a relational database, hence the long explanations..
*
* We will first compare (x, y), (x, y+1), (x, y+2) to see if files in a folder was static over time.
* Then we will check the rest of the folders, starting by comparing (x+1, y), (x+1, y+1), (x+1, y+2) ...
*/
// first line of the log
CountRecord firstLogRecord = countRecords.get(0);
// total number of folders that are logged in his line
int numberOfFoldersToCheck = firstLogRecord.getFoldersToMonitor().size();
/**
* We will make iterations totaling to the number of folders that needs to be monitored
*/
for (int i = 0; i < numberOfFoldersToCheck; i++) {
/**
* i-th folder of the first log record. We will compare this value with other log records (other
* recordings recently)
*/
int firstRecordsFolderFileCount = firstLogRecord.getFoldersToMonitor().get(i).getCurrentNumberOfFiles();
/**
* Go backwards in time (logs) to see if the file count was always the same or not
*/
for (CountRecord countRecord : countRecords) {
// i-th folder of the current log record
FolderToMonitor thisRecordsFolder = countRecord.getFoldersToMonitor().get(i);
// number of files of i-th folder of the current log record
int thisRecordsFolderFileCount = thisRecordsFolder.getCurrentNumberOfFiles();
/**
* If this log record's i-th folder's file count is different than the first log record's
* i-th folder's file count, then it means importer is working on this folder
*/
if (firstRecordsFolderFileCount != thisRecordsFolderFileCount) {
foldersWhoseFilesAreStatic.remove(thisRecordsFolder);
}
}
}
/**
* Log the findings
*/
for (FolderToMonitor monitoredFolder : foldersWhoseFilesAreStatic) {
LOGGER.info("Files are static in directory {}", monitoredFolder.getPathIncludingName());
}
return foldersWhoseFilesAreStatic;
}
/**
* Checks if the number of pending files for importer exceeds a given limit
*/
private static List<FolderToMonitor> getFoldersWhoseNumberOfFilesExceedTheirLimits(List<CountRecord> countRecords) {
List<FolderToMonitor> foldersWhoseFilesExceedTheirLimits = new ArrayList<FolderToMonitor>();
if (countRecords.size() > 0) {
List<CountRecord> countRecordsToTest = new ArrayList<CountRecord>(countRecords);
Collections.sort(countRecordsToTest, CountRecord.Order.ByRecordTime.descending());
CountRecord firstCountRecord = countRecords.get(0);
int numberOfFoldersToCheck = firstCountRecord.getFoldersToMonitor().size();
for (int i = 0; i < numberOfFoldersToCheck; i++) {
for (CountRecord countRecord : countRecords) {
FolderToMonitor thisRecordsFolder = countRecord.getFoldersToMonitor().get(i);
if (thisRecordsFolder.getCurrentNumberOfFiles() > thisRecordsFolder.getMaxNumberOfFiles()
&& !foldersWhoseFilesExceedTheirLimits.contains(thisRecordsFolder)) {
LOGGER.info("Folder {}'s files ({}) exceed its limit ({})",
new Object[]{thisRecordsFolder.getId(), thisRecordsFolder.getCurrentNumberOfFiles(),
thisRecordsFolder.getMaxNumberOfFiles()});
foldersWhoseFilesExceedTheirLimits.add(thisRecordsFolder);
}
}
}
}
return foldersWhoseFilesExceedTheirLimits;
}
private static void sendAWarningEmail(List<FolderToMonitor> foldersThatHaveStaticFiles,
List<FolderToMonitor> foldersWhoseNumberOfFilesExceedTheirLimits,
List<CountRecord> countRecords) {
try {
Resource resource = new ClassPathResource("/" + PROJECT_PROPERTIES_FILE);
Properties props = PropertiesLoaderUtils.loadProperties(resource);
String subject = "Problems encountered in Patient View XML Importer";
String body = getWarningEmailBody(foldersThatHaveStaticFiles, foldersWhoseNumberOfFilesExceedTheirLimits,
countRecords);
String fromAddress = props.getProperty("noreply.email");
// todo For testing purposes this property is overridden
// String[] toAddresses = {props.getProperty("support.email")};
String[] toAddresses = {"patientview-testing@solidstategroup.com"};
sendEmail(fromAddress, toAddresses, null, subject, body);
} catch (IOException e) {
LOGGER.error("Could not find properties file: {}", e);
}
}
public static void sendEmail(String from, String[] to, String[] bcc, String subject, String body) {
if (StringUtils.isBlank(from)) {
throw new IllegalArgumentException("Cannot send mail missing 'from'");
}
if ((to == null || to.length == 0) && (bcc == null || bcc.length == 0)) {
throw new IllegalArgumentException("Cannot send mail missing recipients");
}
if (StringUtils.isBlank(subject)) {
throw new IllegalArgumentException("Cannot send mail missing 'subject'");
}
if (StringUtils.isBlank(body)) {
throw new IllegalArgumentException("Cannot send mail missing 'body'");
}
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[]{"classpath*:context-standalone.xml"});
JavaMailSender javaMailSender = (JavaMailSender) context.getBean("javaMailSender");
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper;
try {
messageHelper = new MimeMessageHelper(message, true);
messageHelper.setTo(to);
if (bcc != null && bcc.length > 0) {
Address[] bccAddresses = new Address[bcc.length];
for (int i = 0; i < bcc.length; i++) {
bccAddresses[i] = new InternetAddress(bcc[i]);
}
message.addRecipients(Message.RecipientType.BCC, bccAddresses);
}
messageHelper.setFrom(from);
messageHelper.setSubject(subject);
messageHelper.setText(body, false); // Note: the second param indicates to send plaintext
javaMailSender.send(messageHelper.getMimeMessage());
LOGGER.info("Sent an email about Importer issues. From: {} To: {}", from, Arrays.toString(to));
} catch (Exception e) {
LOGGER.error("Could not send email: {}", e);
}
}
private static String getWarningEmailBody(List<FolderToMonitor> foldersThatHaveStaticFiles,
List<FolderToMonitor> foldersWhoseNumberOfFilesExceedTheirLimits,
List<CountRecord> countRecords) {
String emailBody = "";
String newLine = System.getProperty("line.separator");
emailBody += "[This is an automated email from Renal PatientView - do not reply to this email]";
emailBody += newLine;
emailBody += newLine + "There are some problems in XML Importer. Please see below for details.";
emailBody += newLine;
if (foldersThatHaveStaticFiles.size() > 0) {
emailBody += newLine + "Importer has not imported any files in some folders recently. These folders are:";
emailBody += newLine;
for (FolderToMonitor folder : foldersThatHaveStaticFiles) {
emailBody += newLine + "Folder ID: " + folder.getId() + " Path: " + folder.getPathIncludingName()
+ " Number of files: " + folder.getCurrentNumberOfFiles();
}
emailBody += newLine;
emailBody += newLine;
}
if (foldersWhoseNumberOfFilesExceedTheirLimits.size() > 0) {
emailBody += newLine + "Files are in some folders are above the threshold. These folders are:";
emailBody += newLine;
for (FolderToMonitor folder : foldersWhoseNumberOfFilesExceedTheirLimits) {
emailBody += newLine + "Folder ID: " + folder.getId() + " Path: " + folder.getPathIncludingName()
+ " Number of files: " + folder.getCurrentNumberOfFiles() + " Threshold: "
+ folder.getMaxNumberOfFiles();
}
}
emailBody += newLine;
emailBody += newLine;
emailBody += newLine + "Please see the following most recent file count records for reference:";
emailBody += newLine;
List<CountRecord> countRecordsToSend = new ArrayList<CountRecord>(countRecords);
Collections.sort(countRecordsToSend, CountRecord.Order.ByRecordTime.descending());
for (CountRecord countRecord : countRecordsToSend) {
emailBody += countRecord;
}
return emailBody;
}
/**
* Returns the last N lines of a file. Assumes lines are terminated by |n ascii character
*/
private static List<String> getLastNLinesOfFile(int numberOfLinesToReturn) {
List<String> lastNLines = new ArrayList<String>();
java.io.RandomAccessFile fileHandler = null;
try {
File file = getTodaysCountFile();
fileHandler = new java.io.RandomAccessFile(file, "r");
long totalNumberOfCharactersInFile = file.length() - 1;
StringBuilder sb = new StringBuilder();
int numberOfLinesRead = 0;
/**
* loop through characters in file, construct lines out of characters, add lines to a list
*/
for (long currentCharacter = totalNumberOfCharactersInFile; currentCharacter != -1; currentCharacter--) {
fileHandler.seek(currentCharacter);
int readByte = fileHandler.readByte();
if (readByte == LINE_FEED || readByte == CARRIAGE_RETURN) {
if (numberOfLinesRead == numberOfLinesToReturn) {
break;
}
numberOfLinesRead++;
/**
* add line to line list
*/
String currentLine = sb.reverse().toString();
sb = new StringBuilder();
if (StringUtils.isNotBlank(currentLine)) {
lastNLines.add(currentLine);
} else {
LOGGER.error("Read line does not contain any data");
continue;
}
} else {
sb.append((char) readByte);
}
}
/**
* add the last line
*/
lastNLines.add(sb.reverse().toString());
} catch (Exception e) {
LOGGER.error("Can not find today's file", e);
} finally {
if (fileHandler != null) {
try {
fileHandler.close();
} catch (IOException e) {
fileHandler = null;
}
}
}
return lastNLines;
}
private static String getProperty(String propertyName) {
Resource resource = new ClassPathResource("/" + PROJECT_PROPERTIES_FILE);
Properties props = null;
String propertyValue = "";
try {
props = PropertiesLoaderUtils.loadProperties(resource);
propertyValue = props.getProperty(propertyName);
} catch (IOException e) {
LOGGER.error("Could not find properties file: {}", e);
}
return propertyValue;
}
/**
* Returns all property names that starts with the given string
*
* @return empty array list if no property name is found
*/
private static List<String> getPropertyNamesStartingWith(String queriedProperyName) {
Resource resource = new ClassPathResource("/" + PROJECT_PROPERTIES_FILE);
Properties props = null;
Set<String> allPropertyNames;
List<String> propertyNames = new ArrayList<String>();
try {
props = PropertiesLoaderUtils.loadProperties(resource);
allPropertyNames = props.stringPropertyNames();
for (String propertyName : allPropertyNames) {
if (propertyName.startsWith(queriedProperyName)) {
propertyNames.add(propertyName);
}
}
} catch (IOException e) {
LOGGER.error("Could not find properties file: {}", e);
}
return propertyNames;
}
/**
* Convert lines on the file into sensible objects
*/
private static List<CountRecord> getCountRecordsFromLines(List<String> lines,
List<FolderToMonitor> foldersToMonitor) {
List<CountRecord> countRecords = new ArrayList<CountRecord>();
for (String line : lines) {
String date = extractDateAsString(line);
List<Integer> numberOfFilesInDirectories = extractNumberOfFilesInDirectories(line);
if (isValidCountRecord(date, RECORD_DATE_FORMAT, numberOfFilesInDirectories)) {
countRecords.add(CountRecord.fromDateAndFileCounts(date, RECORD_DATE_FORMAT,
numberOfFilesInDirectories, foldersToMonitor));
} else {
LOGGER.error("Invalid record: {}", line);
}
}
return countRecords;
}
/**
* Check if the parameters are in correct formats
*/
private static boolean isValidCountRecord(String dateString, String dateFormant,
List<Integer> numberOfFilesInDirectories) {
return numberOfFilesInDirectories != null && numberOfFilesInDirectories.size() > 0
&& parseDate(dateString, dateFormant) != null;
}
private static Date parseDate(String maybeDate, String format) {
Date date = null;
try {
DateTimeFormatter fmt = DateTimeFormat.forPattern(format);
DateTime dateTime = fmt.parseDateTime(maybeDate);
date = dateTime.toDate();
} catch (Exception e) {
LOGGER.error("Invalid date: {}", maybeDate, e);
}
return date;
}
private static String extractDateAsString(String line) {
return extractRecordDataOnThisPosition(line, DATE_POSITION_IN_RECORD);
}
private static List<Integer> extractNumberOfFilesInDirectories(String line) {
List<Integer> numberOfFilesList = new ArrayList<Integer>();
if (StringUtils.isBlank(line)) {
LOGGER.info("Empty record is encountered, this might be the first line");
} else {
String[] recordDataSections = line.split(RECORD_DATA_DELIMITER);
for (int i = DATE_POSITION_IN_RECORD + 1; recordDataSections.length > i; i++) {
try {
if (!recordDataSections[i].startsWith(COMMENT_PREFIX)) {
numberOfFilesList.add(Integer.parseInt(recordDataSections[i]));
}
} catch (NumberFormatException e) {
LOGGER.error("Could not parse file count {} into an integer. The log line is: {}",
new Object[]{recordDataSections[i], line}, e);
break;
}
}
}
return numberOfFilesList;
}
private static String extractRecordDataOnThisPosition(String line, int position) {
String[] recordDataSections = line.split(RECORD_DATA_DELIMITER);
if (recordDataSections != null && recordDataSections.length > position) {
return recordDataSections[position];
} else {
return null;
}
}
private static class FolderToMonitor {
private int id;
private String pathIncludingName;
private int maxNumberOfFiles;
private int currentNumberOfFiles;
public String getPathIncludingName() {
return pathIncludingName;
}
public void setPathIncludingName(String pathIncludingName) {
this.pathIncludingName = pathIncludingName;
}
public int getMaxNumberOfFiles() {
return maxNumberOfFiles;
}
public void setMaxNumberOfFiles(int maxNumberOfFiles) {
this.maxNumberOfFiles = maxNumberOfFiles;
}
public String getName() {
if (StringUtils.isNotBlank(pathIncludingName)) {
String[] pathParts = pathIncludingName.split("\\/");
if (pathParts != null && pathParts.length > 0) {
return pathParts[pathParts.length - 1];
} else {
LOGGER.error("Could not retrieve folder name from path {}");
}
}
return pathIncludingName;
}
public int getCurrentNumberOfFiles() {
return currentNumberOfFiles;
}
public void setCurrentNumberOfFiles(int currentNumberOfFiles) {
this.currentNumberOfFiles = currentNumberOfFiles;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public static enum Order implements Comparator<FolderToMonitor> {
ById() {
public int compare(FolderToMonitor leftRecord, FolderToMonitor rightRecord) {
return Double.compare(leftRecord.getId(), rightRecord.getId()) * -1;
}
};
public abstract int compare(FolderToMonitor leftRecord, FolderToMonitor rightRecord);
public Comparator ascending() {
return this;
}
public Comparator descending() {
return Collections.reverseOrder(this);
}
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof FolderToMonitor)) {
return false;
}
FolderToMonitor rhs = (FolderToMonitor) obj;
return new EqualsBuilder().append(pathIncludingName, rhs.getPathIncludingName()).isEquals();
}
public int hashCode() {
return new HashCodeBuilder(HASH_SEED_17, HASH_SEED_31).append(pathIncludingName)
.append(maxNumberOfFiles).toHashCode();
}
}
private static final class CountRecord {
private Date recordTime = null;
private List<FolderToMonitor> foldersToMonitor;
private CountRecord() {
}
public static CountRecord fromDateAndFileCounts(String dateString, String dateFormat,
List<Integer> currentFileCountsInFolders,
List<FolderToMonitor> foldersInPropertiesFileToMonitor) {
CountRecord countRecord = new CountRecord();
countRecord.setRecordTime(parseDate(dateString, dateFormat));
List<FolderToMonitor> foldersInLogFileToMonitor = new ArrayList<FolderToMonitor>();
for (int i = 0; i < currentFileCountsInFolders.size()
&& i < foldersInPropertiesFileToMonitor.size(); i++) {
FolderToMonitor folderToMonitorInPropertiesFile = foldersInPropertiesFileToMonitor.get(i);
FolderToMonitor folderToMonitor = new FolderToMonitor();
folderToMonitor.setId(folderToMonitorInPropertiesFile.getId());
folderToMonitor.setPathIncludingName(folderToMonitorInPropertiesFile.getPathIncludingName());
folderToMonitor.setMaxNumberOfFiles(folderToMonitorInPropertiesFile.getMaxNumberOfFiles());
folderToMonitor.setCurrentNumberOfFiles(currentFileCountsInFolders.get(i));
foldersInLogFileToMonitor.add(folderToMonitor);
}
countRecord.setFoldersToMonitor(foldersInLogFileToMonitor);
return countRecord;
}
public static enum Order implements Comparator<CountRecord> {
ByRecordTime() {
public int compare(CountRecord leftRecord, CountRecord rightRecord) {
return leftRecord.getRecordTime().compareTo(rightRecord.getRecordTime()) * -1;
}
};
public abstract int compare(CountRecord leftRecord, CountRecord rightRecord);
public Comparator ascending() {
return this;
}
public Comparator descending() {
return Collections.reverseOrder(this);
}
}
public Date getRecordTime() {
return recordTime;
}
public void setRecordTime(Date recordTime) {
this.recordTime = recordTime;
}
public List<FolderToMonitor> getFoldersToMonitor() {
return foldersToMonitor;
}
public void setFoldersToMonitor(List<FolderToMonitor> foldersToMonitor) {
this.foldersToMonitor = foldersToMonitor;
}
public String toString() {
SimpleDateFormat dateTimeFormat = new SimpleDateFormat(RECORD_DATE_FORMAT);
String countRecordAsString = "\n" + dateTimeFormat.format(recordTime);
for (FolderToMonitor folderToMonitor : foldersToMonitor) {
countRecordAsString = countRecordAsString + RECORD_DATA_DELIMITER
+ folderToMonitor.getCurrentNumberOfFiles();
}
countRecordAsString = countRecordAsString + "," + COMMENT_PREFIX + "Folders:";
for (FolderToMonitor folderToMonitor : foldersToMonitor) {
countRecordAsString = countRecordAsString + folderToMonitor.getName() + "-";
}
return countRecordAsString;
}
}
}