/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.scripting.python; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.Writer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallbackExceptionPolicy; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncOrderedExecutionQueue; /** * A specialized {@link Writer} for capturing the output of Jython, which does not seem to call {@link Writer#close()} properly. It relies * on the fact that Jython calls {@link Writer#flush()} at the end of each line. Each line is forwarded to the appropriate notification * call. If {@link Writer#close()} is called from own code, {@link #CONSOLE_END} must be written at the end of the console output to forward * as {@link PythonOutputWriter#close()} waits until this line is captured. This line won't be forwarded. * * @author Robert Mischke * @author Sascha Zur * @author Doreen Seider */ public abstract class PythonOutputWriter extends Writer { /** * Indicates the last console line sent. Must be sent by each consumer after the last productive line. */ public static final String CONSOLE_END = "c02abd1c-67bc-4974-902b-439cd2b14efc"; /** * Timeout used when waiting for {@link #CONSOLE_END}. */ public static final int WAIT_FOR_CONSOLE_END_TIMEOUT_IN_MILI_SEC = 5000; protected final AsyncOrderedExecutionQueue executionQueue; private final Log log = LogFactory.getLog(getClass()); private final StringBuilder buffer = new StringBuilder(); private FileOutputStream logFileStream; private final Object logFileLock = new Object(); private boolean isClosed; private final CountDownLatch finishLatch = new CountDownLatch(1); public PythonOutputWriter(Object lock, File logFile) { super(lock); isClosed = false; // redundant, but added to make it explicit executionQueue = ConcurrencyUtils.getFactory().createAsyncOrderedExecutionQueue(AsyncCallbackExceptionPolicy.LOG_AND_PROCEED); if (logFile != null) { try { logFileStream = new FileOutputStream(logFile); } catch (FileNotFoundException e) { LogFactory.getLog(getClass()).error("Creating stream for given log file failed", e); } } } protected abstract void onNewLineToForward(final String line); @Override public void write(char[] cbuf, int off, int len) throws IOException { synchronized (lock) { buffer.append(cbuf, off, len); } } @Override public void flush() throws IOException { synchronized (lock) { String line = buffer.toString(); buffer.setLength(0); if (isClosed) { if (!line.isEmpty()) { log.warn("Unexpected caller behavior: flush() called after close(); captured output=" + line); } } else { if (line.contains(CONSOLE_END)) { // not forwarded as an empty line would be forwarded as well, which is generated by sys.stderr/stdout.flush() for some // reason. we call both of the commands at the end of each Jython script // forwardLineAsConsoleRow(compInfo, line.replace(CONSOLE_END, "")); isClosed = true; forwardLine(null); finishLatch.countDown(); } else { forwardLine(line); } } } } protected void forwardLine(final String line) { executionQueue.enqueue(new Runnable() { @Override public void run() { synchronized (logFileLock) { if (logFileStream != null && line != null) { try { logFileStream.write(line.getBytes()); } catch (IOException e) { log.error("Writing output to file failed", e); } } } onNewLineToForward(line); } }); } @Override public void close() throws IOException { try { finishLatch.await(WAIT_FOR_CONSOLE_END_TIMEOUT_IN_MILI_SEC, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error( "Waiting for last console line was interrupted. Writer instance will be closed immediately. Console lines might be lost"); } synchronized (logFileLock) { if (logFileStream != null) { logFileStream.close(); logFileStream = null; } } synchronized (lock) { isClosed = true; } } }