/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.component.workflow.execution.internal;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.core.component.execution.api.BatchedConsoleRowsProcessor;
import de.rcenvironment.core.component.execution.api.BatchingConsoleRowsForwarder;
import de.rcenvironment.core.component.execution.api.ComponentExecutionException;
import de.rcenvironment.core.component.execution.api.ConsoleRow;
import de.rcenvironment.core.component.execution.api.ConsoleRow.Type;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionException;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.TempFileService;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;
/**
* Writes {@link ConsoleRow}s into a log files and stores them into the data management.
*
* @author Doreen Seider
*/
public class ComponentsConsoleLogFileWriter {
private static final int WAIT_INTERVAL_FLUSHED_AND_DISPOSED = 60;
private static final String UNDERSCORE = "_";
private static final String ALLOWED_CHARACTERS = "[^a-zA-Z0-9.-]";
private final WorkflowExecutionStorageBridge wfDataManagementStorage;
private AtomicReference<BatchingConsoleRowsForwarder> errorWorkflowConsoleRowWriter = new AtomicReference<>();
private final Map<String, BatchingConsoleRowsForwarder> completeConsoleRowWriters
= Collections.synchronizedMap(new HashMap<String, BatchingConsoleRowsForwarder>());
private final Map<String, BatchingConsoleRowsForwarder> errorConsoleRowWriters
= Collections.synchronizedMap(new HashMap<String, BatchingConsoleRowsForwarder>());
private AtomicReference<CountDownLatch> logFilesFlushedAndDisposedLatch = new AtomicReference<>();
protected ComponentsConsoleLogFileWriter(WorkflowExecutionStorageBridge wfDataManagementStorage) {
this.wfDataManagementStorage = wfDataManagementStorage;
}
protected void initializeWorkflowLogFile() throws IOException {
errorWorkflowConsoleRowWriter.set(new BatchingConsoleRowsForwarder(new BactchedWorkflowErrorLogFileWriter("")));
logFilesFlushedAndDisposedLatch.set(new CountDownLatch(1));
}
protected void initializeComponentLogFile(String compExeId) throws IOException {
logFilesFlushedAndDisposedLatch.set(new CountDownLatch((int) logFilesFlushedAndDisposedLatch.get().getCount() + 2));
completeConsoleRowWriters.put(compExeId, new BatchingConsoleRowsForwarder(new ComponentCompleteLogFileWriter(compExeId)));
errorConsoleRowWriters.put(compExeId, new BatchingConsoleRowsForwarder(new ComponentErrorLogFileWriter(compExeId)));
}
protected void addComponentConsoleRow(ConsoleRow consoleRow) {
if (completeConsoleRowWriters.containsKey(consoleRow.getComponentIdentifier())) {
completeConsoleRowWriters.get(consoleRow.getComponentIdentifier()).onConsoleRow(consoleRow);
} else {
LogFactory.getLog(getClass()).error(StringUtils.format(
"Failed to add console row to component's complete log file: %s", consoleRow.getPayload()));
}
if (consoleRow.getType() == Type.TOOL_ERROR || consoleRow.getType() == Type.COMPONENT_ERROR
|| consoleRow.getType() == Type.LIFE_CYCLE_EVENT) {
if (errorConsoleRowWriters.containsKey(consoleRow.getComponentIdentifier())) {
errorConsoleRowWriters.get(consoleRow.getComponentIdentifier()).onConsoleRow(consoleRow);
} else {
LogFactory.getLog(getClass()).error(StringUtils.format(
"Failed to add console row to component's error log file: %s", consoleRow.getPayload()));
}
errorWorkflowConsoleRowWriter.get().onConsoleRow(consoleRow);
}
}
protected void addWorkflowConsoleRow(ConsoleRow consoleRow) {
errorWorkflowConsoleRowWriter.get().onConsoleRow(consoleRow);
}
protected void flushAndDisposeLogFiles() {
for (BatchingConsoleRowsForwarder writer : completeConsoleRowWriters.values()) {
writer.onConsoleRow(null); // should be improved by dedicated ConsoleRow instance
}
for (BatchingConsoleRowsForwarder writer : errorConsoleRowWriters.values()) {
writer.onConsoleRow(null); // should be improved by dedicated ConsoleRow instance
}
errorWorkflowConsoleRowWriter.get().onConsoleRow(null); // should be improved by dedicated ConsoleRow instance
try {
boolean terminated;
synchronized (this) {
terminated = logFilesFlushedAndDisposedLatch.get().await(WAIT_INTERVAL_FLUSHED_AND_DISPOSED, TimeUnit.SECONDS);
}
if (!terminated) {
LogFactory.getLog(getClass()).error("Time out exceeded while waiting for log files to be written");
}
} catch (InterruptedException e) {
LogFactory.getLog(getClass()).error("Failed to wait log files to be written", e);
}
}
/**
* Writes the {@link ConsoleRow}s to the complete log file of a component in the RCE temp directory and stores the file in the RCE data
* management on dedicated {@link ConsoleRow}s.
*
* @author Doreen Seider
*/
protected class ComponentCompleteLogFileWriter extends AbstractBatchedComponentLogFileWriter {
private static final String VERSION = "1.0";
protected ComponentCompleteLogFileWriter(String exeId) throws IOException {
super(exeId);
}
@Override
protected void storeLogFileInDataManagement(ConsoleRow triggerConsoleRow) {
try {
String[] payload = StringUtils.splitAndUnescape(triggerConsoleRow.getPayload());
String logFileName = StringUtils.format("%s-%s_compl.log", triggerConsoleRow.getComponentName()
.replaceAll(ALLOWED_CHARACTERS, UNDERSCORE), payload[2]);
wfDataManagementStorage.addComponentCompleteLog(logFile, logFileName, payload[1]);
} catch (ComponentExecutionException e) {
logErrorOnWritingToDMFailure(e, triggerConsoleRow);
}
}
@Override
protected String formatConsoleRow(ConsoleRow consoleRow) {
return consoleRowFormatter.toComponentCompleteLogFileFormat(consoleRow);
}
@Override
protected String getFileFormatVersion() {
return VERSION;
}
}
/**
* Writes the {@link ConsoleRow}s to the error log file of a component in the RCE temp directory and stores the file in the RCE data
* management on dedicated {@link ConsoleRow}s.
*
* @author Doreen Seider
*/
protected class ComponentErrorLogFileWriter extends AbstractBatchedComponentLogFileWriter {
private static final String VERSION = "1.0";
protected ComponentErrorLogFileWriter(String exeId) throws IOException {
super(exeId);
}
@Override
protected void storeLogFileInDataManagement(ConsoleRow triggerConsoleRow) {
try {
String[] payload = StringUtils.splitAndUnescape(triggerConsoleRow.getPayload());
String logFileName = StringUtils.format("%s-%s_err.log", triggerConsoleRow.getComponentName()
.replaceAll(ALLOWED_CHARACTERS, UNDERSCORE), payload[2]);
wfDataManagementStorage.addComponentErrorLog(logFile, logFileName, payload[1]);
} catch (ComponentExecutionException e) {
logErrorOnWritingToDMFailure(e, triggerConsoleRow);
}
}
@Override
protected String formatConsoleRow(ConsoleRow consoleRow) {
return consoleRowFormatter.toComponentErrorLogFileFormat(consoleRow);
}
@Override
protected String getFileFormatVersion() {
return VERSION;
}
}
/**
* Writes the {@link ConsoleRow}s to the error log file of a workflow in the RCE temp directory and stores the file in the RCE data
* management on dedicated {@link ConsoleRow}s.
*
* @author Doreen Seider
*/
protected class BactchedWorkflowErrorLogFileWriter extends AbstractBatchedLogFileWriter {
private static final String VERSION = "1.1";
protected BactchedWorkflowErrorLogFileWriter(String exeId) throws IOException {
super(exeId);
}
@Override
protected void storeLogFileInDataManagement(ConsoleRow triggerConsoleRow) {
try {
String logFileName = StringUtils.format("%s-err.log", triggerConsoleRow.getWorkflowName()
.replaceAll(ALLOWED_CHARACTERS, UNDERSCORE));
wfDataManagementStorage.addWorkflowErrorLog(logFile, logFileName);
} catch (WorkflowExecutionException e) {
log.error(StringUtils.format(FAILED_TO_STORE_LOG_FILE_IN_DM,
triggerConsoleRow.getWorkflowName(), triggerConsoleRow.getWorkflowIdentifier(), logFile.getName()), e);
}
}
@Override
protected boolean isTriggerForWritingLogFileToDM(ConsoleRow consoleRow) {
return consoleRow.getType() == ConsoleRow.Type.LIFE_CYCLE_EVENT
&& consoleRow.getPayload().startsWith(ConsoleRow.WorkflowLifecyleEventType.WORKFLOW_LOG_FINISHED.name());
}
@Override
protected String formatConsoleRow(ConsoleRow consoleRow) {
return consoleRowFormatter.toWorkflowErrorLogFileFormat(consoleRow);
}
@Override
protected String getFileFormatVersion() {
return VERSION;
}
}
/**
* Writes the {@link ConsoleRow}s to a log file in the RCE temp directory and stores the file in the RCE data management on dedicated
* {@link ConsoleRow}s.
*
* @author Doreen Seider
*/
protected abstract class AbstractBatchedComponentLogFileWriter extends AbstractBatchedLogFileWriter {
protected AbstractBatchedComponentLogFileWriter(String exeId) throws IOException {
super(exeId);
}
@Override
protected boolean isTriggerForWritingLogFileToDM(ConsoleRow consoleRow) {
return consoleRow.getType() == ConsoleRow.Type.LIFE_CYCLE_EVENT
&& consoleRow.getPayload().startsWith(ConsoleRow.WorkflowLifecyleEventType.COMPONENT_LOG_FINISHED.name());
}
protected void logErrorOnWritingToDMFailure(ComponentExecutionException e, ConsoleRow consoleRow) {
log.error(StringUtils.format(FAILED_TO_STORE_LOG_FILE_IN_DM,
consoleRow.getWorkflowName(), consoleRow.getWorkflowIdentifier(), logFile.getName()), e);
}
}
/**
* Writes the {@link ConsoleRow}s to a log file in the RCE temp directory and stores the file in the RCE data management on dedicated
* {@link ConsoleRow}s.
*
* @author Doreen Seider
*/
protected abstract class AbstractBatchedLogFileWriter implements BatchedConsoleRowsProcessor {
protected static final String FAILED_TO_STORE_LOG_FILE_IN_DM = "Failed to store log file in data management"
+ " - workflow '%s' (%s): %s";
private static final String FAILED_TO_CREATE_TEMPORARY_LOG_FILE = "Failed to create temporary log file: ";
private static final String FAILED_TO_CLEAR_TEMPORARY_LOG_FILE = "Failed to clear temporary log file: ";
private static final String FAILED_TO_DELETE_TEMPORARY_LOG_FILE = "Failed to delete temporary log file: ";
protected final Log log = LogFactory.getLog(getClass());
protected final ConsoleRowFormatter consoleRowFormatter = new ConsoleRowFormatter();
protected File logFile;
private volatile boolean logFileDisposed = false;
private final TempFileService tempFileService;
private final String exeId;
protected AbstractBatchedLogFileWriter(String exeId) throws IOException {
tempFileService = TempFileServiceAccess.getInstance();
logFile = tempFileService.createTempFileFromPattern("*");
this.exeId = exeId;
}
@Override
public void processConsoleRows(ConsoleRow[] consoleRows) {
if (logFileDisposed) {
log.debug(StringUtils.format("Log file '%s' already disposed; ignored %d incoming console row(s)", logFile.getName(),
consoleRows.length));
return;
}
List<String> logFileEntries = new ArrayList<>();
for (ConsoleRow consoleRow : consoleRows) {
if (consoleRow == null) {
if (FileUtils.sizeOf(logFile) != 0) {
printLogFileNotEmptyError(logFile);
}
disposeLogFile();
return;
} else if (isTriggerForWritingLogFileToDM(consoleRow)) {
writeConsoleRowsToFile(logFileEntries);
if (FileUtils.sizeOf(logFile) > 0) {
writeVersionToFile();
storeLogFileInDataManagement(consoleRow);
clearLogFile();
logFileEntries.clear();
}
} else if (isMatchingConsoleRow(consoleRow)) {
logFileEntries.add(formatConsoleRow(consoleRow));
}
}
writeConsoleRowsToFile(logFileEntries);
}
protected abstract boolean isTriggerForWritingLogFileToDM(ConsoleRow consoleRow);
protected boolean isMatchingConsoleRow(ConsoleRow consoleRow) {
return consoleRow.getType() != Type.LIFE_CYCLE_EVENT;
}
protected abstract String formatConsoleRow(ConsoleRow consoleRow);
protected abstract String getFileFormatVersion();
private void printLogFileNotEmptyError(File file) {
String fileContent = "[not available]";
try (FileInputStream inputStream = new FileInputStream(logFile)) {
fileContent = IOUtils.toString(inputStream);
} catch (IOException e) {
log.debug(StringUtils.format("Failed to get content of log file %s; cause: &s", logFile.getAbsolutePath(), e.toString()));
}
log.error(StringUtils.format("Request to dispose non-empty log file (related to %s), means that"
+ " the content get lost as it it was not stored to the data management before; file: %s; size: %d, content: %s",
exeId, file.getAbsolutePath(), FileUtils.sizeOf(file), fileContent));
}
private void writeConsoleRowsToFile(List<String> logFileEntries) {
try {
FileUtils.writeLines(logFile, logFileEntries, true);
} catch (IOException e) {
log.error("Failed to add a console log row to the log file: " + logFile, e);
}
}
private void writeVersionToFile() {
try {
FileUtils.writeStringToFile(logFile, StringUtils.format("[Log file format version: %s]\n", getFileFormatVersion()), true);
} catch (IOException e) {
log.error("Failed to add a console log row to the log file: " + logFile, e);
}
}
private void disposeLogFile() {
try {
tempFileService.disposeManagedTempDirOrFile(logFile);
} catch (IOException e) {
log.error(FAILED_TO_DELETE_TEMPORARY_LOG_FILE + logFile.getAbsolutePath(), e);
}
logFileDisposed = true;
logFilesFlushedAndDisposedLatch.get().countDown();
}
protected abstract void storeLogFileInDataManagement(ConsoleRow triggerConsoleRow);
private void clearLogFile() {
try {
FileUtils.writeStringToFile(logFile, "", false);
} catch (IOException e) {
log.error(FAILED_TO_CLEAR_TEMPORARY_LOG_FILE + logFile.getAbsolutePath(), e);
try {
tempFileService.disposeManagedTempDirOrFile(logFile);
} catch (IOException e1) {
log.error(FAILED_TO_DELETE_TEMPORARY_LOG_FILE + logFile.getAbsolutePath(), e);
} finally {
try {
logFile = tempFileService.createTempFileFromPattern("*");
} catch (IOException e1) {
log.error(FAILED_TO_CREATE_TEMPORARY_LOG_FILE + logFile.getAbsolutePath(), e);
}
}
}
}
}
}