View Javadoc
1   package de.dlr.bt.stc;
2   
3   import java.awt.GraphicsEnvironment;
4   import java.io.IOException;
5   import java.nio.file.FileSystems;
6   import java.util.concurrent.atomic.AtomicBoolean;
7   
8   import org.greenrobot.eventbus.EventBus;
9   import org.slf4j.LoggerFactory;
10  
11  import ch.qos.logback.classic.Logger;
12  import de.dlr.bt.stc.cli.CLIOptions;
13  import de.dlr.bt.stc.init.ModuleInitializer;
14  import de.dlr.bt.stc.init.STCInstance;
15  import de.dlr.bt.stc.init.STCInstance.AddConfigurationFolder;
16  import de.dlr.bt.stc.init.STCInstance.LoadConfiguration;
17  import de.dlr.bt.stc.init.STCInstance.StartEvent;
18  import de.dlr.bt.stc.init.STCInstance.StopEvent;
19  import de.dlr.bt.stc.opcuaserver.UAServer;
20  import lombok.extern.slf4j.Slf4j;
21  import picocli.CommandLine;
22  
23  @Slf4j
24  public class App {
25  
26  	private static final int AUTO_RESTART_TIME_MS = 2500;
27  	private static final int GRACEFUL_TERMINATION_TIMEOUT = 20000;
28  
29  	private static final AtomicBoolean mainThreadFinished = new AtomicBoolean(false);
30  
31  	public static void main(String[] args) {
32  
33  		log.info("Starting ...");
34  
35  		ModuleInitializer.initialize();
36  
37  		final var managementEventBus = new EventBus();
38  
39  		CLIOptions options = new CLIOptions();
40  		CommandLine cmd = new CommandLine(options);
41  		cmd.parseArgs(args);
42  
43  		if (options.isUsageHelpRequested()) {
44  			cmd.usage(System.out);
45  			return;
46  		}
47  
48  		final boolean isHeadless;
49  		if (options.isForceHeadless()) {
50  			isHeadless = true;
51  		} else {
52  			isHeadless = GraphicsEnvironment.isHeadless();
53  		}
54  
55  		if (options.getLogLevel() != null) {
56  			Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
57  			rootLogger.setLevel(options.getLogLevel());
58  		}
59  
60  		UAServer uaserver = null;
61  		if (options.isRunOpcuaServer()) {
62  			try {
63  				uaserver = new UAServer(options.getOpcuaServerPort(), managementEventBus);
64  				uaserver.startup().get();
65  			} catch (InterruptedException ie) {
66  				Thread.currentThread().interrupt();
67  			} catch (Exception ex) {
68  				log.error("Failed to start OPC/UA server!");
69  			}
70  		}
71  
72  		STCInstance instance = new STCInstance(managementEventBus);
73  
74  		managementEventBus
75  				.post(new AddConfigurationFolder(FileSystems.getDefault().getPath(options.getConfigFolder())));
76  		managementEventBus.post(new LoadConfiguration());
77  		managementEventBus.post(new StartEvent(AUTO_RESTART_TIME_MS));
78  
79  		Runtime.getRuntime().addShutdownHook(new Thread(() -> terminateApplication(managementEventBus)));
80  
81  		log.info("System started, send SIGTERM or SIGINT to stop (Ctrl+C)");
82  
83  		if (!isHeadless) {
84  			Thread consoleInput = new Thread(() -> waitForConsoleQuit(instance, managementEventBus),
85  					"ConsoleStdInThread");
86  			consoleInput.setDaemon(true);
87  			consoleInput.start();
88  		}
89  
90  		// Hand over main thread to STCInstance, blocking until stop is requested
91  		instance.run();
92  
93  		try {
94  			if (uaserver != null)
95  				uaserver.shutdown().get();
96  		} catch (InterruptedException ie) {
97  			Thread.currentThread().interrupt();
98  		} catch (Exception ex) {
99  			log.error("Failed to stop OPC/UA server!");
100 		}
101 		log.info("sTC finished");
102 
103 		mainThreadFinished.set(true);
104 		synchronized (mainThreadFinished) {
105 			mainThreadFinished.notifyAll();
106 		}
107 	}
108 
109 	private static void waitForConsoleQuit(STCInstance instance, EventBus managementBus) {
110 		log.info("Started, press e (followed by enter) to stop");
111 		while (!instance.isTerminate()) {
112 			try {
113 				int chr = System.in.read();
114 				if (chr == 'e') {
115 					terminateApplication(managementBus);
116 				}
117 			} catch (IOException ioe) {
118 			}
119 		}
120 	}
121 
122 	/**
123 	 * Terminates the application. A signal is sent to the management event bus for
124 	 * graceful termination. If the main thread is still alive after
125 	 * {@link #GRACEFUL_TERMINATION_TIMEOUT} ms, the JVM will be killed
126 	 * unconditionally and ungracefully.
127 	 * 
128 	 * @param managementBus The management event bus.
129 	 */
130 	private static void terminateApplication(EventBus managementBus) {
131 		// Do nothing after we have already gracefully terminated
132 		if (mainThreadFinished.get())
133 			return;
134 
135 		// Post the stop event
136 		managementBus.post(new StopEvent(true));
137 
138 		// Wait for graceful termination
139 		try {
140 			var startTime = System.currentTimeMillis();
141 			while (!mainThreadFinished.get() && System.currentTimeMillis() - startTime < GRACEFUL_TERMINATION_TIMEOUT) {
142 				synchronized (mainThreadFinished) {
143 					mainThreadFinished.wait(500);
144 				}
145 			}
146 
147 			// Main thread should have finished by now. If not, terminate the JVM
148 			// unconditionally
149 
150 			if (!mainThreadFinished.get()) {
151 				log.error("The application did not terminate within {} ms. Forcefully terminating now ...",
152 						GRACEFUL_TERMINATION_TIMEOUT);
153 				Runtime.getRuntime().halt(-1);
154 			}
155 		} catch (InterruptedException ie) {
156 			log.error("Final wait has been interrupted");
157 			Thread.currentThread().interrupt();
158 		}
159 	}
160 
161 }