/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.component.workflow.execution.headless.api;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.core.communication.spi.CallbackMethod;
import de.rcenvironment.core.component.execution.api.ConsoleRow;
import de.rcenvironment.core.component.workflow.execution.api.WorkflowState;
import de.rcenvironment.core.component.workflow.execution.headless.internal.ExtendedHeadlessWorkflowExecutionContext;
import de.rcenvironment.core.component.workflow.execution.internal.ConsoleRowFormatter;
import de.rcenvironment.core.notification.DefaultNotificationSubscriber;
import de.rcenvironment.core.notification.Notification;
import de.rcenvironment.core.notification.NotificationSubscriber;
import de.rcenvironment.core.utils.common.StringUtils;
/**
* Subscriber for ConsoleRow events, including workflow life-cycle events.
*
* Note: this subscriber should work for remote subscription now, but this was no tested yet
*
* @author Sascha Zur
* @author Robert Mischke
* @author Doreen Seider
*/
public class ConsoleRowSubscriber extends DefaultNotificationSubscriber implements Closeable {
private static final String LOG_FILE_SUFFIX = ".log";
private static final String TEMP_FILE_SUFFIX = ".tmp";
private static final long serialVersionUID = 1233786794388388297L;
private final transient File finalLogFileDestination;
private final transient File tempLogFileLocation;
private final transient ExtendedHeadlessWorkflowExecutionContext workflowExecutionContext;
private final transient Log log = LogFactory.getLog(getClass());
private final transient ConsoleRowFormatter consoleRowFormatter = new ConsoleRowFormatter();
private transient BufferedWriter bufferedLogWriter;
public ConsoleRowSubscriber(ExtendedHeadlessWorkflowExecutionContext context, File logDirectory) {
this.workflowExecutionContext = context;
String consoleLogName = "workflow";
finalLogFileDestination = new File(logDirectory, consoleLogName + LOG_FILE_SUFFIX);
if (finalLogFileDestination.exists()) {
log.warn("Log file does already exists; overwriting: " + finalLogFileDestination.getAbsolutePath());
finalLogFileDestination.delete();
if (finalLogFileDestination.exists()) {
log.warn("Failed to delete existing log file " + finalLogFileDestination.getAbsolutePath());
}
}
tempLogFileLocation = new File(logDirectory, consoleLogName + LOG_FILE_SUFFIX + TEMP_FILE_SUFFIX);
if (!tempLogFileLocation.exists()) {
try {
tempLogFileLocation.createNewFile();
} catch (IOException e) {
Logger.getAnonymousLogger().warning("Could not create " + tempLogFileLocation.getAbsolutePath());
}
}
bufferedLogWriter = setupLogWriter(tempLogFileLocation);
}
@Override
public Class<?> getInterface() {
return NotificationSubscriber.class;
}
@Override
@CallbackMethod
public void processNotification(Notification n) {
// String notificationId = n.getHeader().getNotificationIdentifier();
// log.debug("Received console row notification, id=" + notificationId);
Serializable body = n.getBody();
if (!(body instanceof ConsoleRow)) {
log.error("Unexpected notification type on ConsoleRow channel: body class is " + body.getClass());
return;
}
ConsoleRow row = (ConsoleRow) body;
// TODO this synchronizes for each single notification and blocks the caller; improve
// for performance - misc_ro
synchronized (this) {
if (bufferedLogWriter != null) {
try {
// bufferedLogWriter.write((notificationId + ": " + body) +
bufferedLogWriter.write(consoleRowFormatter.toSingleWorkflowLogFileFormat(row));
} catch (IOException e) {
log.error("Closing log file " + tempLogFileLocation
+ " after error; the file will not be moved to its final location", e);
IOUtils.closeQuietly(bufferedLogWriter);
bufferedLogWriter = null;
}
} else {
if (row.getType() == ConsoleRow.Type.LIFE_CYCLE_EVENT
&& (row.getPayload().equals(StringUtils.escapeAndConcat(
ConsoleRow.WorkflowLifecyleEventType.NEW_STATE.name(), WorkflowState.DISPOSING.name()))
|| row.getPayload().equals(StringUtils.escapeAndConcat(
ConsoleRow.WorkflowLifecyleEventType.NEW_STATE.name(), WorkflowState.DISPOSED.name())))) {
return;
}
// should not occur
log.warn("Workflow log writer is already closed; ignoring event " + row.getType() + ":" + row.getPayload());
}
}
if (row.getType() == ConsoleRow.Type.LIFE_CYCLE_EVENT) {
log.debug("Received workflow life-cycle event: " + row.getPayload());
if (ConsoleRow.WorkflowLifecyleEventType.WORKFLOW_FINISHED.name().equals(row.getPayload())) {
workflowExecutionContext.reportConsoleOutputTerminated();
}
}
workflowExecutionContext.reportConsoleRowReceived(row);
}
@Override
public void close() {
synchronized (this) {
if (bufferedLogWriter != null) {
log.debug("Closing temporary log file " + tempLogFileLocation);
try {
bufferedLogWriter.close();
if (tempLogFileLocation.renameTo(finalLogFileDestination)) {
log.debug("Completed workflow log file " + finalLogFileDestination);
} else {
log.warn("Failed to move log file " + tempLogFileLocation + " to final destination " + finalLogFileDestination);
}
} catch (IOException e) {
log.warn("Exception while closing log file " + tempLogFileLocation, e);
}
bufferedLogWriter = null;
} else {
// should not occur
log.warn("close() called on already-closed workflow log writer");
}
}
}
private BufferedWriter setupLogWriter(File logFile) {
BufferedWriter fw = null;
try {
fw = new BufferedWriter(new FileWriter(logFile));
} catch (IOException e) {
log.error("Failed to open log file " + logFile + " for writing", e);
}
return fw;
}
}