/*
* Copyright 2011-16 Fraunhofer ISE
*
* This file is part of OpenMUC.
* For more information visit http://www.openmuc.org
*
* OpenMUC 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.
*
* OpenMUC 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 OpenMUC. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openmuc.framework.datalogger.ascii;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import org.openmuc.framework.data.Record;
import org.openmuc.framework.datalogger.ascii.utils.Const;
import org.openmuc.framework.datalogger.ascii.utils.LoggerUtils;
import org.openmuc.framework.datalogger.spi.DataLoggerService;
import org.openmuc.framework.datalogger.spi.LogChannel;
import org.openmuc.framework.datalogger.spi.LogRecordContainer;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class AsciiLogger implements DataLoggerService {
private final static Logger logger = LoggerFactory.getLogger(AsciiLogger.class);
private final String loggerDirectory;
private final HashMap<String, LogChannel> logChannelList = new HashMap<>();
private static HashMap<String, Long> lastLoggedLineList = new HashMap<>();
private boolean isFillUpFiles = false;
private final static String DIRECTORY = System
.getProperty(AsciiLogger.class.getPackage().getName().toLowerCase() + ".directory");
protected void activate(ComponentContext context) {
logger.info("Activating Ascii Logger");
setSystemProperties();
}
protected void deactivate(ComponentContext context) {
logger.info("Deactivating Ascii Logger");
}
public AsciiLogger() {
if (DIRECTORY == null) {
loggerDirectory = Const.DEFAULT_DIRECTORY;
}
else {
loggerDirectory = DIRECTORY.trim();
}
createDirectory(loggerDirectory);
}
public AsciiLogger(String loggerDirectory) {
this.loggerDirectory = loggerDirectory;
createDirectory(loggerDirectory);
}
private void createDirectory(String loggerDirectory) {
logger.trace("using directory: " + loggerDirectory);
File asciidata = new File(loggerDirectory);
if (!asciidata.exists()) {
if (!asciidata.mkdirs()) {
logger.error("Could not create logger directory: " + asciidata.getAbsolutePath());
// TODO: weitere Behandlung,
}
}
}
@Override
public String getId() {
return "asciilogger";
}
/**
* Will called if OpenMUC starts the logger
*/
@Override
public void setChannelsToLog(List<LogChannel> channels) {
Calendar calendar = new GregorianCalendar(Locale.getDefault());
logChannelList.clear();
logger.trace("channels to log:");
for (LogChannel channel : channels) {
if (logger.isTraceEnabled()) {
logger.trace("channel.getId() " + channel.getId());
logger.trace("channel.getLoggingInterval() " + channel.getLoggingInterval());
}
logChannelList.put(channel.getId(), channel);
}
if (isFillUpFiles) {
Map<String, Boolean> areHeaderIdentical = LoggerUtils.areHeadersIdentical(loggerDirectory, channels,
calendar);
for (Entry<String, Boolean> entry : areHeaderIdentical.entrySet()) {
String key = entry.getKey();
boolean isHeaderIdentical = entry.getValue();
if (isHeaderIdentical) {
// Fill file up with error flag 32 (DATA_LOGGING_NOT_ACTIVE)
if (logger.isTraceEnabled()) {
logger.trace(
"Fill file " + LoggerUtils.buildFilename(key, calendar) + " up with error flag 32.");
}
fillUpFileWithErrorCode(loggerDirectory, key, calendar);
}
else {
// rename file in old file, because of configuration has changed
if (logger.isTraceEnabled()) {
logger.trace("Header not identical. Rename file " + LoggerUtils.buildFilename(key, calendar)
+ " to old.");
}
LoggerUtils.renameFileToOld(loggerDirectory, key, calendar);
}
}
}
else {
LoggerUtils.renameAllFilesToOld(loggerDirectory, calendar);
}
}
@Override
public synchronized void log(List<LogRecordContainer> containers, long timestamp) {
HashMap<List<Integer>, LogIntervalContainerGroup> logIntervalGroups = new HashMap<>();
// add each container to a group with the same logging interval
for (LogRecordContainer container : containers) {
int logInterval = -1;
int logTimeOffset = 0;
List<Integer> logTimeArray = Arrays.asList(logInterval, logTimeOffset);
if (logChannelList.containsKey(container.getChannelId())) {
logInterval = logChannelList.get(container.getChannelId()).getLoggingInterval();
logTimeOffset = logChannelList.get(container.getChannelId()).getLoggingTimeOffset();
logTimeArray = Arrays.asList(logInterval, logTimeOffset);
}
else {
// TODO there might be a change in the channel config file
}
if (logIntervalGroups.containsKey(logTimeArray)) {
// add the container to an existing group
LogIntervalContainerGroup group = logIntervalGroups.get(logTimeArray);
group.add(container);
}
else {
// create a new group and add the container
LogIntervalContainerGroup group = new LogIntervalContainerGroup();
group.add(container);
logIntervalGroups.put(logTimeArray, group);
}
}
// alle gruppen loggen
Iterator<Entry<List<Integer>, LogIntervalContainerGroup>> it = logIntervalGroups.entrySet().iterator();
List<Integer> logTimeArray;
Calendar calendar = new GregorianCalendar(Locale.getDefault());
while (it.hasNext()) {
logTimeArray = it.next().getKey();
LogIntervalContainerGroup group = logIntervalGroups.get(logTimeArray);
LogFileWriter fileOutHandler = new LogFileWriter(loggerDirectory, isFillUpFiles);
calendar.setTimeInMillis(timestamp);
fileOutHandler.log(group, logTimeArray.get(0), logTimeArray.get(1), calendar, logChannelList);
setLastLoggedLineTimeStamp(logTimeArray.get(0), logTimeArray.get(1), calendar.getTimeInMillis());
}
}
@Override
public List<Record> getRecords(String channelId, long startTime, long endTime) throws IOException {
LogChannel logChannel = logChannelList.get(channelId);
LogFileReader reader = null;
if (logChannel != null) {
reader = new LogFileReader(loggerDirectory, logChannel);
return reader.getValues(startTime, endTime);
} // TODO: hier einfügen das nach Loggdateien gesucht werden sollen die vorhanden sind aber nicht geloggt
// werden,
// z.B für server only ohne Logging. Das suchen sollte nur beim ersten mal passieren (start).
else {
throw new IOException("ChannelID (" + channelId + ") not available. It's not a logging Channel.");
}
}
private void setSystemProperties() {
String fillUpProperty = System.getProperty("org.openmuc.framework.datalogger.ascii.fillUpFiles");
if (fillUpProperty != null) {
isFillUpFiles = Boolean.parseBoolean(fillUpProperty);
}
}
public static Long getLastLoggedLineTimeStamp(int loggingInterval, int loggingOffset) {
return lastLoggedLineList.get(loggingInterval + Const.TIME_SEPERATOR_STRING + loggingOffset);
}
public static void setLastLoggedLineTimeStamp(String loggerInterval_loggerTimeOffset, long lastTimestamp) {
lastLoggedLineList.put(loggerInterval_loggerTimeOffset, lastTimestamp);
}
public static void setLastLoggedLineTimeStamp(int loggingInterval, int loggingOffset, long lastTimestamp) {
lastLoggedLineList.put(loggingInterval + Const.TIME_SEPERATOR_STRING + loggingOffset, lastTimestamp);
}
public static long fillUpFileWithErrorCode(String directoryPath, String loggerInterval_loggerTimeOffset,
Calendar calendar) {
String filename = LoggerUtils.buildFilename(loggerInterval_loggerTimeOffset, calendar);
File file = new File(directoryPath + filename);
RandomAccessFile raf = LoggerUtils.getRandomAccessFile(file, "r");
PrintWriter out = null;
String firstLogLine = "";
String lastLogLine = "";
long loggingInterval = 0;
if (loggerInterval_loggerTimeOffset.contains(Const.TIME_SEPERATOR_STRING)) {
loggingInterval = Long.parseLong(loggerInterval_loggerTimeOffset.split(Const.TIME_SEPERATOR_STRING)[0]);
}
else {
loggingInterval = Long.parseLong(loggerInterval_loggerTimeOffset);
}
long lastLogLineTimeStamp = 0;
if (raf != null) {
try {
String line = raf.readLine();
if (line != null) {
while (line.startsWith(Const.COMMENT_SIGN)) {
// do nothing with this data, only for finding the begin of logging
line = raf.readLine();
}
firstLogLine = raf.readLine();
}
// read last line backwards and read last line
byte[] readedByte = new byte[1];
long filePosition = file.length() - 2;
String charString;
while (lastLogLine.isEmpty() && filePosition > 0) {
raf.seek(filePosition);
int readedBytes = raf.read(readedByte);
if (readedBytes == 1) {
charString = new String(readedByte, Const.CHAR_SET);
if (charString.equals(Const.LINESEPARATOR_STRING)) {
lastLogLine = raf.readLine();
}
else {
filePosition -= 1;
}
}
else {
filePosition = -1; // leave the while loop
}
}
raf.close();
int firstLogLineLength = firstLogLine.length();
int lastLogLineLength = lastLogLine.length();
if (firstLogLineLength != lastLogLineLength) {
/**
* TODO: different size of logging lines, probably the last one is corrupted we have to fill it up
* restOfLastLine = completeLastLine(firstLogLine, lastLogLine); raf.writeChars(restOfLastLine);
*/
// File is corrupted rename to old
LoggerUtils.renameFileToOld(directoryPath, loggerInterval_loggerTimeOffset, calendar);
logger.error("File is coruppted, could not fill up, renamed it. " + file.getAbsolutePath());
return 0l;
}
else {
String lastLogLineArray[] = lastLogLine.split(Const.SEPARATOR);
StringBuilder errorValues = LoggerUtils.getErrorValues(lastLogLineArray);
lastLogLineTimeStamp = (long) (Double.parseDouble(lastLogLineArray[2]) * 1000.);
out = LoggerUtils.getPrintWriter(file, true);
long numberOfFillUpLines = LoggerUtils.getNumberOfFillUpLines(lastLogLineTimeStamp,
loggingInterval);
while (numberOfFillUpLines > 0) {
lastLogLineTimeStamp = LoggerUtils.fillUp(out, lastLogLineTimeStamp, loggingInterval,
lastLogLineLength, numberOfFillUpLines, errorValues);
numberOfFillUpLines = LoggerUtils.getNumberOfFillUpLines(lastLogLineTimeStamp, loggingInterval);
}
out.close();
AsciiLogger.setLastLoggedLineTimeStamp(loggerInterval_loggerTimeOffset, lastLogLineTimeStamp);
}
} catch (IOException e) {
logger.error("Could not read file " + file.getAbsolutePath(), e);
LoggerUtils.renameFileToOld(directoryPath, loggerInterval_loggerTimeOffset, calendar);
} finally {
try {
raf.close();
if (out != null) {
out.close();
}
} catch (IOException e) {
logger.error("Could not close file " + file.getAbsolutePath());
}
}
}
return lastLogLineTimeStamp;
}
}