diff --git a/module/communication/controller/build.gradle.kts b/module/communication/controller/build.gradle.kts index aa4bcc9ef..e1c39cd21 100644 --- a/module/communication/controller/build.gradle.kts +++ b/module/communication/controller/build.gradle.kts @@ -18,10 +18,7 @@ dependencies { api(project(":jul.interface")) api(project(":jul.schedule")) api(project(":jul.pattern.controller")) - testImplementation("org.testcontainers:junit-jupiter:_") { - exclude(group = "junit", module = "junit") - } - testImplementation("io.quarkus:quarkus-junit4-mock:_") // required as long as testcontainers depends on junit4 + testApi(project(":jul.communication.mqtt.test")) } description = "JUL Extension Controller" diff --git a/module/communication/controller/src/main/java/org/openbase/jul/communication/controller/AbstractControllerServer.java b/module/communication/controller/src/main/java/org/openbase/jul/communication/controller/AbstractControllerServer.java index a1f235da7..45218eb4c 100644 --- a/module/communication/controller/src/main/java/org/openbase/jul/communication/controller/AbstractControllerServer.java +++ b/module/communication/controller/src/main/java/org/openbase/jul/communication/controller/AbstractControllerServer.java @@ -115,7 +115,6 @@ public abstract class AbstractControllerServer. - * #L% - */ - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.openbase.jps.core.JPService; -import org.openbase.jps.exception.JPServiceException; -import org.openbase.jul.communication.jp.JPComHost; -import org.openbase.jul.communication.jp.JPComPort; -import org.openbase.jul.communication.mqtt.SharedMqttClient; -import org.testcontainers.containers.BindMode; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.Arrays; - -public class MqttIntegrationTest { - - public static final int port = 1883; - public static Path mosquittoConfig; - public static GenericContainer broker; - - @BeforeAll - public static void setUpClass() throws JPServiceException, IOException { - mosquittoConfig = Files.createTempFile("mosquitto_", ".conf"); - Files.write(mosquittoConfig, Arrays.asList( - "allow_anonymous true", - "listener "+port) - ); - - broker = new GenericContainer<>(DockerImageName.parse("eclipse-mosquitto")) - .withExposedPorts(port) - .withFileSystemBind( - mosquittoConfig.toString(), - "/mosquitto/config/mosquitto.conf", - BindMode.READ_ONLY - ); - broker.withStartupTimeout(Duration.ofSeconds(30)).start(); - - JPService.registerProperty(JPComPort.class, broker.getFirstMappedPort()); - JPService.registerProperty(JPComHost.class, broker.getHost()); - JPService.setupJUnitTestMode(); - } - - @AfterAll - public static void tearDownClass() throws IOException { - SharedMqttClient.INSTANCE.waitForShutdown(); - broker.stop(); - Files.delete(mosquittoConfig); - } -} diff --git a/module/communication/mqtttest/build.gradle.kts b/module/communication/mqtttest/build.gradle.kts new file mode 100644 index 000000000..a1b564a04 --- /dev/null +++ b/module/communication/mqtttest/build.gradle.kts @@ -0,0 +1,29 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This project uses @Incubating APIs which are subject to change. + */ + +plugins { + id("org.openbase.jul") +} + +dependencies { + api(project(":jul.communication")) + api(project(":jul.schedule")) + api(project(":jul.extension.type.processing")) + api(project(":jul.communication.mqtt")) + api("com.hivemq:hivemq-mqtt-client:_") + api("org.testcontainers:junit-jupiter:_") { + exclude(group = "junit", module = "junit") + } + api("io.quarkus:quarkus-junit4-mock:_") + api(Testing.junit.jupiter) + api(Testing.junit.jupiter.api) +} + +description = "JUL Extension MQTT Test" + +java { + withJavadocJar() +} diff --git a/module/communication/mqtttest/src/main/kotlin/org/openbase/jul/communication/mqtt/test/MqttIntegrationTest.kt b/module/communication/mqtttest/src/main/kotlin/org/openbase/jul/communication/mqtt/test/MqttIntegrationTest.kt new file mode 100644 index 000000000..5f403a357 --- /dev/null +++ b/module/communication/mqtttest/src/main/kotlin/org/openbase/jul/communication/mqtt/test/MqttIntegrationTest.kt @@ -0,0 +1,95 @@ +package org.openbase.jul.communication.mqtt.test +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestInstance +import org.openbase.jps.core.JPService +import org.openbase.jps.exception.JPServiceException +import org.openbase.jul.communication.jp.JPComHost +import org.openbase.jul.communication.jp.JPComPort +import org.openbase.jul.communication.mqtt.SharedMqttClient.waitForShutdown +import org.testcontainers.containers.BindMode +import org.testcontainers.containers.GenericContainer +import org.testcontainers.utility.DockerImageName +import java.nio.file.Files +import java.nio.file.Path +import java.time.Duration +import java.util.* + +/*- + * #%L + * JUL Extension Controller + * %% + * Copyright (C) 2015 - 2021 openbase.org + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * . + * #L% + + * */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +open class MqttIntegrationTest { + + companion object { + const val port = 1884 + var mosquittoConfig: Path? = null + var broker: GenericContainer<*>? = null + val configLock = Any() + } + + @BeforeAll + fun setupMqtt() { + synchronized(configLock) { + mosquittoConfig = Files.createTempFile("mosquitto_", ".conf") + Files.write( + mosquittoConfig, listOf( + "allow_anonymous true", + "listener " + port + ) + ) + GenericContainer(DockerImageName.parse("eclipse-mosquitto")) + .withExposedPorts(port) + .withFileSystemBind( + mosquittoConfig.toString(), + "/mosquitto/config/mosquitto.conf", + BindMode.READ_ONLY + ) + .apply { withStartupTimeout(Duration.ofSeconds(30)).start() } + .also { if (broker?.takeIf { it.containerId != null } != null) + error("broker was already initialized!") } + .also { broker = it } + .also { setupProperties() } + } + } + + @AfterAll + fun tearDownMQTT() { + synchronized(configLock) { + waitForShutdown() + broker?.stop() + Files.delete(mosquittoConfig) + } + } + + @Throws(JPServiceException::class) + private fun setupProperties() { + JPService.reset() + JPService.registerProperty(JPComPort::class.java, broker!!.firstMappedPort) + JPService.registerProperty(JPComHost::class.java, broker!!.host) + setupCustomProperties() + JPService.setupJUnitTestMode() + } + + open fun setupCustomProperties() {} +} diff --git a/module/exception/src/main/java/org/openbase/jul/exception/InitializationException.java b/module/exception/src/main/java/org/openbase/jul/exception/InitializationException.java index e815c9f9b..3d1055055 100644 --- a/module/exception/src/main/java/org/openbase/jul/exception/InitializationException.java +++ b/module/exception/src/main/java/org/openbase/jul/exception/InitializationException.java @@ -50,10 +50,10 @@ public InitializationException(Object context, Throwable cause) { /** * Creates a new InitializationException instance. * - * @param context the class which could not be initialized. + * @param clazz the class which could not be initialized. * @param cause the reason why the initialization failed. */ - public InitializationException(Class context, Throwable cause) { - super("Could not initialize " + context + "!", cause); + public InitializationException(Class clazz, Throwable cause) { + super("Could not initialize " + clazz.getSimpleName() + "!", cause); } } diff --git a/module/exception/src/main/java/org/openbase/jul/exception/RedundantExecutionException.kt b/module/exception/src/main/java/org/openbase/jul/exception/RedundantExecutionException.kt new file mode 100644 index 000000000..7510332af --- /dev/null +++ b/module/exception/src/main/java/org/openbase/jul/exception/RedundantExecutionException.kt @@ -0,0 +1,5 @@ +package org.openbase.jul.exception + +class RedundantExecutionException( + context: String +): VerificationFailedException("Redundant execution of $context detected!") diff --git a/module/exception/src/main/java/org/openbase/jul/exception/ShutdownInProgressException.java b/module/exception/src/main/java/org/openbase/jul/exception/ShutdownInProgressException.java index 1d6af66d0..454a69222 100644 --- a/module/exception/src/main/java/org/openbase/jul/exception/ShutdownInProgressException.java +++ b/module/exception/src/main/java/org/openbase/jul/exception/ShutdownInProgressException.java @@ -41,6 +41,17 @@ public ShutdownInProgressException(final Object service) { super(service + " shutdown in progress!"); } + /** + * {@inheritDoc} + * + * @param message {@inheritDoc} The reason of the shutdown. + * + * Note: The given service should provide a proper toString() method. + */ + public ShutdownInProgressException(final String message) { + super(message); + } + /** * {@inheritDoc} * diff --git a/module/exception/src/main/java/org/openbase/jul/exception/printer/ExceptionPrinter.java b/module/exception/src/main/java/org/openbase/jul/exception/printer/ExceptionPrinter.java index bb90e62f8..04cd6d86e 100644 --- a/module/exception/src/main/java/org/openbase/jul/exception/printer/ExceptionPrinter.java +++ b/module/exception/src/main/java/org/openbase/jul/exception/printer/ExceptionPrinter.java @@ -32,9 +32,7 @@ import org.openbase.jps.exception.JPServiceException; import org.openbase.jps.preset.JPLogLevel; import org.openbase.jps.preset.JPVerbose; -import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.exception.FatalImplementationErrorException; -import org.openbase.jul.exception.MultiException; +import org.openbase.jul.exception.*; import org.openbase.jul.exception.MultiException.SourceExceptionEntry; import org.slf4j.Logger; @@ -181,10 +179,6 @@ public static void printHistory(final T th, final Logger l */ public static void printHistoryAndExit(final T th, final Logger logger) { printHistory(th, logger, LogLevel.ERROR); - if (JPService.testMode()) { - assert false; - return; - } exit(255); } @@ -213,10 +207,6 @@ public static void printHistory(final String message, T th */ public static void printHistoryAndExit(final String message, T th, final Logger logger) { printHistory(new CouldNotPerformException(message, th), logger, LogLevel.ERROR); - if (JPService.testMode()) { - assert false; - return; - } exit(255); } @@ -274,18 +264,16 @@ public static void printHistory(final String message, fina */ public static void printHistoryAndExit(final String message, final T th, final PrintStream stream) { printHistory(new CouldNotPerformException(message, th), new SystemPrinter(stream)); - if (JPService.testMode()) { - assert false; - return; - } exit(255); } private static void exit(final int errorCode) { - if (JPService.testMode()) { - throw new RuntimeException("System exit called in test mode!"); + // skip in tests since it would interfere with the entire test framework. + if (!JPService.testMode()) { + System.exit(errorCode); } - System.exit(errorCode); + + throw new RuntimeException(new ShutdownInProgressException("Shutdown with error code "+ errorCode + " initiated!")); } /** diff --git a/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/BuilderSyncSetup.java b/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/BuilderSyncSetup.java index 9c4a27ef0..cb09ae31d 100644 --- a/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/BuilderSyncSetup.java +++ b/module/extension/protobuf/src/main/java/org/openbase/jul/extension/protobuf/BuilderSyncSetup.java @@ -266,10 +266,17 @@ public void unlockWrite(final NotificationStrategy notificationStrategy) { } catch (InterruptedException ex) { Thread.currentThread().interrupt(); return; + } catch (NotInitializedException ex) { + // do nothing if service is not initialized yet } catch (CouldNotPerformException ex) { // only print error if the exception was not caused by a system shutdown. if (!ExceptionProcessor.isCausedBySystemShutdown(ex)) { - ExceptionPrinter.printHistory(new CouldNotPerformException("Could not inform builder holder about data update!", ex), logger, LogLevel.ERROR); + ExceptionPrinter.printHistory( + "Could not inform builder holder about data update!", + ex, + logger, + LogLevel.ERROR + ); } } } diff --git a/module/pattern/launch/build.gradle.kts b/module/pattern/launch/build.gradle.kts index 4c3caf59f..4bd9a76c0 100644 --- a/module/pattern/launch/build.gradle.kts +++ b/module/pattern/launch/build.gradle.kts @@ -11,6 +11,7 @@ plugins { dependencies { api(project(":jul.interface")) api(project(":jul.communication.controller")) + testApi(project(":jul.communication.mqtt.test")) } description = "JUL Pattern Launch" diff --git a/module/pattern/launch/src/main/java/org/openbase/jul/pattern/launch/AbstractLauncher.java b/module/pattern/launch/src/main/java/org/openbase/jul/pattern/launch/AbstractLauncher.java index c8e44ccb9..4602588a4 100644 --- a/module/pattern/launch/src/main/java/org/openbase/jul/pattern/launch/AbstractLauncher.java +++ b/module/pattern/launch/src/main/java/org/openbase/jul/pattern/launch/AbstractLauncher.java @@ -10,12 +10,12 @@ * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Lesser Public License for more details. - * + * * You should have received a copy of the GNU General Lesser Public * License along with this program. If not, see * . @@ -29,11 +29,11 @@ import org.openbase.jps.core.JPService; import org.openbase.jps.exception.JPNotAvailableException; import org.openbase.jul.communication.controller.AbstractIdentifiableController; -import org.openbase.jul.communication.controller.RPCUtils; import org.openbase.jul.communication.iface.RPCServer; import org.openbase.jul.exception.InstantiationException; import org.openbase.jul.exception.*; import org.openbase.jul.exception.printer.ExceptionPrinter; +import org.openbase.jul.extension.protobuf.ClosableDataBuilder; import org.openbase.jul.extension.type.processing.ScopeProcessor; import org.openbase.jul.iface.Launchable; import org.openbase.jul.iface.VoidInitializable; @@ -41,12 +41,17 @@ import org.openbase.jul.pattern.Launcher; import org.openbase.jul.pattern.launch.jp.*; import org.openbase.jul.processing.StringProcessor; +import org.openbase.jul.schedule.CloseableWriteLockWrapper; import org.openbase.jul.schedule.FutureProcessor; import org.openbase.jul.schedule.GlobalCachedExecutorService; import org.openbase.jul.schedule.SyncObject; -import org.openbase.type.domotic.state.ActivationStateType.ActivationState; +import org.openbase.type.domotic.state.ConnectionStateType.ConnectionState.State; +import org.openbase.type.execution.LauncherDataType.LauncherData; +import org.openbase.type.execution.LauncherDataType.LauncherData.Builder; +import org.openbase.type.execution.LauncherDataType.LauncherData.LauncherState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.*; @@ -59,22 +64,25 @@ * * @author Divine Threepwood */ -public abstract class AbstractLauncher extends AbstractIdentifiableController implements Launcher, VoidInitializable, NameProvider { - - protected final Logger logger = LoggerFactory.getLogger(getClass()); +public abstract class +AbstractLauncher extends AbstractIdentifiableController implements Launcher, VoidInitializable, NameProvider { public static final long LAUNCHER_TIMEOUT = 60000; public static final String SCOPE_PREFIX_LAUNCHER = ScopeProcessor.COMPONENT_SEPARATOR + "launcher"; - + private static final List> waitingTaskList = new ArrayList<>(); + private static final SyncObject VERIFICATION_STACK_LOCK = new SyncObject("VerificationStackLock"); + private static final SyncObject ERROR_STACK_LOCK = new SyncObject("ErrorStackLock"); + private static final SyncObject WAITING_TASK_LIST_LOCK = new SyncObject("WaitingTaskLock"); + private static MultiException.ExceptionStack errorExceptionStack = null; + private static MultiException.ExceptionStack verificationExceptionStack = null; + protected final Logger logger = LoggerFactory.getLogger(getClass()); private final Class launchableClass; private final Class applicationClass; private L launchable; private long launchTime = -1; - private LauncherState state; private boolean verified; private VerificationFailedException verificationFailedException; - - private Future launcherTask; + volatile private Future launcherTask; /** * Constructor prepares the launcher and registers already a shutdown hook. @@ -88,209 +96,11 @@ public abstract class AbstractLauncher extends AbstractIde * @throws org.openbase.jul.exception.InstantiationException */ public AbstractLauncher(final Class applicationClass, final Class launchableClass) throws InstantiationException { - super(ActivationState.newBuilder()); + super(LauncherData.newBuilder()); this.launchableClass = launchableClass; this.applicationClass = applicationClass; } - @Override - public void init() throws InitializationException, InterruptedException { - super.init(SCOPE_PREFIX_LAUNCHER + ScopeProcessor.COMPONENT_SEPARATOR + ScopeProcessor.convertIntoValidScopeComponent(getName())); - } - - - /** - * This method can be overwritten to identify a core launcher. - * This means that if the start of this launcher fails the whole launching - * process will be stopped. - * - * @return false, can be overwritten to return true - */ - public boolean isCoreLauncher() { - return false; - } - - public L getLaunchable() { - return launchable; - } - - /** - * Method returns the application name. - *

- * By default the application name is the name of the given application class name. - * - * @return the name as string. - */ - @Override - public String getName() { - return generateName(); - } - - /** - * Method creates a launchable instance without any arguments.. In case the launchable needs arguments you can overwrite this method and instantiate the launchable by ourself. - * - * @return the new instantiated launchable. - * - * @throws CouldNotPerformException is thrown in case the launchable could not properly be instantiated. - */ - protected L instantiateLaunchable() throws CouldNotPerformException { - try { - return launchableClass.getConstructor().newInstance(); - } catch (java.lang.InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { - throw new CouldNotPerformException("Could not load launchable class!", ex); - } - } - - // Load application specific java properties. - protected abstract void loadProperties(); - - /** - * Method verifies a running application. - * - * @throws VerificationFailedException is thrown if the application is started with any restrictions. - * @throws InterruptedException is thrown if the verification process is externally interrupted. - */ - protected void verify() throws VerificationFailedException, InterruptedException { - // overwrite for verification. - } - - private final SyncObject LAUNCHER_LOCK = new SyncObject(this); - - public enum LauncherState { - - INITIALIZING, - LAUNCHING, - RUNNING, - STOPPING, - STOPPED, - ERROR, - INTERRUPTED - } - - private void setState(final LauncherState state) { - this.state = state; - } - - @Override - public Future launch() { - - if (launcherTask != null && !launcherTask.isDone()) { - return FutureProcessor.canceledFuture(Void.class, new InvalidStateException("Could not launch " + getName() + "! Application still running!")); - } - - launcherTask = GlobalCachedExecutorService.submit(() -> { - synchronized (LAUNCHER_LOCK) { - setState(LauncherState.INITIALIZING); - launchable = instantiateLaunchable(); - try { - launchable.init(); - setState(LauncherState.LAUNCHING); - launchable.activate(); - launchTime = System.currentTimeMillis(); - setState(LauncherState.RUNNING); - try { - verify(); - verified = true; - } catch (VerificationFailedException ex) { - verified = false; - verificationFailedException = ex; - } - } catch (InterruptedException ex) { - setState(LauncherState.INTERRUPTED); - return null; - } catch (Exception ex) { - setState(LauncherState.ERROR); - launchable.shutdown(); - if (!ExceptionProcessor.isCausedBySystemShutdown(ex)) { - ExceptionPrinter.printHistoryAndReturnThrowable(new CouldNotPerformException("Could not launch " + getName(), ex), logger); - } - } - return null; - } - }); - return launcherTask; - } - - @Override - public void relaunch() throws CouldNotPerformException, InterruptedException { - synchronized (LAUNCHER_LOCK) { - stop(); - try { - launch().get(); - } catch (ExecutionException | CancellationException ex) { - throw new CouldNotPerformException(ex); - } - } - } - - @Override - public void stop() { - - interruptBoot(); - - synchronized (LAUNCHER_LOCK) { - setState(LauncherState.STOPPING); - if (launchable != null) { - launchable.shutdown(); - } - setState(LauncherState.STOPPED); - } - } - - /** - * Method cancels the boot process of this launcher. - */ - private void interruptBoot() { - if (isBooting()) { - launcherTask.cancel(true); - } - } - - /** - * @return true if the launcher is currently booting. - */ - private boolean isBooting() { - return launcherTask != null && !launcherTask.isDone(); - } - - @Override - public void shutdown() { - stop(); - super.shutdown(); - } - - @Override - public long getUpTime() { - if (launchTime < 0) { - return 0; - } - return (System.currentTimeMillis() - launchTime); - } - - @Override - public long getLaunchTime() { - return launchTime; - } - - @Override - public boolean isVerified() { - return verified; - } - - public VerificationFailedException getVerificationFailedCause() { - return verificationFailedException; - } - - public Future getLauncherTask() { - return launcherTask; - } - - private static MultiException.ExceptionStack errorExceptionStack = null; - private static MultiException.ExceptionStack verificationExceptionStack = null; - - private static final List> waitingTaskList = new ArrayList<>(); - - private static void loadCustomLoggerSettings() throws CouldNotPerformException { // assume SLF4J is bound to logback in the current environment final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); @@ -401,7 +211,8 @@ public static void main(final Class application, final String[] args, final C for (final Class launcherClass : launchers) { try { launcherMap.put(launcherClass, launcherClass.getConstructor().newInstance()); - } catch (java.lang.InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { + } catch (java.lang.InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException ex) { errorExceptionStack = MultiException.push(application, new CouldNotPerformException("Could not load launcher class!", ex), errorExceptionStack); } } @@ -434,7 +245,7 @@ public static void main(final Class application, final String[] args, final C try { if (JPService.getProperty(JPPrintLauncher.class).getValue()) { if (launcherMap.isEmpty()) { - System.out.println(generateName() + " does not provide any launcher!"); + System.out.println(generateAppName() + " does not provide any launcher!"); System.exit(255); } System.out.println("Available launcher:"); @@ -453,12 +264,13 @@ public static void main(final Class application, final String[] args, final C ExceptionPrinter.printHistory("Could not check if launcher should be printed.", ex, logger); } - logger.info("Start " + generateName() + "..."); + logger.info("Start " + generateAppName() + "..."); for (final Entry, AbstractLauncher> launcherEntry : new HashSet<>(launcherMap.entrySet())) { // check if launcher was excluded boolean exclude = false; + try { //filter excluded launcher for (String exclusionPatter : JPService.getProperty(JPExcludeLauncher.class).getValue()) { @@ -469,6 +281,7 @@ public static void main(final Class application, final String[] args, final C } catch (JPNotAvailableException ex) { ExceptionPrinter.printHistory("Could not process launcher exclusion!", ex, logger); } + if (exclude) { logger.info(launcherEntry.getKey().getSimpleName() + " excluded from execution."); launcherMap.remove(launcherEntry.getKey()); @@ -513,16 +326,14 @@ public static void main(final Class application, final String[] args, final C } catch (Exception ex) { final CouldNotPerformException exx = new CouldNotPerformException("Could not launch " + launcherEntry.getKey().getSimpleName() + "!", ex); + pushToErrorExceptionStack(application, exx); + // if a core launcher could not be started the whole startup failed so interrupt if (launcherEntry.getValue().isCoreLauncher()) { - pushToErrorExceptionStack(application, ExceptionPrinter.printHistoryAndReturnThrowable(exx, logger)); - // shutdown all launcher forceStopLauncher(launcherMap); } - pushToErrorExceptionStack(application, ExceptionPrinter.printHistoryAndReturnThrowable(exx, logger)); - // finish launcher return null; } @@ -541,29 +352,26 @@ public static void main(final Class application, final String[] args, final C // these exception will be pushed to the error exception stack anyway and printed in the summary } catch (CancellationException ex) { // if a core launcher fails a cancellation exception will be thrown - throw new InterruptedException(); + printSummary(application, logger, generateAppName() + " will be stopped because at least one core laucher could not be started."); + System.exit(200); } } } catch (InterruptedException ex) { - // shutdown all launcher - forceStopLauncher(launcherMap); - // recover interruption Thread.currentThread().interrupt(); + // shutdown all launcher + forceStopLauncher(launcherMap); + // print a summary containing the exceptions - printSummary(application, logger, generateName() + " caught shutdown signal during startup phase!"); + printSummary(application, logger, generateAppName() + " caught shutdown signal during startup phase!"); return; } - printSummary(application, logger, generateName() + " was started with restrictions!"); + printSummary(application, logger, generateAppName() + " was started with restrictions!"); } - private static final SyncObject VERIFICATION_STACK_LOCK = new SyncObject("VerificationStackLock"); - private static final SyncObject ERROR_STACK_LOCK = new SyncObject("ErrorStackLock"); - private static final SyncObject WAITING_TASK_LIST_LOCK = new SyncObject("WaitingStopLock"); - private static void pushToVerificationExceptionStack(Object source, Exception ex) { synchronized (VERIFICATION_STACK_LOCK) { verificationExceptionStack = MultiException.push(source, ex, verificationExceptionStack); @@ -586,20 +394,20 @@ private static void stopWaiting() { } } - private static void interruptLauncherBoot(final Map, AbstractLauncher> launcherMap) { + private static void interruptLaunch(final Map, AbstractLauncher> launcherMap) { // stop boot stopWaiting(); // interrupt all launcher for (final Entry, AbstractLauncher> launcherEntryToStop : launcherMap.entrySet()) { - launcherEntryToStop.getValue().interruptBoot(); + launcherEntryToStop.getValue().interruptLaunch(); } } private static void forceStopLauncher(final Map, AbstractLauncher> launcherMap) { - interruptLauncherBoot(launcherMap); + interruptLaunch(launcherMap); // stop all launcher. This is done in an extra loop since stop can block if the launcher is not yet fully interrupted. for (final Entry, AbstractLauncher> launcherEntryToStop : launcherMap.entrySet()) { @@ -607,7 +415,7 @@ private static void forceStopLauncher(final Map errorMessage, exceptionStack); - logger.info(generateName()+" successfully started."); + logger.info(generateAppName() + " successfully started."); } catch (MultiException ex) { ExceptionPrinter.printHistory(ex, logger); } } + @Override + public void init() throws InitializationException, InterruptedException { + super.init(SCOPE_PREFIX_LAUNCHER + ScopeProcessor.COMPONENT_SEPARATOR + JPService.getApplicationName() + ScopeProcessor.COMPONENT_SEPARATOR + ScopeProcessor.convertIntoValidScopeComponent(getName())); + + try { + verifyNonRedundantExecution(); + } catch (VerificationFailedException e) { + throw new InitializationException(this, e); + } + } + + /** + * This method can be overwritten to identify a core launcher. + * This means that if the start of this launcher fails the whole launching + * process will be stopped. + * + * @return false, can be overwritten to return true + */ + public boolean isCoreLauncher() { + return false; + } + + public L getLaunchable() { + return launchable; + } + + /** + * Method returns the name of this launcher. + * + * @return the name as string. + */ + @Override + public String getName() { + return getClass().getSimpleName().replace("Launcher", ""); + } + + /** + * Method creates a launchable instance without any arguments.. In case the launchable needs arguments you can overwrite this method and instantiate the launchable by ourself. + * + * @return the new instantiated launchable. + * + * @throws CouldNotPerformException is thrown in case the launchable could not properly be instantiated. + */ + protected L instantiateLaunchable() throws CouldNotPerformException { + try { + return launchableClass.getConstructor().newInstance(); + } catch (java.lang.InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException ex) { + throw new CouldNotPerformException("Could not load launchable class!", ex); + } + } + + // Load application specific java properties. + protected abstract void loadProperties(); + + /** + * Method verifies a running application. + * + * @throws VerificationFailedException is thrown if the application is started with any restrictions. + * @throws InterruptedException is thrown if the verification process is externally interrupted. + */ + protected void verify() throws VerificationFailedException, InterruptedException { + // overwrite for verification. + } + + private void setState(final LauncherState state) { + try (ClosableDataBuilder dataBuilder = getDataBuilder(this)) { + dataBuilder.getInternalBuilder().setLauncherState(state); + } catch (Exception e) { + ExceptionPrinter.printHistory("Could not apply state change!", e, logger); + } + } + + public void verifyNonRedundantExecution() throws VerificationFailedException { + // verify that launcher was not already externally started + try { + final LauncherRemote launcherRemote = new LauncherRemote(); + launcherRemote.init(getScope()); + try { + launcherRemote.activate(); + launcherRemote.waitForMiddleware(); + launcherRemote.waitForConnectionState(State.CONNECTED, 1000); + throw new RedundantExecutionException("Launcher[" + getName() + "]"); + } catch (org.openbase.jul.exception.TimeoutException e) { + // this is the default since no other instance should be launched yet. + } finally { + launcherRemote.shutdown(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (VerificationFailedException e) { + throw e; + } catch (CouldNotPerformException e) { + ExceptionPrinter.printHistory("Could not properly detect redundant launcher!", e, logger); + } + } + + @Override + public Future launch() { + + try(final CloseableWriteLockWrapper ignored = getManageWriteLockInterruptible(this)) { + + if (launcherTask != null && !launcherTask.isDone()) { + return FutureProcessor.canceledFuture(Void.class, new InvalidStateException("Could not launch " + getName() + "! Application still running!")); + } + + launcherTask = GlobalCachedExecutorService.submit(() -> { + + try(final CloseableWriteLockWrapper ignored1 = getManageWriteLockInterruptible(this)) { + + setState(LauncherState.INITIALIZING); + + try { + init(); + activate(); + } catch (CouldNotPerformException | RuntimeException e) { + setState(LauncherState.ERROR); + throw e; + } catch (InterruptedException e) { + setState(LauncherState.STOPPED); + throw e; + } + + launchable = instantiateLaunchable(); + try { + launchable.init(); + setState(LauncherState.LAUNCHING); + + launchable.activate(); + launchTime = System.currentTimeMillis(); + setState(LauncherState.RUNNING); + try { + verify(); + verified = true; + } catch (VerificationFailedException ex) { + verified = false; + verificationFailedException = ex; + } + } catch (InterruptedException ex) { + setState(LauncherState.STOPPING); + return null; + } catch (Exception ex) { + setState(LauncherState.ERROR); + launchable.shutdown(); + if (!ExceptionProcessor.isCausedBySystemShutdown(ex)) { + ExceptionPrinter.printHistoryAndReturnThrowable(new CouldNotPerformException("Could not launch " + getName(), ex), logger); + } + } + return null; + } + }); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ex); + } + return launcherTask; + } + + @Override + public void relaunch() throws CouldNotPerformException, InterruptedException { + try(final CloseableWriteLockWrapper ignored = getManageWriteLockInterruptible(this)) { + stop(); + try { + launch().get(); + } catch (ExecutionException | CancellationException ex) { + throw new CouldNotPerformException(ex); + } + } + } + + @Override + public void stop() { + + try(final CloseableWriteLockWrapper ignored = getManageWriteLockInterruptible(this)) { + interruptLaunch(); + setState(LauncherState.STOPPING); + if (launchable != null) { + launchable.shutdown(); + } + setState(LauncherState.STOPPED); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new RuntimeException(ex); + } + } + + /** + * Method cancels the launch process of this launcher. + */ + private void interruptLaunch() { + if (isLaunching()) { + launcherTask.cancel(true); + } + } + + /** + * @return true if the launcher is currently launching. + */ + private boolean isLaunching() { + return launcherTask != null && !launcherTask.isDone(); + } + + @Override + public void shutdown() { + stop(); + super.shutdown(); + } + + @Override + public long getUpTime() { + if (launchTime < 0) { + return 0; + } + return (System.currentTimeMillis() - launchTime); + } + + @Override + public long getLaunchTime() { + return launchTime; + } + + @Override + public boolean isVerified() { + return verified; + } + + public VerificationFailedException getVerificationFailedCause() { + return verificationFailedException; + } + + public Future getLauncherTask() { + return launcherTask; + } + @Override public void registerMethods(RPCServer server) { // currently, the launcher does not support any rpc methods. diff --git a/module/pattern/launch/src/main/java/org/openbase/jul/pattern/launch/LauncherRemote.kt b/module/pattern/launch/src/main/java/org/openbase/jul/pattern/launch/LauncherRemote.kt new file mode 100644 index 000000000..efe6e4514 --- /dev/null +++ b/module/pattern/launch/src/main/java/org/openbase/jul/pattern/launch/LauncherRemote.kt @@ -0,0 +1,6 @@ +package org.openbase.jul.pattern.launch + +import org.openbase.jul.communication.controller.AbstractIdentifiableRemote +import org.openbase.type.execution.LauncherDataType.LauncherData + +class LauncherRemote: AbstractIdentifiableRemote(LauncherData::class.java) diff --git a/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableLockProvider.java b/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableLockProvider.java index 7cd78657e..27cf26ebf 100644 --- a/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableLockProvider.java +++ b/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableLockProvider.java @@ -1,6 +1,6 @@ package org.openbase.jul.schedule; -/*- +/* * #%L * JUL Schedule * %% @@ -21,7 +21,6 @@ * . * #L% */ - public interface CloseableLockProvider { /** diff --git a/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableReadLockWrapper.java b/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableReadLockWrapper.java index 090434797..07b357c71 100644 --- a/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableReadLockWrapper.java +++ b/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableReadLockWrapper.java @@ -40,7 +40,12 @@ protected CloseableReadLockWrapper(final ReadWriteLock lock, final boolean alloc this.lock = lock; if(allocate) { - this.lock.lockRead(); + try { + this.lock.lockReadInterruptibly(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } } } diff --git a/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableWriteLockWrapper.java b/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableWriteLockWrapper.java index 3a1b5b02e..6c95632fa 100644 --- a/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableWriteLockWrapper.java +++ b/module/schedule/src/main/java/org/openbase/jul/schedule/CloseableWriteLockWrapper.java @@ -38,7 +38,12 @@ public CloseableWriteLockWrapper(final ReadWriteLock lock) { public CloseableWriteLockWrapper(final ReadWriteLock lock, final boolean allocate) { this.lock = lock; if (allocate) { - this.lock.lockWrite(); + try { + this.lock.lockWriteInterruptibly(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 5242723f6..1ed8dc563 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,6 +12,7 @@ include(":jul.extension.type.storage") include(":jul.pattern.module") include(":jul.extension.type") include(":jul.communication.mqtt") +include(":jul.communication.mqtt.test") include(":jul.processing.xml") include(":jul.extension.protobuf") include(":jul.processing.json") @@ -44,6 +45,7 @@ project(":jul.extension.type.storage").projectDir = file("module/extension/type/ project(":jul.pattern.module").projectDir = file("module/pattern") project(":jul.extension.type").projectDir = file("module/extension/type") project(":jul.communication.mqtt").projectDir = file("module/communication/mqtt") +project(":jul.communication.mqtt.test").projectDir = file("module/communication/mqtttest") project(":jul.processing.xml").projectDir = file("module/processing/xml") project(":jul.extension.protobuf").projectDir = file("module/extension/protobuf") project(":jul.processing.json").projectDir = file("module/processing/json") diff --git a/versions.properties b/versions.properties index 6415601b0..7be7f57b0 100644 --- a/versions.properties +++ b/versions.properties @@ -124,7 +124,7 @@ version.org.openjfx..javafx-controls=17 ## unused version.org.openjfx..javafx-base=17 -version.org.openbase..type=[1.3,1.4-alpha) +version.org.openbase..type=[1.4,1.5-alpha) version.org.openbase..jps=[3.6,3.7-alpha) ## # available=3.1.1