App.java
package de.dlr.bt.stc;
import java.awt.GraphicsEnvironment;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.util.concurrent.atomic.AtomicBoolean;
import org.greenrobot.eventbus.EventBus;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import de.dlr.bt.stc.cli.CLIOptions;
import de.dlr.bt.stc.init.ModuleInitializer;
import de.dlr.bt.stc.init.STCInstance;
import de.dlr.bt.stc.init.STCInstance.AddConfigurationFolder;
import de.dlr.bt.stc.init.STCInstance.LoadConfiguration;
import de.dlr.bt.stc.init.STCInstance.StartEvent;
import de.dlr.bt.stc.init.STCInstance.StopEvent;
import de.dlr.bt.stc.opcuaserver.UAServer;
import lombok.extern.slf4j.Slf4j;
import picocli.CommandLine;
@Slf4j
public class App {
private static final int AUTO_RESTART_TIME_MS = 2500;
private static final int GRACEFUL_TERMINATION_TIMEOUT = 20000;
private static final AtomicBoolean mainThreadFinished = new AtomicBoolean(false);
public static void main(String[] args) {
log.info("Starting ...");
ModuleInitializer.initialize();
final var managementEventBus = new EventBus();
CLIOptions options = new CLIOptions();
CommandLine cmd = new CommandLine(options);
cmd.parseArgs(args);
if (options.isUsageHelpRequested()) {
cmd.usage(System.out);
return;
}
final boolean isHeadless;
if (options.isForceHeadless()) {
isHeadless = true;
} else {
isHeadless = GraphicsEnvironment.isHeadless();
}
if (options.getLogLevel() != null) {
Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(options.getLogLevel());
}
UAServer uaserver = null;
if (options.isRunOpcuaServer()) {
try {
uaserver = new UAServer(options.getOpcuaServerPort(), managementEventBus);
uaserver.startup().get();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
} catch (Exception ex) {
log.error("Failed to start OPC/UA server!");
}
}
STCInstance instance = new STCInstance(managementEventBus);
managementEventBus
.post(new AddConfigurationFolder(FileSystems.getDefault().getPath(options.getConfigFolder())));
managementEventBus.post(new LoadConfiguration());
managementEventBus.post(new StartEvent(AUTO_RESTART_TIME_MS));
Runtime.getRuntime().addShutdownHook(new Thread(() -> terminateApplication(managementEventBus)));
log.info("System started, send SIGTERM or SIGINT to stop (Ctrl+C)");
if (!isHeadless) {
Thread consoleInput = new Thread(() -> waitForConsoleQuit(instance, managementEventBus),
"ConsoleStdInThread");
consoleInput.setDaemon(true);
consoleInput.start();
}
// Hand over main thread to STCInstance, blocking until stop is requested
instance.run();
try {
if (uaserver != null)
uaserver.shutdown().get();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
} catch (Exception ex) {
log.error("Failed to stop OPC/UA server!");
}
log.info("sTC finished");
mainThreadFinished.set(true);
synchronized (mainThreadFinished) {
mainThreadFinished.notifyAll();
}
}
private static void waitForConsoleQuit(STCInstance instance, EventBus managementBus) {
log.info("Started, press e (followed by enter) to stop");
while (!instance.isTerminate()) {
try {
int chr = System.in.read();
if (chr == 'e') {
terminateApplication(managementBus);
}
} catch (IOException ioe) {
}
}
}
/**
* Terminates the application. A signal is sent to the management event bus for
* graceful termination. If the main thread is still alive after
* {@link #GRACEFUL_TERMINATION_TIMEOUT} ms, the JVM will be killed
* unconditionally and ungracefully.
*
* @param managementBus The management event bus.
*/
private static void terminateApplication(EventBus managementBus) {
// Do nothing after we have already gracefully terminated
if (mainThreadFinished.get())
return;
// Post the stop event
managementBus.post(new StopEvent(true));
// Wait for graceful termination
try {
var startTime = System.currentTimeMillis();
while (!mainThreadFinished.get() && System.currentTimeMillis() - startTime < GRACEFUL_TERMINATION_TIMEOUT) {
synchronized (mainThreadFinished) {
mainThreadFinished.wait(500);
}
}
// Main thread should have finished by now. If not, terminate the JVM
// unconditionally
if (!mainThreadFinished.get()) {
log.error("The application did not terminate within {} ms. Forcefully terminating now ...",
GRACEFUL_TERMINATION_TIMEOUT);
Runtime.getRuntime().halt(-1);
}
} catch (InterruptedException ie) {
log.error("Final wait has been interrupted");
Thread.currentThread().interrupt();
}
}
}