From 45d4687caa2ee55e004dea125cc21f73fbf4e127 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sat, 25 Jun 2022 12:46:19 +0200 Subject: [PATCH 01/70] switch openhab to v3.2 --- versions.properties | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/versions.properties b/versions.properties index b287925b0a..66febff476 100644 --- a/versions.properties +++ b/versions.properties @@ -127,9 +127,7 @@ version.org.springframework.boot..spring-boot-starter-websocket=2.6.3 version.org.springframework..spring-webmvc=5.3.15 -version.org.openhab.core.bundles..org.openhab.core.io.rest.core=3.1.0 -## # available=3.1.1 -## # available=3.2.0 +version.org.openhab.core.bundles..org.openhab.core.io.rest.core=3.2.0 plugin.org.springframework.boot=2.6.2 ## # available=2.6.3 From f31d0f9ed1cfa5b7fb84fb9cb1ace9d8986d9185 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sat, 25 Jun 2022 12:46:37 +0200 Subject: [PATCH 02/70] apply api changes --- .../synchronizer/SynchronizationProcessor.java | 15 ++++++++------- .../ThingDeviceUnitSynchronization.java | 5 +++-- .../synchronizer/ThingUnitSynchronization.java | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/SynchronizationProcessor.java b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/SynchronizationProcessor.java index a884d979d8..6dac5b1436 100644 --- a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/SynchronizationProcessor.java +++ b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/SynchronizationProcessor.java @@ -49,6 +49,7 @@ import org.openhab.core.io.rest.core.thing.EnrichedThingDTO; import org.openhab.core.items.dto.ItemDTO; import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.dto.AbstractThingDTO; import org.openhab.core.thing.dto.ChannelDTO; import org.openhab.core.thing.dto.ThingDTO; import org.openhab.core.thing.link.dto.ItemChannelLinkDTO; @@ -84,7 +85,7 @@ public class SynchronizationProcessor { * @return a device unit config for the thing as described above * @throws NotAvailableException if no device could be found */ - public static UnitConfig getDeviceForThing(final ThingDTO thingDTO) throws CouldNotPerformException { + public static UnitConfig getDeviceForThing(final AbstractThingDTO thingDTO) throws CouldNotPerformException { // iterate over all devices for (final UnitConfig deviceUnitConfig : Registries.getUnitRegistry().getUnitConfigsByUnitTypeFiltered(UnitType.DEVICE, false)) { // get the most global meta config @@ -135,7 +136,7 @@ public static String getUniquePrefix(final String uniqueId) { return uniquePrefix.toString(); } - public static UnitConfig getLocationForThing(final ThingDTO thingDTO) throws CouldNotPerformException, InterruptedException { + public static UnitConfig getLocationForThing(final AbstractThingDTO thingDTO) throws CouldNotPerformException, InterruptedException { if (thingDTO.location != null) { List locationConfigs = Registries.getUnitRegistry(true).getUnitConfigsByLabelAndUnitType(thingDTO.location, UnitType.LOCATION); @@ -178,7 +179,7 @@ public static DeviceClass getDeviceClassByDiscoveryResult(final DiscoveryResultD return resolveDeviceClass(discoveryResult.label, discoveryResult.thingTypeUID, properties); } - public static DeviceClass getDeviceClassForThing(final ThingDTO thingDTO) throws CouldNotPerformException { + public static DeviceClass getDeviceClassForThing(final AbstractThingDTO thingDTO) throws CouldNotPerformException { return resolveDeviceClass(thingDTO.label, thingDTO.thingTypeUID, thingDTO.properties); } @@ -193,7 +194,7 @@ public static GatewayClass getGatewayClassByDiscoveryResult(final DiscoveryResul return resolveGatewayClass(discoveryResult.label, discoveryResult.thingTypeUID, properties); } - public static GatewayClass getGatewayClassForThing(final ThingDTO thingDTO) throws CouldNotPerformException { + public static GatewayClass getGatewayClassForThing(final AbstractThingDTO thingDTO) throws CouldNotPerformException { return resolveGatewayClass(thingDTO.label, thingDTO.thingTypeUID, thingDTO.properties); } @@ -368,7 +369,7 @@ public static Map generateServiceMap(final UnitConf return serviceTypePatternMap; } - private static String getChannelUID(final UnitConfig unitConfig, final ServiceType serviceType, final ServicePattern servicePattern, final ThingDTO thingDTO) throws CouldNotPerformException { + private static String getChannelUID(final UnitConfig unitConfig, final ServiceType serviceType, final ServicePattern servicePattern, final EnrichedThingDTO thingDTO) throws CouldNotPerformException { String channelUID = ""; // todo: validate that unit host is available by using UnitConfigProcessor.isHostUnitAvailable(unitConfig) and make sure apps are handled as well. final UnitConfig deviceUnitConfig = Registries.getUnitRegistry().getUnitConfigById(unitConfig.getUnitHostId()); @@ -410,7 +411,7 @@ private static String getChannelUID(final UnitConfig unitConfig, final ServiceTy return channelUID; } - public static void registerAndValidateItems(final UnitConfig unitConfig, final ThingDTO thingDTO) throws CouldNotPerformException { + public static void registerAndValidateItems(final UnitConfig unitConfig, final EnrichedThingDTO thingDTO) throws CouldNotPerformException { final List itemChannelLinks = OpenHABRestCommunicator.getInstance().getItemChannelLinks(); for (final Entry entry : generateServiceMap(unitConfig).entrySet()) { final ServiceType serviceType = entry.getKey(); @@ -581,7 +582,7 @@ public static boolean updateUnitToThing(final EnrichedThingDTO thing, final Unit * @param thingDTO the thing to be removed. * @throws CouldNotPerformException if removing the thing fails. */ - public static void deleteThing(final ThingDTO thingDTO) throws CouldNotPerformException { + public static void deleteThing(final EnrichedThingDTO thingDTO) throws CouldNotPerformException { final List itemChannelLinks = OpenHABRestCommunicator.getInstance().getItemChannelLinks(); for (final ChannelDTO channel : thingDTO.channels) { for (final ItemChannelLinkDTO itemChannelLink : itemChannelLinks) { diff --git a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingDeviceUnitSynchronization.java b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingDeviceUnitSynchronization.java index 7e5e78bb0e..0866efea20 100644 --- a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingDeviceUnitSynchronization.java +++ b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingDeviceUnitSynchronization.java @@ -44,6 +44,7 @@ import org.openbase.type.domotic.unit.device.DeviceClassType.DeviceClass; import org.openbase.type.domotic.unit.gateway.GatewayClassType.GatewayClass; import org.openhab.core.io.rest.core.thing.EnrichedThingDTO; +import org.openhab.core.thing.dto.AbstractThingDTO; import org.openhab.core.thing.dto.ThingDTO; import java.util.ArrayList; @@ -97,7 +98,7 @@ public void update(IdentifiableEnrichedThingDTO identifiableEnrichedThingDTO) th @Override public void register(IdentifiableEnrichedThingDTO identifiableEnrichedThingDTO) throws CouldNotPerformException, InterruptedException { logger.debug("Synchronize {} ...", identifiableEnrichedThingDTO.getDTO().UID); - final ThingDTO thingDTO = identifiableEnrichedThingDTO.getDTO(); + final EnrichedThingDTO thingDTO = identifiableEnrichedThingDTO.getDTO(); try { final UnitConfig deviceUnitConfig = SynchronizationProcessor.getDeviceForThing(thingDTO); @@ -111,7 +112,7 @@ public void register(IdentifiableEnrichedThingDTO identifiableEnrichedThingDTO) } } - private void registerDevice(ThingDTO thingDTO) throws CouldNotPerformException, InterruptedException { + private void registerDevice(EnrichedThingDTO thingDTO) throws CouldNotPerformException, InterruptedException { //TODO: should this entire action be rolled back if one part fails? // get device class for thing DeviceClass deviceClass; diff --git a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingUnitSynchronization.java b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingUnitSynchronization.java index 39a9458f81..2d0a0cb97c 100644 --- a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingUnitSynchronization.java +++ b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/registry/synchronizer/ThingUnitSynchronization.java @@ -120,12 +120,12 @@ public boolean isSupported(IdentifiableEnrichedThingDTO identifiableEnrichedThin return identifiableEnrichedThingDTO.getId().startsWith(BCO_BINDING_ID); } - private String getUnitId(ThingDTO thingDTO) { + private String getUnitId(EnrichedThingDTO thingDTO) { String[] split = thingDTO.UID.split(":"); return split[split.length - 1]; } - private void registerAndValidateItems(final ThingDTO thingDTO) throws CouldNotPerformException { + private void registerAndValidateItems(final EnrichedThingDTO thingDTO) throws CouldNotPerformException { // save current item channel links to compute if some already exist final List itemChannelLinks = OpenHABRestCommunicator.getInstance().getItemChannelLinks(); // retrieve the unit belonging to the thing From 457221a3ae0dc84ed8197d1dd995ff5d3af0e9a9 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 23 May 2023 20:03:25 +0200 Subject: [PATCH 03/70] improve docker build log --- build-docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-docker.sh b/build-docker.sh index 88ba62dc7b..32828cd711 100755 --- a/build-docker.sh +++ b/build-docker.sh @@ -10,7 +10,7 @@ WHITE='\033[0;37m' APP_NAME='docker images' APP_NAME=${BLUE}${APP_NAME}${NC} -echo -e "=== ${APP_NAME} are building${WHITE}${NC}" && +echo -e "=== ${APP_NAME} build docker image...${WHITE}${NC}" && docker build -f docker/Dockerfile -t openbaseorg/bco:local . docker build -f docker/device-manager/openhab/Dockerfile -t openbaseorg/bco-device-manager-openhab:local --build-arg BCO_BASE_IMAGE_VERSION=local docker/device-manager/openhab docker build -f docker/bco-demo/Dockerfile -t openbaseorg/bco-demo:local --build-arg BCO_BASE_IMAGE_VERSION=local docker/bco-demo From 742627da10b450be8b348f97e3755ebd11507e39 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 23 May 2023 22:26:05 +0200 Subject: [PATCH 04/70] fix targeted java version in gradle --- build.gradle.kts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index fd4a56fb11..a4621fd612 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { id("io.github.gradle-nexus.publish-plugin") version "1.1.0" } @@ -12,3 +14,10 @@ nexusPublishing { } } } + + +tasks.withType(KotlinCompile::class).all { + kotlinOptions { + jvmTarget = "17" + } +} From 364e03663b8cb6cece249a9a5dfa3c66bf73541e Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 23 May 2023 22:26:27 +0200 Subject: [PATCH 05/70] link against latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index c042bac636..b048f01587 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit c042bac636025c440dfb5eaf0cdbd0f3b7f767a2 +Subproject commit b048f01587809daf1785c13cf2e30aff1f341d0f From 1404f66c6011514ea1987d141b352f030737aa4f Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 23 May 2023 22:35:12 +0200 Subject: [PATCH 06/70] auto formatting, improve registry shutdown. --- .../core/AuthenticationController.java | 6 +++--- .../core/AuthenticatorLauncher.java | 9 ++++---- .../layer/unit/app/AppManagerLauncher.java | 6 +++--- .../lib/launch/AbstractRegistryLauncher.java | 7 ++++--- .../unit/core/UnitRegistryController.java | 21 +++++++++++++++++-- .../unit/remote/UnitRegistryRemote.java | 6 +++++- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticationController.java b/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticationController.java index a078fea91a..b004b41907 100644 --- a/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticationController.java +++ b/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticationController.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -237,7 +237,7 @@ public Future requestTicketGrantingTicket(final UserCli clientCredentials = credentialStore.getCredentials(userClientPair.getClientId()); } - if(userCredentials != null && clientCredentials != null) { + if (userCredentials != null && clientCredentials != null) { if (!userCredentials.getSymmetric() && !clientCredentials.getSymmetric()) { throw new NotSupportedException("Login with two asymmetric keys", AuthenticationController.class); } diff --git a/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticatorLauncher.java b/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticatorLauncher.java index 233f15398a..c2c0f413a5 100644 --- a/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticatorLauncher.java +++ b/module/authentication/core/src/main/java/org/openbase/bco/authentication/core/AuthenticatorLauncher.java @@ -18,19 +18,19 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . * #L% */ + /** - * * @author Tamino Huxohl */ public class AuthenticatorLauncher extends AbstractLauncher { @@ -50,7 +50,8 @@ protected void loadProperties() { /** * @param args the command line arguments - * @throws java.lang.InterruptedException is thrown when the thread was externally interrupted. + * + * @throws java.lang.InterruptedException is thrown when the thread was externally interrupted. * @throws org.openbase.jul.exception.CouldNotPerformException thrown in case the launcher could not be started. */ public static void main(final String[] args) throws InterruptedException, CouldNotPerformException { diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppManagerLauncher.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppManagerLauncher.java index 4f92ab7c43..169b0f857c 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppManagerLauncher.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppManagerLauncher.java @@ -1,7 +1,7 @@ package org.openbase.bco.dal.control.layer.unit.app; -import org.openbase.bco.dal.lib.layer.unit.app.AppManager; import org.openbase.bco.authentication.lib.BCO; +import org.openbase.bco.dal.lib.layer.unit.app.AppManager; import org.openbase.jul.pattern.launch.AbstractLauncher; /* @@ -14,12 +14,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java index 2661b0bfe8..b716eb475b 100644 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -29,9 +29,10 @@ /** * @param + * * @author Divine Threepwood */ -public abstract class AbstractRegistryLauncher extends AbstractLauncher { +public abstract class AbstractRegistryLauncher> extends AbstractLauncher { public AbstractRegistryLauncher(Class applicationClass, Class launchableClass) throws InstantiationException { super(applicationClass, launchableClass); diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java index 5b3617b12c..202cb4cddc 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java @@ -207,8 +207,11 @@ public void activate() throws InterruptedException, CouldNotPerformException { @Override public void deactivate() throws InterruptedException, CouldNotPerformException { this.removeDataObserver(clearUnitConfigsByTypeObserver); - CachedTemplateRegistryRemote.getRegistry().removeDataObserver(clearUnitConfigsByTypeObserver); - + try { + CachedTemplateRegistryRemote.getRegistry().removeDataObserver(clearUnitConfigsByTypeObserver); + } catch (NotAvailableException e) { + // just continue + } super.deactivate(); } @@ -572,7 +575,9 @@ public UnitConfig getUnitConfigById(final String unitConfigId) throws NotAvailab * {@inheritDoc} * * @param unitAlias {@inheritDoc} + * * @return {@inheritDoc} + * * @throws NotAvailableException {@inheritDoc} */ @Override @@ -594,7 +599,9 @@ public UnitConfig getUnitConfigByAlias(final String unitAlias) throws NotAvailab * * @param unitAlias {@inheritDoc} * @param unitType {@inheritDoc} + * * @return {@inheritDoc} + * * @throws NotAvailableException {@inheritDoc} */ @Override @@ -674,7 +681,9 @@ public Future removeUnitConfigAuthenticated(final Authentica * {@inheritDoc} * * @param filterDisabledUnits {@inheritDoc} + * * @return {@inheritDoc} + * * @throws CouldNotPerformException {@inheritDoc} * @throws NotAvailableException {@inheritDoc} */ @@ -698,6 +707,7 @@ public List getUnitConfigsFiltered(boolean filterDisabledUnits) thro * {@inheritDoc} * * @return {@inheritDoc} + * * @throws CouldNotPerformException {@inheritDoc} */ @Override @@ -709,6 +719,7 @@ public List getDalUnitConfigs() throws CouldNotPerformException { * {@inheritDoc} * * @return {@inheritDoc} + * * @throws CouldNotPerformException {@inheritDoc} */ @Override @@ -759,7 +770,9 @@ public Boolean isUnitGroupConfigRegistryConsistent() { * {@inheritDoc} * * @param serviceType + * * @return + * * @throws CouldNotPerformException */ @Override @@ -780,7 +793,9 @@ public List getServiceConfigsByServiceType(final ServiceType serv * * @param unitType * @param serviceTypes + * * @return + * * @throws CouldNotPerformException */ @Override @@ -1048,6 +1063,7 @@ protected UnitRegistryData filterDataForUser(final UnitRegistryData.Builder data * {@inheritDoc} * * @param authorizationToken {@inheritDoc} + * * @return {@inheritDoc} */ @Override @@ -1066,6 +1082,7 @@ public Future requestAuthorizationToken(final AuthorizationToken authori * {@inheritDoc} * * @param authenticatedValue {@inheritDoc} + * * @return {@inheritDoc} */ @Override diff --git a/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/UnitRegistryRemote.java b/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/UnitRegistryRemote.java index 50f3395e9b..5ce54eb604 100644 --- a/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/UnitRegistryRemote.java +++ b/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/UnitRegistryRemote.java @@ -164,7 +164,11 @@ public void activate() throws InterruptedException, CouldNotPerformException { public void deactivate() throws InterruptedException, CouldNotPerformException { unitConfigRemoteRegistry.removeDataObserver(aliasMapUpdateObserver); unitConfigRemoteRegistry.removeDataObserver(clearUnitConfigsByTypeObserver); - CachedUnitRegistryRemote.getRegistry().removeDataObserver(clearUnitConfigsByTypeObserver); + try { + CachedUnitRegistryRemote.getRegistry().removeDataObserver(clearUnitConfigsByTypeObserver); + } catch (NotAvailableException e) { + // just continue + } super.deactivate(); } From cef40d2155928e1fe3df3035fae919bf138ba3a8 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 23 May 2023 23:50:36 +0200 Subject: [PATCH 07/70] port CustomUnitPool to kotlin. --- .../subscriptions/SubscriptionModule.kt | 2 +- .../dal/remote/detector/PresenceDetector.java | 171 ++++---- .../dal/remote/layer/unit/CustomUnitPool.java | 374 ------------------ .../dal/remote/layer/unit/CustomUnitPool.kt | 356 +++++++++++++++++ .../bco/dal/remote/layer/unit/Units.java | 18 +- .../dal/remote/printer/UnitStatePrinter.java | 36 +- 6 files changed, 463 insertions(+), 494 deletions(-) delete mode 100644 module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.java create mode 100644 module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt index 17e154c215..a877304693 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt @@ -49,7 +49,7 @@ import java.util.function.Consumer private val BACKPRESSURE_STRATEGY = BackpressureStrategy.BUFFER @Throws(BCOGraphQLError::class) - fun subscribeUnits(unitFilter: UnitFilter?): Publisher { + fun subscribeUnits(unitFilter: UnitFilter): Publisher { return try { val subscriptionUnitPool = CustomUnitPool() subscriptionUnitPool.init(unitFilter) diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java index 7daeb037d4..51e86b5bec 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java @@ -10,26 +10,23 @@ * 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% */ -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - import org.openbase.bco.dal.lib.layer.unit.location.Location; import org.openbase.bco.dal.remote.layer.unit.CustomUnitPool; import org.openbase.jps.core.JPService; -import org.openbase.jul.exception.*; import org.openbase.jul.exception.InstantiationException; +import org.openbase.jul.exception.*; import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.exception.printer.LogLevel; import org.openbase.jul.extension.type.processing.TimestampProcessor; @@ -42,19 +39,22 @@ import org.openbase.type.domotic.state.ButtonStateType.ButtonState; import org.openbase.type.domotic.state.ButtonStateType.ButtonStateOrBuilder; import org.openbase.type.domotic.state.DoorStateType.DoorState; +import org.openbase.type.domotic.state.MotionStateType.MotionState; +import org.openbase.type.domotic.state.MotionStateType.MotionStateOrBuilder; +import org.openbase.type.domotic.state.PresenceStateType.PresenceState; +import org.openbase.type.domotic.state.PresenceStateType.PresenceState.State; +import org.openbase.type.domotic.state.PresenceStateType.PresenceStateOrBuilder; import org.openbase.type.domotic.state.WindowStateType.WindowState; import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType; import org.openbase.type.domotic.unit.connection.ConnectionConfigType.ConnectionConfig.ConnectionType; import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType; +import org.openbase.type.domotic.unit.location.LocationDataType.LocationData; import org.openbase.type.domotic.unit.location.TileConfigType.TileConfig.TileType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.openbase.type.domotic.state.MotionStateType.MotionState; -import org.openbase.type.domotic.state.MotionStateType.MotionStateOrBuilder; -import org.openbase.type.domotic.state.PresenceStateType.PresenceState; -import org.openbase.type.domotic.state.PresenceStateType.PresenceState.State; -import org.openbase.type.domotic.state.PresenceStateType.PresenceStateOrBuilder; -import org.openbase.type.domotic.unit.location.LocationDataType.LocationData; + +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; /** * @author Divine Threepwood @@ -81,70 +81,65 @@ public class PresenceDetector implements Manageable, DataProvider(this); - this.presenceTimeout = new Timeout(PRESENCE_TIMEOUT) { - - @Override - public void expired() { - try { - // if motion is still detected just restart the timeout. - if (location.getData().getMotionState().getValue() == MotionState.State.MOTION) { - GlobalCachedExecutorService.submit(() -> { - try { - presenceTimeout.restart(); - } catch (final CouldNotPerformException ex) { - ExceptionPrinter.printHistory("Could not setup presence timeout!", ex, logger); - } - }); - return; - } - updatePresenceState(PresenceState.newBuilder().setValue(PresenceState.State.ABSENT)); - } catch (ShutdownInProgressException ex) { - // skip update on shutdown - } catch (CouldNotPerformException ex) { - ExceptionPrinter.printHistory(new CouldNotPerformException("Could not notify absent by timer!", ex), logger); + this.presenceStateBuilder = PresenceState.newBuilder(); + this.active = false; + this.presenceStateObservable = new ObservableImpl<>(this); + this.presenceTimeout = new Timeout(PRESENCE_TIMEOUT) { + + @Override + public void expired() { + try { + // if motion is still detected just restart the timeout. + if (location.getData().getMotionState().getValue() == MotionState.State.MOTION) { + GlobalCachedExecutorService.submit(() -> { + try { + presenceTimeout.restart(); + } catch (final CouldNotPerformException ex) { + ExceptionPrinter.printHistory("Could not setup presence timeout!", ex, logger); + } + }); + return; } + updatePresenceState(PresenceState.newBuilder().setValue(PresenceState.State.ABSENT)); + } catch (ShutdownInProgressException ex) { + // skip update on shutdown + } catch (CouldNotPerformException ex) { + ExceptionPrinter.printHistory(new CouldNotPerformException("Could not notify absent by timer!", ex), logger); } - }; + } + }; - locationDataObserver = (DataProvider source, LocationData data) -> { - updateMotionState(data.getMotionState()); - }; + locationDataObserver = (DataProvider source, LocationData data) -> { + updateMotionState(data.getMotionState()); + }; - this.buttonUnitPool = new CustomUnitPool(); - this.connectionUnitPool = new CustomUnitPool(); + this.buttonUnitPool = new CustomUnitPool(); + this.connectionUnitPool = new CustomUnitPool(); - this.buttonUnitPool.addServiceStateObserver((source, data) -> { - try { - PresenceDetector.this.updateButtonState((ButtonState) data); - } catch (ClassCastException ex) { - ExceptionPrinter.printHistory("ButtonPool entail incompatible units!", ex, logger); - } - }); - - this.connectionUnitPool.addServiceStateObserver((source, data) -> { - switch (source.getServiceType()) { - case WINDOW_STATE_SERVICE: - updateWindowState((WindowState) data); - break; - case DOOR_STATE_SERVICE: - updateDoorState((DoorState) data); - break; - case PASSAGE_STATE_SERVICE: - // just ignore passage states. - break; - - default: - logger.warn("Invalid connection service update received: " + source.getServiceType().name() + " from " + source + " pool:" + connectionUnitPool.isActive()); - } - }); - - } catch (CouldNotPerformException ex) { - throw new InstantiationException(this, ex); - } + this.buttonUnitPool.addServiceStateObserver((source, data) -> { + try { + PresenceDetector.this.updateButtonState((ButtonState) data); + } catch (ClassCastException ex) { + ExceptionPrinter.printHistory("ButtonPool entail incompatible units!", ex, logger); + } + }); + + this.connectionUnitPool.addServiceStateObserver((source, data) -> { + switch (source.getServiceType()) { + case WINDOW_STATE_SERVICE: + updateWindowState((WindowState) data); + break; + case DOOR_STATE_SERVICE: + updateDoorState((DoorState) data); + break; + case PASSAGE_STATE_SERVICE: + // just ignore passage states. + break; + + default: + logger.warn("Invalid connection service update received: " + source.getServiceType().name() + " from " + source + " pool:" + connectionUnitPool.isActive()); + } + }); } @Override @@ -165,23 +160,23 @@ public void init(final Location location) throws InitializationException, Interr if ((location.getConfig().getLocationConfig().getLocationType() == LocationType.TILE)) { connectionUnitPool.init( - unitConfig -> unitConfig.getUnitType() == UnitType.CONNECTION, - unitConfig -> { - try { - return unitConfig.getConnectionConfig().getTileIdList().contains(location.getId()); - } catch (NotAvailableException ex) { - ExceptionPrinter.printHistory("Could not resolve location id within connection filter operation.", ex, logger); - return false; - } - }, - unitConfig -> { - try { - return location.getConfig().getLocationConfig().getTileConfig().getTileType() != TileType.OUTDOOR || unitConfig.getConnectionConfig().getConnectionType() != ConnectionType.WINDOW; - } catch (NotAvailableException ex) { - ExceptionPrinter.printHistory("Could not resolve location id within connection filter operation.", ex, logger); - return false; - } - }); + unitConfig -> unitConfig.getUnitType() == UnitType.CONNECTION, + unitConfig -> { + try { + return unitConfig.getConnectionConfig().getTileIdList().contains(location.getId()); + } catch (NotAvailableException ex) { + ExceptionPrinter.printHistory("Could not resolve location id within connection filter operation.", ex, logger); + return false; + } + }, + unitConfig -> { + try { + return location.getConfig().getLocationConfig().getTileConfig().getTileType() != TileType.OUTDOOR || unitConfig.getConnectionConfig().getConnectionType() != ConnectionType.WINDOW; + } catch (NotAvailableException ex) { + ExceptionPrinter.printHistory("Could not resolve location id within connection filter operation.", ex, logger); + return false; + } + }); } } catch (CouldNotPerformException ex) { throw new InitializationException(this, ex); diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.java deleted file mode 100644 index 1fa9bb6bb6..0000000000 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.java +++ /dev/null @@ -1,374 +0,0 @@ -package org.openbase.bco.dal.remote.layer.unit; - -/*- - * #%L - * BCO DAL Remote - * %% - * Copyright (C) 2014 - 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% - */ - -import com.google.protobuf.Message; -import org.openbase.bco.dal.lib.layer.service.ServiceStateProvider; -import org.openbase.bco.dal.lib.layer.unit.Unit; -import org.openbase.bco.dal.lib.layer.unit.UnitRemote; -import org.openbase.bco.registry.remote.Registries; -import org.openbase.bco.registry.unit.lib.filter.UnitConfigFilterImpl; -import org.openbase.jul.exception.InstantiationException; -import org.openbase.jul.exception.*; -import org.openbase.jul.exception.printer.ExceptionPrinter; -import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.extension.protobuf.ProtobufListDiff; -import org.openbase.jul.iface.Manageable; -import org.openbase.jul.pattern.Filter; -import org.openbase.jul.pattern.ObservableImpl; -import org.openbase.jul.pattern.Observer; -import org.openbase.jul.pattern.provider.DataProvider; -import org.openbase.jul.storage.registry.RemoteControllerRegistry; -import org.openbase.type.domotic.registry.UnitRegistryDataType.UnitRegistryData; -import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServiceType; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig.Builder; -import org.openbase.type.domotic.unit.UnitFilterType.UnitFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -public class CustomUnitPool implements Manageable>> { - - private static final Logger LOGGER = LoggerFactory.getLogger(CustomUnitPool.class); - - private final ReentrantReadWriteLock UNIT_REMOTE_REGISTRY_LOCK = new ReentrantReadWriteLock(); - private final Observer, UnitRegistryData> unitRegistryDataObserver; - private final Observer unitDataObserver; - private final Observer serviceStateObserver; - private final RemoteControllerRegistry> unitRemoteRegistry; - private final ProtobufListDiff unitConfigDiff; - private final Set> filterSet; - private final ObservableImpl, Message> unitDataObservable; - private final ObservableImpl, Message> serviceStateObservable; - private volatile boolean active; - - public CustomUnitPool() throws InstantiationException { - try { - this.filterSet = new HashSet<>(); - this.unitConfigDiff = new ProtobufListDiff<>(); - this.unitRemoteRegistry = new RemoteControllerRegistry<>(); - this.unitRegistryDataObserver = (source, data) -> { - sync(); - }; - this.unitDataObservable = new ObservableImpl<>(); - this.unitDataObserver = (source, message) -> { - try { - unitDataObservable.notifyObservers(((Unit) source), (Message) message); - } catch (ClassCastException ex) { - ExceptionPrinter.printHistory("Could not handle incoming data because type is unknown!", ex, LOGGER); - } - }; - this.serviceStateObservable = new ObservableImpl<>(); - this.serviceStateObserver = (source, data) -> { - try { - serviceStateObservable.notifyObservers((ServiceStateProvider) source, (Message) data); - } catch (ClassCastException ex) { - ExceptionPrinter.printHistory("Could not handle incoming data because type is unknown!", ex, LOGGER); - } - }; - - } catch (CouldNotPerformException ex) { - throw new InstantiationException(this, ex); - } - } - - /** - * This filter initialization is optional. - * Method sets a new filter set for the custom pool. - * If the pool is already active, then the filter is directly applied. - * If you call this method twice, only the latest filter set is used. - * - * @param unitFilter this is a unit filter that can be used to limit the number of unit to observer. No filter means every unit is observed by this pool. - * - * @throws InitializationException is throw if the initialization fails. - * @throws InterruptedException is thrown if the thread was externally interrupted. - */ - public final void init(final UnitFilter unitFilter) throws InitializationException, InterruptedException { - init(new UnitConfigFilterImpl(unitFilter)); - } - - /** - * This filter initialization is optional. - * Method sets a new filter set for the custom pool. - * If the pool is already active, then the filter is directly applied. - * If you call this method twice, only the latest filter set is used. - * - * @param filters this set of filters can be used to limit the number of unit to observer. No filter means every unit is observed by this pool. - * - * @throws InitializationException is throw if the initialization fails. - * @throws InterruptedException is thrown if the thread was externally interrupted. - */ - public final void init(final Filter... filters) throws InitializationException, InterruptedException { - init(Arrays.asList(filters)); - } - - /** - * This filter initialization is optional. - * Method sets a new filter set for the custom pool. - * If the pool is already active, then the filter is directly applied. - * If you call this method twice, only the latest filter set is used. - * - * @param filters this set of filters can be used to limit the number of unit to observer. No filter means every unit is observed by this pool. - * - * @throws InitializationException is throw if the initialization fails. - * @throws InterruptedException is thrown if the thread was externally interrupted. - */ - @Override - public void init(final Collection> filters) throws InitializationException, InterruptedException { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly(); - try { - filterSet.clear(); - filterSet.addAll(filters); - if (active) { - sync(); - } - } finally { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock(); - } - } - - private void sync() throws InterruptedException { - // skip if registry is not ready yet. - try { - if (!Registries.getUnitRegistry().isDataAvailable()) { - return; - } - } catch (NotAvailableException e) { - return; - } - - try { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly(); - try { - // todo: why not filter before diff? That would make so much things easier here. - unitConfigDiff.diffMessages(Registries.getUnitRegistry().getUnitConfigs()); - - // handle new units - unitLoop: - for (Entry> entry : unitConfigDiff.getNewMessageMap().entrySet()) { - - // apply unit filter - for (Filter filter : filterSet) { - if (filter.pass(entry.getValue().getMessage())) { - continue unitLoop; - } - } - addUnitRemote(entry.getKey()); - } - - // handle updated units - unitLoop: - for (Entry> entry : unitConfigDiff.getUpdatedMessageMap().entrySet()) { - - for (Filter filter : filterSet) { - - // remove known units which pass the filter - if (filter.pass(entry.getValue().getMessage()) && unitRemoteRegistry.contains(entry.getKey())) { - removeUnitRemote(entry.getKey()); - continue unitLoop; - } - - // we are done if unit is already known - if (unitRemoteRegistry.contains(entry.getKey())) { - continue unitLoop; - } - - // filter if required - if (filter.pass(entry.getValue().getMessage())) { - continue unitLoop; - } - } - - // unit has not been removed, is not already in the pool and is not skipped by the filters, therefore we need to add it - addUnitRemote(entry.getKey()); - } - - //handle removed units - for (Entry> entry : unitConfigDiff.getRemovedMessageMap().entrySet()) { - if (unitRemoteRegistry.contains(entry.getKey())) { - removeUnitRemote(entry.getKey()); - } - } - - // validate already registered units - unitLoop: - for (Unit unit : new ArrayList<>(unitRemoteRegistry.getEntries())) { - - // apply unit filter - for (Filter filter : filterSet) { - if (filter.pass(unit.getConfig())) { - removeUnitRemote(unit.getId()); - continue unitLoop; - } - } - } - - } finally { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock(); - } - } catch (CouldNotPerformException ex) { - ExceptionPrinter.printHistory("Could not sync " + this, ex, LOGGER); - } - } - - private void addUnitRemote(final String unitId) throws InterruptedException { - try { - - if (!isActive()) { - new FatalImplementationErrorException("unid remote registered but pool was never activated!", this); - } - - final UnitRemote unitRemote = Units.getUnit(unitId, false); - unitRemoteRegistry.register(unitRemote); - - for (ServiceType serviceType : unitRemote.getAvailableServiceTypes()) { - unitRemote.addServiceStateObserver(serviceType, serviceStateObserver); - } - unitRemote.addDataObserver(unitDataObserver); - } catch (CouldNotPerformException ex) { - if (!ExceptionProcessor.isCausedBySystemShutdown(ex)) { - ExceptionPrinter.printHistory("Could not add " + unitId, ex, LOGGER); - } - } - } - - private void removeUnitRemote(final String unitId) { - try { - final UnitRemote unitRemote; - try { - unitRemote = unitRemoteRegistry.get(unitId); - } catch (NotAvailableException ex) { - // unit not registered so removal not necessary. - return; - } - for (ServiceType serviceType : unitRemote.getAvailableServiceTypes()) { - unitRemote.removeServiceStateObserver(serviceType, serviceStateObserver); - } - unitRemote.removeDataObserver(unitDataObserver); - unitRemoteRegistry.remove(unitRemote); - } catch (CouldNotPerformException ex) { - if (!ExceptionProcessor.isCausedBySystemShutdown(ex)) { - ExceptionPrinter.printHistory("Could not remove " + unitId, ex, LOGGER); - } - } - } - - /** - * Method registers the given observer to all internal unit remotes to get informed about state changes. - * - * @param observer is the observer to register. - */ - public void addDataObserver(Observer, Message> observer) { - unitDataObservable.addObserver(observer); - } - - /** - * Method removes the given observer from all internal unit remotes. - * - * @param observer is the observer to remove. - */ - public void removeDataObserver(Observer, Message> observer) { - unitDataObservable.removeObserver(observer); - } - - /** - * Method registers the given observer to all internal unit remotes to get informed about state changes. - * - * @param observer is the observer to register. - */ - public void addServiceStateObserver(Observer, Message> observer) { - serviceStateObservable.addObserver(observer); - } - - /** - * Method removes the given observer from all internal unit remotes. - * - * @param observer is the observer to remove. - */ - public void removeServiceStateObserver(Observer, Message> observer) { - serviceStateObservable.removeObserver(observer); - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - - @Override - public void activate() throws CouldNotPerformException, InterruptedException { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly(); - try { - - // skip run if already active - if (isActive()) { - return; - } - - // add observer - Registries.getUnitRegistry().addDataObserver(unitRegistryDataObserver); - - active = true; - - // trigger initial sync - if (Registries.getUnitRegistry().isDataAvailable()) { - sync(); - } - } finally { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock(); - } - } - - @Override - public void deactivate() throws CouldNotPerformException, InterruptedException { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly(); - try { - active = false; - try { - Registries.getUnitRegistry().removeDataObserver(unitRegistryDataObserver); - } catch (NotAvailableException ex) { - // if the registry is not available an observer deregistration is not required. - // This can for example be the case when the unit registry has already been terminated during the shutdown progress. - } - - // deregister all observed units - for (String unitId : new ArrayList<>(unitRemoteRegistry.getEntryMap().keySet())) { - removeUnitRemote(unitId); - } - } finally { - UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock(); - } - } - - @Override - public boolean isActive() { - return active; - } - - public List> getInternalUnitList() { - return unitRemoteRegistry.getEntries(); - } -} diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt new file mode 100644 index 0000000000..b2f10cbc53 --- /dev/null +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt @@ -0,0 +1,356 @@ +package org.openbase.bco.dal.remote.layer.unit + +import com.google.protobuf.Message +import org.openbase.bco.dal.lib.layer.service.ServiceStateProvider +import org.openbase.bco.dal.lib.layer.unit.Unit +import org.openbase.bco.dal.lib.layer.unit.UnitRemote +import org.openbase.bco.registry.remote.Registries +import org.openbase.bco.registry.unit.lib.filter.UnitConfigFilterImpl +import org.openbase.jul.exception.* +import org.openbase.jul.exception.ExceptionProcessor.isCausedBySystemShutdown +import org.openbase.jul.exception.printer.ExceptionPrinter +import org.openbase.jul.extension.protobuf.ProtobufListDiff +import org.openbase.jul.iface.Manageable +import org.openbase.jul.pattern.Filter +import org.openbase.jul.pattern.ObservableImpl +import org.openbase.jul.pattern.Observer +import org.openbase.jul.pattern.provider.DataProvider +import org.openbase.jul.storage.registry.RemoteControllerRegistry +import org.openbase.type.domotic.registry.UnitRegistryDataType +import org.openbase.type.domotic.unit.UnitConfigType +import org.openbase.type.domotic.unit.UnitFilterType +import org.slf4j.LoggerFactory +import java.util.* +import java.util.concurrent.locks.ReentrantReadWriteLock + +/*- + * #%L + * BCO DAL Remote + * %% + * Copyright (C) 2014 - 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% + */ + +class CustomUnitPool : Manageable>> { + private val UNIT_REMOTE_REGISTRY_LOCK = ReentrantReadWriteLock() + private var unitRegistryDataObserver: Observer, UnitRegistryDataType.UnitRegistryData> + private var unitDataObserver: Observer, Message> + private var serviceStateObserver: Observer, out Message> + private var unitRemoteRegistry: RemoteControllerRegistry> + private var unitConfigDiff: ProtobufListDiff + private var filterSet: MutableSet> + private var unitDataObservable: ObservableImpl, Message> + private var serviceStateObservable: ObservableImpl, Message> + + @Volatile + private var active = false + + + init { + try { + filterSet = HashSet() + unitConfigDiff = ProtobufListDiff() + unitRemoteRegistry = RemoteControllerRegistry() + unitRegistryDataObserver = + Observer { source: DataProvider, data: UnitRegistryDataType.UnitRegistryData? -> sync() } + unitDataObservable = ObservableImpl() + unitDataObserver = Observer { source: Any, message: Any -> + try { + unitDataObservable.notifyObservers(source as Unit, message as Message) + } catch (ex: ClassCastException) { + ExceptionPrinter.printHistory("Could not handle incoming data because type is unknown!", ex, LOGGER) + } + } + serviceStateObservable = ObservableImpl() + serviceStateObserver = Observer { source: Any, data: Any -> + try { + serviceStateObservable.notifyObservers(source as ServiceStateProvider, data as Message) + } catch (ex: ClassCastException) { + ExceptionPrinter.printHistory("Could not handle incoming data because type is unknown!", ex, LOGGER) + } + } + } catch (ex: CouldNotPerformException) { + throw InstantiationException(this, ex) + } + } + + /** + * This filter initialization is optional. + * Method sets a new filter set for the custom pool. + * If the pool is already active, then the filter is directly applied. + * If you call this method twice, only the latest filter set is used. + * + * @param unitFilter this is a unit filter that can be used to limit the number of unit to observer. No filter means every unit is observed by this pool. + * + * @throws InitializationException is throw if the initialization fails. + * @throws InterruptedException is thrown if the thread was externally interrupted. + */ + @Throws(InitializationException::class, InterruptedException::class) + fun init(unitFilter: UnitFilterType.UnitFilter) { + init(UnitConfigFilterImpl(unitFilter)) + } + + /** + * This filter initialization is optional. + * Method sets a new filter set for the custom pool. + * If the pool is already active, then the filter is directly applied. + * If you call this method twice, only the latest filter set is used. + * + * @param filters this set of filters can be used to limit the number of unit to observer. No filter means every unit is observed by this pool. + * + * @throws InitializationException is throw if the initialization fails. + * @throws InterruptedException is thrown if the thread was externally interrupted. + */ + @Throws(InitializationException::class, InterruptedException::class) + fun init(vararg filters: Filter) { + init(filters.toList()) + } + + /** + * This filter initialization is optional. + * Method sets a new filter set for the custom pool. + * If the pool is already active, then the filter is directly applied. + * If you call this method twice, only the latest filter set is used. + * + * @param filters this set of filters can be used to limit the number of unit to observer. No filter means every unit is observed by this pool. + * + * @throws InitializationException is throw if the initialization fails. + * @throws InterruptedException is thrown if the thread was externally interrupted. + */ + @Throws(InitializationException::class, InterruptedException::class) + override fun init(filters: Collection>) { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly() + try { + filterSet.clear() + filterSet.addAll(filters) + if (active) { + sync() + } + } finally { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock() + } + } + + @Throws(InterruptedException::class) + private fun sync() { + // skip if registry is not ready yet. + try { + if (!Registries.getUnitRegistry().isDataAvailable) { + return + } + } catch (e: NotAvailableException) { + return + } + try { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly() + try { + // todo: why not filter before diff? That would make so much things easier here. + unitConfigDiff.diffMessages(Registries.getUnitRegistry().unitConfigs) + + // handle new units + unitLoop@ for ((key, value) in unitConfigDiff.newMessageMap) { + + // apply unit filter + for (filter in filterSet) { + if (filter.pass(value.message)) { + continue@unitLoop + } + } + addUnitRemote(key) + } + + // handle updated units + unitLoop@ for ((key, value) in unitConfigDiff.updatedMessageMap) { + for (filter in filterSet) { + + // remove known units which pass the filter + if (filter.pass(value.message) && unitRemoteRegistry.contains(key)) { + removeUnitRemote(key) + continue@unitLoop + } + + // we are done if unit is already known + if (unitRemoteRegistry.contains(key)) { + continue@unitLoop + } + + // filter if required + if (filter.pass(value.message)) { + continue@unitLoop + } + } + + // unit has not been removed, is not already in the pool and is not skipped by the filters, therefore we need to add it + addUnitRemote(key) + } + + //handle removed units + for ((key) in unitConfigDiff.removedMessageMap) { + if (unitRemoteRegistry.contains(key)) { + removeUnitRemote(key) + } + } + + // validate already registered units + unitLoop@ for (unit in ArrayList>( + unitRemoteRegistry.entries + )) { + + // apply unit filter + for (filter in filterSet) { + if (filter.pass(unit.config)) { + removeUnitRemote(unit.id) + continue@unitLoop + } + } + } + } finally { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock() + } + } catch (ex: CouldNotPerformException) { + ExceptionPrinter.printHistory("Could not sync $this", ex, LOGGER) + } + } + + @Throws(InterruptedException::class) + private fun addUnitRemote(unitId: String) { + try { + if (!isActive) { + FatalImplementationErrorException("unid remote registered but pool was never activated!", this) + } + val unitRemote: UnitRemote = Units.getUnit(unitId, false) + unitRemoteRegistry.register(unitRemote) + for (serviceType in unitRemote.availableServiceTypes) { + unitRemote.addServiceStateObserver(serviceType, serviceStateObserver) + } + unitRemote.addDataObserver(unitDataObserver) + } catch (ex: CouldNotPerformException) { + if (!isCausedBySystemShutdown(ex)) { + ExceptionPrinter.printHistory("Could not add $unitId", ex, LOGGER) + } + } + } + + private fun removeUnitRemote(unitId: String) { + try { + unitRemoteRegistry[unitId]?.let { unitRemote -> + unitRemote.availableServiceTypes.forEach { serviceType -> + unitRemote.removeServiceStateObserver(serviceType, serviceStateObserver) + } + unitRemote.removeDataObserver(unitDataObserver) + unitRemoteRegistry.remove(unitRemote) + } + } catch (ex: CouldNotPerformException) { + if (!isCausedBySystemShutdown(ex)) { + ExceptionPrinter.printHistory("Could not remove $unitId", ex, LOGGER) + } + } + } + + /** + * Method registers the given observer to all internal unit remotes to get informed about state changes. + * + * @param observer is the observer to register. + */ + fun addDataObserver(observer: Observer, Message>) { + unitDataObservable.addObserver(observer) + } + + /** + * Method removes the given observer from all internal unit remotes. + * + * @param observer is the observer to remove. + */ + fun removeDataObserver(observer: Observer, Message>) { + unitDataObservable.removeObserver(observer) + } + + /** + * Method registers the given observer to all internal unit remotes to get informed about state changes. + * + * @param observer is the observer to register. + */ + fun addServiceStateObserver(observer: Observer, Message>) { + serviceStateObservable.addObserver(observer) + } + + /** + * Method removes the given observer from all internal unit remotes. + * + * @param observer is the observer to remove. + */ + fun removeServiceStateObserver(observer: Observer, Message>) { + serviceStateObservable.removeObserver(observer) + } + + override fun toString(): String { + return javaClass.simpleName + } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun activate() { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly() + try { + + // skip run if already active + if (isActive) { + return + } + + // add observer + Registries.getUnitRegistry().addDataObserver(unitRegistryDataObserver) + active = true + + // trigger initial sync + if (Registries.getUnitRegistry().isDataAvailable) { + sync() + } + } finally { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock() + } + } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun deactivate() { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly() + try { + active = false + try { + Registries.getUnitRegistry().removeDataObserver(unitRegistryDataObserver) + } catch (ex: NotAvailableException) { + // if the registry is not available an observer deregistration is not required. + // This can for example be the case when the unit registry has already been terminated during the shutdown progress. + } + + // deregister all observed units + for (unitId in ArrayList(unitRemoteRegistry.entryMap.keys)) { + removeUnitRemote(unitId) + } + } finally { + UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock() + } + } + + override fun isActive(): Boolean = active + + val internalUnitList: List> + get() = unitRemoteRegistry.entries + + companion object { + private val LOGGER = LoggerFactory.getLogger(CustomUnitPool::class.java) + } +} diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/Units.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/Units.java index 7d10f7026f..3d4daa921e 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/Units.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/Units.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 * . @@ -53,7 +53,6 @@ import org.openbase.jul.storage.registry.RemoteControllerRegistry; import org.openbase.rct.Transform; import org.openbase.type.communication.ScopeType; -import org.openbase.type.communication.ScopeType.Scope; import org.openbase.type.domotic.registry.UnitRegistryDataType.UnitRegistryData; import org.openbase.type.domotic.state.EnablingStateType.EnablingState; import org.openbase.type.domotic.unit.UnitConfigType; @@ -61,6 +60,7 @@ import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -384,9 +384,9 @@ private static void resetUnitRegistryObserver() throws CouldNotPerformException * @throws InterruptedException is thrown if the current thread was * externally interrupted. */ - private static UnitRemote getUnitRemote(final String unitId) throws NotAvailableException, InterruptedException { + private static UnitRemote getUnitRemote(final String unitId) throws NotAvailableException, InterruptedException { final boolean newInstance; - final UnitRemote unitRemote; + final UnitRemote unitRemote; try { if (shutdownInitialized) { @@ -488,7 +488,7 @@ private static UnitRemote getUnitRemote(final UnitConfig unitConfig) throws N * @throws InterruptedException is thrown in case the thread is externally * interrupted. */ - private static UnitRemote waitForData(final UnitRemote unitRemote, final boolean waitForData) throws CouldNotPerformException, InterruptedException { + private static UnitRemote waitForData(final UnitRemote unitRemote, final boolean waitForData) throws CouldNotPerformException, InterruptedException { if (waitForData) { Registries.getUnitRegistry(true); unitRemote.waitForData(); @@ -510,7 +510,7 @@ private static UnitRemote waitForData(final UnitRemote unitRemote, final b * @throws InterruptedException is thrown in case the thread is externally * interrupted. */ - private static UnitRemote waitForData(final UnitRemote unitRemote, final long timeout, final TimeUnit timeUnit) throws CouldNotPerformException, InterruptedException { + private static UnitRemote waitForData(final UnitRemote unitRemote, final long timeout, final TimeUnit timeUnit) throws CouldNotPerformException, InterruptedException { final TimeoutSplitter timeoutSplitter = new TimeoutSplitter(timeout, timeUnit); Registries.getUnitRegistry(timeoutSplitter.getTime(), timeoutSplitter.getTimeUnit()); unitRemote.waitForData(timeoutSplitter.getTime(), timeoutSplitter.getTimeUnit()); @@ -847,7 +847,7 @@ public static > UR getUnitByAlias(final String alias, f * @throws InterruptedException is thrown in case the thread is externally * interrupted */ - public static UnitRemote getUnit(final String unitId, final boolean waitForData) throws NotAvailableException, InterruptedException { + public static UnitRemote getUnit(final String unitId, final boolean waitForData) throws NotAvailableException, InterruptedException { if (unitId == null) { assert false; @@ -855,7 +855,7 @@ public static UnitRemote getUnit(final String unitId, final boolean waitForDa } try { - return waitForData(getUnitRemote(unitId), waitForData); + return waitForData(((UnitRemote) getUnitRemote(unitId)), waitForData); } catch (CouldNotPerformException ex) { throw new NotAvailableException("Unit[" + unitId + "]", ex); } diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/printer/UnitStatePrinter.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/printer/UnitStatePrinter.java index 475451fc8a..95160df54d 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/printer/UnitStatePrinter.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/printer/UnitStatePrinter.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 * . @@ -107,27 +107,19 @@ public Config setPrintInitialStates(boolean printInitialStates) { } public UnitStatePrinter(final PrintStream printStream, final Config config) throws InstantiationException { - try { - this.config = config; - this.outputConsumer = null; - this.printStream = printStream; - this.customUnitPool = new CustomUnitPool(); - this.unitStateObserver = (source, data) -> print((Unit) source.getServiceProvider(), source.getServiceType(), data); - } catch (CouldNotPerformException ex) { - throw new InstantiationException(this, ex); - } + this.config = config; + this.outputConsumer = null; + this.printStream = printStream; + this.customUnitPool = new CustomUnitPool(); + this.unitStateObserver = (source, data) -> print((Unit) source.getServiceProvider(), source.getServiceType(), data); } public UnitStatePrinter(final Consumer outputConsumer, final Config config) throws InstantiationException { - try { - this.config = config; - this.outputConsumer = outputConsumer; - this.printStream = null; - this.customUnitPool = new CustomUnitPool(); - this.unitStateObserver = (source, data) -> print((Unit) source.getServiceProvider(), source.getServiceType(), data); - } catch (CouldNotPerformException ex) { - throw new InstantiationException(this, ex); - } + this.config = config; + this.outputConsumer = outputConsumer; + this.printStream = null; + this.customUnitPool = new CustomUnitPool(); + this.unitStateObserver = (source, data) -> print((Unit) source.getServiceProvider(), source.getServiceType(), data); } @SafeVarargs @@ -220,11 +212,11 @@ private void print(Unit unit, ServiceType serviceType, Message serviceState) // compute related units to filter for (ActionReferenceType.ActionReference cause : responsibleAction.getActionCauseList()) { - relatedUnitIdServiceTypePair.add("['" + IdResolver.getId(cause.getServiceStateDescription().getUnitId()) + "', "+cause.getServiceStateDescription().getServiceType().name().toLowerCase()+"]"); + relatedUnitIdServiceTypePair.add("['" + IdResolver.getId(cause.getServiceStateDescription().getUnitId()) + "', " + cause.getServiceStateDescription().getServiceType().name().toLowerCase() + "]"); } for (ActionReferenceType.ActionReference impact : responsibleAction.getActionImpactList()) { - relatedUnitIdServiceTypePair.add("['" + IdResolver.getId(impact.getServiceStateDescription().getUnitId()) + "', "+impact.getServiceStateDescription().getServiceType().name().toLowerCase()+"]"); + relatedUnitIdServiceTypePair.add("['" + IdResolver.getId(impact.getServiceStateDescription().getUnitId()) + "', " + impact.getServiceStateDescription().getServiceType().name().toLowerCase() + "]"); } } From 8f8a086d53713c7570cd244cc75720aabcf0b21c Mon Sep 17 00:00:00 2001 From: lhuxohl Date: Tue, 4 Jul 2023 20:29:25 +0200 Subject: [PATCH 08/70] add agents, apps to registry --- .../bco/api/graphql/schema/RegistrySchemaModule.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt index c3f96ee52a..aa7bdef9e6 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt @@ -24,6 +24,8 @@ import org.openbase.type.configuration.MetaConfigType import org.openbase.type.domotic.unit.UnitConfigType import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig import org.openbase.type.domotic.unit.UnitFilterType +import org.openbase.type.domotic.unit.agent.AgentClassType +import org.openbase.type.domotic.unit.app.AppClassType import org.openbase.type.domotic.unit.gateway.GatewayClassType import org.openbase.type.geometry.PoseType import org.openbase.type.language.LabelType @@ -158,6 +160,16 @@ import java.util.concurrent.* fun gatewayClasses(): ImmutableList = ImmutableList.copyOf(Registries.getClassRegistry(true).gatewayClasses) + @Query("agentClasses") + @Throws(CouldNotPerformException::class, InterruptedException::class) + fun agentClasses(): ImmutableList = + ImmutableList.copyOf(Registries.getClassRegistry(true).agentClasses) + + @Query("appClasses") + @Throws(CouldNotPerformException::class, InterruptedException::class) + fun appClasses(): ImmutableList = + ImmutableList.copyOf(Registries.getClassRegistry(true).appClasses) + @Mutation("updateUnitConfig") @Throws(BCOGraphQLError::class) fun updateUnitConfig( From 437be8d53beaa964fa6c421f54c3cb0aa21562a3 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 4 Jul 2023 20:37:56 +0200 Subject: [PATCH 09/70] Implement labelString support for AppClass and AgentClass --- .../bco/api/graphql/schema/SchemaModificationsAdd.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java index 054cfb31de..2f704a8f0c 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java @@ -116,6 +116,16 @@ String addLabelBestMatch(UnitTemplate unitTemplate, DataFetchingEnvironment env) return getLabelForContext(unitTemplate.getLabel(), env.getContext()); } + @SchemaModification(addField = "labelString", onType = AgentClass.class) + String addLabelBestMatch(AgentClass agentClass, DataFetchingEnvironment env) { + return getLabelForContext(agentClass.getLabel(), env.getContext()); + } + + @SchemaModification(addField = "labelString", onType = AppClass.class) + String addLabelBestMatch(AppClass appClass, DataFetchingEnvironment env) { + return getLabelForContext(appClass.getLabel(), env.getContext()); + } + @SchemaModification(addField = "descriptionString", onType = UnitConfig.class) String addDescriptionBestMatch(UnitConfig unitConfig, DataFetchingEnvironment env) { return getTextForContext(unitConfig.getDescription(), env.getContext()); From 3a741a21e575e903f766544b0826162b22f703d5 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 4 Jul 2023 22:23:09 +0200 Subject: [PATCH 10/70] introduce service and unit template queries --- .../bco/api/graphql/schema/RegistrySchemaModule.kt | 12 ++++++++++++ .../api/graphql/schema/SchemaModificationsAdd.java | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt index aa7bdef9e6..aa0f40b500 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt @@ -21,9 +21,11 @@ import org.openbase.jul.extension.type.processing.LabelProcessor.getBestMatch import org.openbase.jul.extension.type.processing.LabelProcessor.replace import org.openbase.type.configuration.EntryType import org.openbase.type.configuration.MetaConfigType +import org.openbase.type.domotic.service.ServiceTemplateType import org.openbase.type.domotic.unit.UnitConfigType import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig import org.openbase.type.domotic.unit.UnitFilterType +import org.openbase.type.domotic.unit.UnitTemplateType import org.openbase.type.domotic.unit.agent.AgentClassType import org.openbase.type.domotic.unit.app.AppClassType import org.openbase.type.domotic.unit.gateway.GatewayClassType @@ -170,6 +172,16 @@ import java.util.concurrent.* fun appClasses(): ImmutableList = ImmutableList.copyOf(Registries.getClassRegistry(true).appClasses) + @Query("unitTemplates") + @Throws(CouldNotPerformException::class, InterruptedException::class) + fun unitTemplates(): ImmutableList = + ImmutableList.copyOf(Registries.getTemplateRegistry(true).unitTemplates) + + @Query("serviceTemplates") + @Throws(CouldNotPerformException::class, InterruptedException::class) + fun serviceTemplates(): ImmutableList = + ImmutableList.copyOf(Registries.getTemplateRegistry(true).serviceTemplates) + @Mutation("updateUnitConfig") @Throws(BCOGraphQLError::class) fun updateUnitConfig( diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java index 2f704a8f0c..3d00a34b78 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java @@ -245,6 +245,11 @@ String addLabelBestMatch(GatewayClass gatewayClass, DataFetchingEnvironment env) return getLabelForContext(gatewayClass.getLabel(), env.getContext()); } + @SchemaModification(addField = "labelString", onType = ServiceTemplate.class) + String addLabelBestMatch(ServiceTemplate serviceTemplate, DataFetchingEnvironment env) { + return getLabelForContext(serviceTemplate.getLabel(), env.getContext()); + } + @SchemaModification(addField = "descriptionString", onType = GatewayClass.class) String addDescriptionBestMatch(GatewayClass gatewayClass, DataFetchingEnvironment env) { return getTextForContext(gatewayClass.getDescription(), env.getContext()); From 090cad096a87a72597fe0017f280b13506da17e4 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 4 Jul 2023 22:27:44 +0200 Subject: [PATCH 11/70] code cleanup --- .../schema/SchemaModificationsAdd.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java index 3d00a34b78..6ac2d03a5a 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/SchemaModificationsAdd.java @@ -126,6 +126,21 @@ String addLabelBestMatch(AppClass appClass, DataFetchingEnvironment env) { return getLabelForContext(appClass.getLabel(), env.getContext()); } + @SchemaModification(addField = "labelString", onType = DeviceClass.class) + String addLabelBestMatch(DeviceClass deviceClass, DataFetchingEnvironment env) { + return getLabelForContext(deviceClass.getLabel(), env.getContext()); + } + + @SchemaModification(addField = "labelString", onType = GatewayClass.class) + String addLabelBestMatch(GatewayClass gatewayClass, DataFetchingEnvironment env) { + return getLabelForContext(gatewayClass.getLabel(), env.getContext()); + } + + @SchemaModification(addField = "labelString", onType = ServiceTemplate.class) + String addLabelBestMatch(ServiceTemplate serviceTemplate, DataFetchingEnvironment env) { + return getLabelForContext(serviceTemplate.getLabel(), env.getContext()); + } + @SchemaModification(addField = "descriptionString", onType = UnitConfig.class) String addDescriptionBestMatch(UnitConfig unitConfig, DataFetchingEnvironment env) { return getTextForContext(unitConfig.getDescription(), env.getContext()); @@ -235,21 +250,6 @@ private UnitConfig resolveParentTile(String locationId) throws NotAvailableExcep } } - @SchemaModification(addField = "labelString", onType = DeviceClass.class) - String addLabelBestMatch(DeviceClass deviceClass, DataFetchingEnvironment env) { - return getLabelForContext(deviceClass.getLabel(), env.getContext()); - } - - @SchemaModification(addField = "labelString", onType = GatewayClass.class) - String addLabelBestMatch(GatewayClass gatewayClass, DataFetchingEnvironment env) { - return getLabelForContext(gatewayClass.getLabel(), env.getContext()); - } - - @SchemaModification(addField = "labelString", onType = ServiceTemplate.class) - String addLabelBestMatch(ServiceTemplate serviceTemplate, DataFetchingEnvironment env) { - return getLabelForContext(serviceTemplate.getLabel(), env.getContext()); - } - @SchemaModification(addField = "descriptionString", onType = GatewayClass.class) String addDescriptionBestMatch(GatewayClass gatewayClass, DataFetchingEnvironment env) { return getTextForContext(gatewayClass.getDescription(), env.getContext()); From 8b65a44488de0c245b3f07b2931aa63fb517740e Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 18 Jul 2023 20:35:08 +0200 Subject: [PATCH 12/70] update publish pipeline deps --- .github/workflows/publish-jars.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-jars.yml b/.github/workflows/publish-jars.yml index cde426ae3d..1160bea37a 100644 --- a/.github/workflows/publish-jars.yml +++ b/.github/workflows/publish-jars.yml @@ -13,12 +13,14 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v2 - - name: Set up Java - uses: actions/setup-java@v2 + - name: "Checkout Branch" + uses: actions/checkout@v3 + - name: "Setup Java" + uses: actions/setup-java@v3 with: - java-version: '17' - distribution: 'adopt' + distribution: 'temurin' + java-version: 17 + cache: 'gradle' - name: Validate Gradle wrapper uses: gradle/wrapper-validation-action@v1 - name: Publish Packages From 2438a1720a2856e3b92a4afb2b810d647c0f92ec Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 26 Jul 2023 19:36:04 +0200 Subject: [PATCH 13/70] fix remote pool filtering and add some debug logs. --- lib/jul | 2 +- ...ctAuthenticatedConfigurableController.java | 8 +-- ...AbstractAuthenticatedControllerServer.java | 8 +-- .../layer/unit/AbstractUnitController.java | 17 ++++++ .../dal/remote/layer/unit/CustomUnitPool.kt | 61 ++----------------- ...tractUnitTransformationRegistryPlugin.java | 15 ++--- 6 files changed, 37 insertions(+), 74 deletions(-) diff --git a/lib/jul b/lib/jul index b048f01587..7387220d8a 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit b048f01587809daf1785c13cf2e30aff1f341d0f +Subproject commit 7387220d8a87ab278a2ea458a8fadd65e72f01b1 diff --git a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.java b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.java index 955d2be78d..913c81ca0b 100644 --- a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.java +++ b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.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 * . @@ -32,13 +32,13 @@ import org.openbase.jps.core.JPService; import org.openbase.jps.exception.JPNotAvailableException; import org.openbase.jul.annotation.RPCMethod; +import org.openbase.jul.communication.controller.AbstractConfigurableController; import org.openbase.jul.communication.iface.RPCServer; import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.InstantiationException; import org.openbase.jul.exception.InvalidStateException; import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.exception.printer.LogLevel; -import org.openbase.jul.communication.controller.AbstractConfigurableController; import org.openbase.type.domotic.authentication.AuthenticatedValueType.AuthenticatedValue; import org.openbase.type.domotic.authentication.TicketAuthenticatorWrapperType.TicketAuthenticatorWrapper; import org.openbase.type.domotic.authentication.UserClientPairType.UserClientPair; @@ -89,7 +89,7 @@ public M requestStatus() throws CouldNotPerformException { @Override public AuthenticatedValue requestDataAuthenticated(final TicketAuthenticatorWrapper ticket) throws CouldNotPerformException { - logger.trace("requestStatusAuthenticated of " + this); + printDebug("requestStatusAuthenticated of " + this); // evaluate the ticket final AuthenticationBaseData authenticationBaseData = AuthenticatedServerManager.getInstance().verifyClientServerTicket(ticket); diff --git a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.java b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.java index 135e2d5a31..321ab3e1b7 100644 --- a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.java +++ b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.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 * . @@ -31,6 +31,7 @@ import org.openbase.jps.core.JPService; import org.openbase.jps.exception.JPNotAvailableException; import org.openbase.jul.annotation.RPCMethod; +import org.openbase.jul.communication.controller.AbstractControllerServer; import org.openbase.jul.communication.iface.RPCServer; import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.InstantiationException; @@ -38,7 +39,6 @@ import org.openbase.jul.exception.NotAvailableException; import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.exception.printer.LogLevel; -import org.openbase.jul.communication.controller.AbstractControllerServer; import org.openbase.type.domotic.authentication.AuthenticatedValueType.AuthenticatedValue; import org.openbase.type.domotic.authentication.TicketAuthenticatorWrapperType.TicketAuthenticatorWrapper; import org.openbase.type.domotic.authentication.UserClientPairType.UserClientPair; @@ -97,7 +97,7 @@ public M requestStatus() throws CouldNotPerformException { @Override public AuthenticatedValue requestDataAuthenticated(final TicketAuthenticatorWrapper ticket) throws CouldNotPerformException { - logger.trace("requestStatusAuthenticated of " + this); + printDebug("requestStatusAuthenticated of " + this); // evaluate the ticket final AuthenticationBaseData authenticationBaseData = AuthenticatedServerManager.getInstance().verifyClientServerTicket(ticket); diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java index 9d4064d3a7..9e19126148 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java @@ -121,6 +121,7 @@ /** * @param the data type of this unit used for the state synchronization. * @param the builder used to build the unit data instance. + * * @author Divine Threepwood */ public abstract class AbstractUnitController> extends AbstractAuthenticatedConfigurableController implements UnitController { @@ -262,6 +263,7 @@ protected long getShutdownDelay() { @Override public void init(ScopeType.Scope scope) throws InitializationException, InterruptedException { try { + printDebug("new scope is: " + ScopeProcessor.generateStringRep(scope)); super.init(Registries.getUnitRegistry(true).getUnitConfigByScope(scope)); } catch (CouldNotPerformException ex) { throw new InitializationException(this, ex); @@ -282,6 +284,7 @@ public void init(final UnitConfig config) throws InitializationException, Interr if (config == null) { throw new NotAvailableException("config"); } + printDebug("new scope is: " + ScopeProcessor.generateStringRep(config.getScope())); if (!config.hasId()) { throw new NotAvailableException("config.id"); @@ -682,7 +685,9 @@ public Future applyAction(final ActionDescription actionDescr * @param actionId the id of the action retrieved. * @param lockConsumer string identifying the task. Required because this method has to lock the builder setup because * of access to the {@link #scheduledActionList}. + * * @return the action identified by the provided id as described above. + * * @throws NotAvailableException if not action with the provided id could be found. */ public SchedulableAction getActionById(final String actionId, final String lockConsumer) throws NotAvailableException, InterruptedException { @@ -714,6 +719,7 @@ public SchedulableAction getActionById(final String actionId, final String lockC * * @param userId the id of the user whose permissions are checked. * @param action the action checked. + * * @throws PermissionDeniedException if the user has no permissions to modify the provided action. * @throws CouldNotPerformException if the permissions check could not be performed. */ @@ -887,6 +893,7 @@ private int getSchedulingIndex(Action action) throws InterruptedException { * * @return the {@code action} which is ranked highest and which is therefore currently allocating this unit. * If there is no action left to schedule null is returned. + * * @throws CouldNotPerformException is throw in case the scheduling is currently not possible, e.g. because of a system shutdown. */ public Action reschedule() throws CouldNotPerformException, InterruptedException { @@ -898,8 +905,10 @@ public Action reschedule() throws CouldNotPerformException, InterruptedException * If the current action is not finished it will be rejected. * * @param actionToSchedule a new action to schedule. If null it will be ignored. + * * @return the {@code action} which is ranked highest and which is therefore currently allocating this unit. * If there is no action left to schedule null is returned. + * * @throws CouldNotPerformException is throw in case the scheduling is currently not possible, e.g. because of a system shutdown. */ private Action reschedule(final SchedulableAction actionToSchedule) throws CouldNotPerformException, InterruptedException { @@ -1153,6 +1162,7 @@ public void notifyScheduledActionList() throws InterruptedException { * Syncs the action list into the given {@code dataBuilder}. * * @param dataBuilder used to synchronize with. + * * @throws CouldNotPerformException is thrown if the sync failed. */ private void syncActionList(final DB dataBuilder) throws CouldNotPerformException { @@ -1479,7 +1489,9 @@ protected Action getCurrentAction() throws NotAvailableException { * @param serviceState the prototype of the new state. * @param serviceType the service type of the new state. * @param internalBuilder the builder object used to access the currently applied state. + * * @return the computed state. + * * @throws RejectedException in case the state would not change anything compared to the current one. * @throws CouldNotPerformException if the state could not be computed. */ @@ -1764,6 +1776,7 @@ protected void copyResponsibleAction(final ServiceType sourceServiceType, final * * @param internalBuilder The data builder of this unit which already contains the updated state. * @param serviceType The service type which has been updated. + * * @throws InterruptedException is thrown if the thread is externally interrupted. */ protected void applyCustomDataUpdate(DB internalBuilder, ServiceType serviceType) throws InterruptedException { @@ -1851,7 +1864,9 @@ protected Message getServiceState(final ServiceType serviceType, String userId) * otherwise the parent location remote is returned which refers the location where this unit is placed in. * * @param waitForData flag defines if the method should block until the remote is fully synchronized. + * * @return a location remote instance. + * * @throws NotAvailableException is thrown if the location remote is currently not available. * @throws java.lang.InterruptedException is thrown if the current was externally interrupted. */ @@ -1919,6 +1934,7 @@ public void activate() throws InterruptedException, CouldNotPerformException { * * @param serviceType the type of the new service. * @param operationService the service which performes the operation. + * * @throws CouldNotPerformException is thrown if the type of the service is already registered. */ protected void registerOperationService(final ServiceType serviceType, final OperationService operationService) throws CouldNotPerformException { @@ -1952,6 +1968,7 @@ public Map getOperationServiceMap() { * * @param serviceState {@inheritDoc} * @param serviceType {@inheritDoc} + * * @return {@inheritDoc} */ @Override diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt index b2f10cbc53..209264df21 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt @@ -158,66 +158,17 @@ class CustomUnitPool : Manageable>> try { UNIT_REMOTE_REGISTRY_LOCK.writeLock().lockInterruptibly() try { - // todo: why not filter before diff? That would make so much things easier here. - unitConfigDiff.diffMessages(Registries.getUnitRegistry().unitConfigs) + // update list diff + Registries.getUnitRegistry().unitConfigs + .filter { unitConfig -> filterSet.all { it.match(unitConfig) } } + .let { unitConfigDiff.diffMessages(it) } // handle new units - unitLoop@ for ((key, value) in unitConfigDiff.newMessageMap) { + unitConfigDiff.newMessageMap.keys.forEach { addUnitRemote(it) } - // apply unit filter - for (filter in filterSet) { - if (filter.pass(value.message)) { - continue@unitLoop - } - } - addUnitRemote(key) - } - - // handle updated units - unitLoop@ for ((key, value) in unitConfigDiff.updatedMessageMap) { - for (filter in filterSet) { - - // remove known units which pass the filter - if (filter.pass(value.message) && unitRemoteRegistry.contains(key)) { - removeUnitRemote(key) - continue@unitLoop - } - - // we are done if unit is already known - if (unitRemoteRegistry.contains(key)) { - continue@unitLoop - } - - // filter if required - if (filter.pass(value.message)) { - continue@unitLoop - } - } - - // unit has not been removed, is not already in the pool and is not skipped by the filters, therefore we need to add it - addUnitRemote(key) - } //handle removed units - for ((key) in unitConfigDiff.removedMessageMap) { - if (unitRemoteRegistry.contains(key)) { - removeUnitRemote(key) - } - } - - // validate already registered units - unitLoop@ for (unit in ArrayList>( - unitRemoteRegistry.entries - )) { - - // apply unit filter - for (filter in filterSet) { - if (filter.pass(unit.config)) { - removeUnitRemote(unit.id) - continue@unitLoop - } - } - } + unitConfigDiff.removedMessageMap.keys.forEach { removeUnitRemote(it) } } finally { UNIT_REMOTE_REGISTRY_LOCK.writeLock().unlock() } diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/AbstractUnitTransformationRegistryPlugin.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/AbstractUnitTransformationRegistryPlugin.java index d0fa01eb5c..9f6d9e4f64 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/AbstractUnitTransformationRegistryPlugin.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/AbstractUnitTransformationRegistryPlugin.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -25,18 +25,13 @@ import org.openbase.jps.core.JPService; import org.openbase.jul.exception.*; import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.iface.Transformer; -import org.openbase.jul.schedule.GlobalCachedExecutorService; import org.openbase.jul.storage.registry.ProtoBufRegistry; import org.openbase.jul.storage.registry.plugin.ProtobufRegistryPluginAdapter; import org.openbase.rct.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig.Builder; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class AbstractUnitTransformationRegistryPlugin extends ProtobufRegistryPluginAdapter { @@ -70,7 +65,7 @@ protected void verifyPublication(final Transform transformation, String targetFr // wait until transformation was published try { int maxChecks = 10; - Exception exception = new CouldNotPerformException("This should not happen"); + Exception exception = new CouldNotPerformException("Update not advertised."); for (int i = 0; i < maxChecks; i++) { try { // check if transformation was published From 3adb5e7c5163dbcc1eadea853eaa3efaa0695282 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 26 Jul 2023 19:57:49 +0200 Subject: [PATCH 14/70] update jul --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 7387220d8a..0932d981e5 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 7387220d8a87ab278a2ea458a8fadd65e72f01b1 +Subproject commit 0932d981e52c6fe12f17390e136e8902279e9e9f From da926a84e5b7e4820535da9c924315b9f1a69c86 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 26 Jul 2023 21:34:46 +0200 Subject: [PATCH 15/70] cleanup code --- lib/jul | 2 +- .../lib/com/AbstractAuthenticatedConfigurableController.java | 2 +- .../lib/com/AbstractAuthenticatedControllerServer.java | 2 +- .../bco/dal/control/layer/unit/AbstractUnitController.java | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/jul b/lib/jul index 0932d981e5..41b805754f 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 0932d981e52c6fe12f17390e136e8902279e9e9f +Subproject commit 41b805754fa996b76169c8f8edeb6335e3145d70 diff --git a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.java b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.java index 913c81ca0b..a728ceddf8 100644 --- a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.java +++ b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedConfigurableController.java @@ -89,7 +89,7 @@ public M requestStatus() throws CouldNotPerformException { @Override public AuthenticatedValue requestDataAuthenticated(final TicketAuthenticatorWrapper ticket) throws CouldNotPerformException { - printDebug("requestStatusAuthenticated of " + this); + logger.trace("requestStatusAuthenticated of " + this); // evaluate the ticket final AuthenticationBaseData authenticationBaseData = AuthenticatedServerManager.getInstance().verifyClientServerTicket(ticket); diff --git a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.java b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.java index 321ab3e1b7..6f5406d6b4 100644 --- a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.java +++ b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/com/AbstractAuthenticatedControllerServer.java @@ -97,7 +97,7 @@ public M requestStatus() throws CouldNotPerformException { @Override public AuthenticatedValue requestDataAuthenticated(final TicketAuthenticatorWrapper ticket) throws CouldNotPerformException { - printDebug("requestStatusAuthenticated of " + this); + logger.trace("requestStatusAuthenticated of " + this); // evaluate the ticket final AuthenticationBaseData authenticationBaseData = AuthenticatedServerManager.getInstance().verifyClientServerTicket(ticket); diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java index 9e19126148..9224444bdc 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java @@ -284,7 +284,6 @@ public void init(final UnitConfig config) throws InitializationException, Interr if (config == null) { throw new NotAvailableException("config"); } - printDebug("new scope is: " + ScopeProcessor.generateStringRep(config.getScope())); if (!config.hasId()) { throw new NotAvailableException("config.id"); From e4f2f358c103b66e951a9d7fe31e1d3873810c82 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 26 Jul 2023 21:59:42 +0200 Subject: [PATCH 16/70] link against latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 41b805754f..05bf6eea4d 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 41b805754fa996b76169c8f8edeb6335e3145d70 +Subproject commit 05bf6eea4df56e9185c78129f5d3c95f33a016d4 From 4cee8441b7403977e1dbc36b5fe5efb9e89a0b96 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 26 Jul 2023 22:40:31 +0200 Subject: [PATCH 17/70] update latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 05bf6eea4d..1cc37479b3 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 05bf6eea4df56e9185c78129f5d3c95f33a016d4 +Subproject commit 1cc37479b34df6c9931ec96f7725fc0d68904b1b From 56ca34f05f602072dc93482562b5d8c97d3e2941 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 26 Jul 2023 22:43:19 +0200 Subject: [PATCH 18/70] remove debug print --- .../bco/dal/control/layer/unit/AbstractUnitController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java index 9224444bdc..a13c39b949 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java @@ -263,7 +263,6 @@ protected long getShutdownDelay() { @Override public void init(ScopeType.Scope scope) throws InitializationException, InterruptedException { try { - printDebug("new scope is: " + ScopeProcessor.generateStringRep(scope)); super.init(Registries.getUnitRegistry(true).getUnitConfigByScope(scope)); } catch (CouldNotPerformException ex) { throw new InitializationException(this, ex); From 193d2b2de12b0031cff333d5e5a5eee3e4dde3bb Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sun, 30 Jul 2023 12:31:39 +0200 Subject: [PATCH 19/70] cleanup disabled and outdated gql code fragments. --- .../BcoGraphQlApiSpringBootApplication.kt | 197 +----------------- 1 file changed, 1 insertion(+), 196 deletions(-) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt index d5ceec9ddc..830e5f7b00 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt @@ -27,7 +27,6 @@ import org.openbase.bco.registry.remote.Registries import org.openbase.bco.registry.unit.lib.UnitRegistry import org.openbase.jul.exception.NotAvailableException import org.openbase.type.domotic.unit.UnitFilterType -import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Bean @@ -122,88 +121,12 @@ open class BcoGraphQlApiSpringBootApplication { SubscriptionModule.subscribeUnitConfigs(unitFilter, includeDisabledUnits) }) .build() - schema = GraphQLSchema.newSchema(schema) + return GraphQLSchema.newSchema(schema) .subscription(builder.build()) .codeRegistry(codeRegistry) .build() - - //final GraphQLObjectType.Builder queryTypeBuilder = GraphQLObjectType.newObject(schema.getQueryType()); - // final GraphQLObjectType.Builder mutationTypeBuilder = GraphQLObjectType.newObject(schema.getMutationType()); - //TODO: can I define that these arguments can not be null as in an SDL - //TODO: would the preferred way be to define these in an sdl? - /*queryTypeBuilder.field(GraphQLFieldDefinition.newFieldDefinition().name("login").type(Scalars.GraphQLString) - .argument(GraphQLArgument.newArgument().name("username").type(GraphQLNonNull.nonNull(Scalars.GraphQLString)).build()) - .argument(GraphQLArgument.newArgument().name("password").type(GraphQLNonNull.nonNull(Scalars.GraphQLString)).build()) - .build()); - mutationTypeBuilder.field(GraphQLFieldDefinition.newFieldDefinition().name("changePassword").type(Scalars.GraphQLBoolean) - .argument(GraphQLArgument.newArgument().name("username").type(GraphQLNonNull.nonNull(Scalars.GraphQLString)).build()) - .argument(GraphQLArgument.newArgument().name("oldPassword").type(GraphQLNonNull.nonNull(Scalars.GraphQLString)).build()) - .argument(GraphQLArgument.newArgument().name("newPassword").type(GraphQLNonNull.nonNull(Scalars.GraphQLString)).build()) - .build()); - - final GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry(schema.getCodeRegistry()) - .dataFetcher(FieldCoordinates.coordinates(schema.getQueryType().getName(), "login"), new DataFetcher() { - - @Override - public String get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception { - final String username = dataFetchingEnvironment.getArgument("username"); - final String password = dataFetchingEnvironment.getArgument("password"); - - try { - final String userId = Registries.getUnitRegistry().getUserUnitIdByUserName(username); - final SessionManager sessionManager = new SessionManager(); - sessionManager.loginUser(userId, password, false); - AuthenticatedValueType.AuthenticatedValue authenticatedValue = sessionManager.initializeRequest(AuthenticationTokenType.AuthenticationToken.newBuilder().setUserId(userId).build(), null); - String tokenValue = new AuthenticatedValueFuture<>(Registries.getUnitRegistry().requestAuthenticationTokenAuthenticated(authenticatedValue), - String.class, - authenticatedValue.getTicketAuthenticatorWrapper(), - sessionManager).get(ServerError.BCO_TIMEOUT_SHORT, ServerError.BCO_TIMEOUT_TIME_UNIT); - return tokenValue; - } catch (NotAvailableException ex) { - - throw new ArgumentError(ex); - } catch (Throwable ex) { - System.out.println("Which ex is thrown here? " + ex.getClass().getSimpleName() + ", " + ex.getMessage()); - throw new Exception(ex); - } - } - }) - .dataFetcher(FieldCoordinates.coordinates(schema.getMutationType().getName(), "changePassword"), new DataFetcher() { - @Override - public Boolean get(DataFetchingEnvironment dataFetchingEnvironment) throws Exception { - final String username = dataFetchingEnvironment.getArgument("username"); - final String oldPassword = dataFetchingEnvironment.getArgument("oldPassword"); - final String newPassword = dataFetchingEnvironment.getArgument("newPassword"); - - final String userId = Registries.getUnitRegistry().getUserUnitIdByUserName(username); - - final SessionManager sessionManager = new SessionManager(); - sessionManager.loginUser(userId, oldPassword, false); - sessionManager.changePassword(userId, oldPassword, newPassword).get(ServerError.BCO_TIMEOUT_SHORT, ServerError.BCO_TIMEOUT_TIME_UNIT); - - return true; - } - }) - .build(); - - - schema = GraphQLSchema.newSchema(schema) - .query(queryTypeBuilder.build()) - .mutation(mutationTypeBuilder.build()) - .codeRegistry(codeRegistry) - .build();*/return schema } - /*@Bean - public GraphQL graphQL() { - System.out.println("Add exec strategy.."); - return GraphQL.newGraphQL(schema()).subscriptionExecutionStrategy(new SubscriptionExecutionStrategy()).build(); - }*/ - // - // @Bean - // public GraphQL graphQL() { - // return GraphQL.newGraphQL(schemaProvider().getSchema()).build(); - // } @Bean open fun instrumentation(): Instrumentation { return GuavaListenableFutureSupport.listenableFutureInstrumentation() @@ -246,123 +169,5 @@ open class BcoGraphQlApiSpringBootApplication { ) } } - } // @Autowired - - // GraphQLDataFetchers graphQLDataFetchers; - // public static void main(String[] args) throws InterruptedException, CouldNotPerformException { - // String schema = "type Query{hello: String}"; - // - // SchemaParser schemaParser = new SchemaParser(); - // TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema); - // - // RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring() - // .type("Query", builder -> builder.dataFetcher("hello", new StaticDataFetcher("world"))) - // .build(); - // - // SchemaGenerator schemaGenerator = new SchemaGenerator(); - // GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring); - // - // GraphQL build = GraphQL.newGraphQL(new GraphQLProvider().buildSchema()).build(); - // ExecutionResult executionResult = build.execute("{bookById(id: \"book-1\"){name}}"); - // - // System.out.println(executionResult.getData().toString()); - // } - /*private static final ImmutableList STATIC_FIELD = - ImmutableList.of(newFieldDefinition().type(Scalars.GraphQLString).name("_").staticValue("-").build()); - - private static GraphQLFieldDefinition convertField( - Descriptors.FieldDescriptor fieldDescriptor, SchemaOptions schemaOptions) { - DataFetcher dataFetcher = new ProtoDataFetcher(fieldDescriptor); - GraphQLFieldDefinition.Builder builder = - newFieldDefinition() - .type(convertType(fieldDescriptor, schemaOptions)) - .dataFetcher(dataFetcher) - .name(fieldDescriptor.getJsonName()); - builder.description(schemaOptions.commentsMap().get(fieldDescriptor.getFullName())); - if (fieldDescriptor.getOptions().hasDeprecated() - && fieldDescriptor.getOptions().getDeprecated()) { - builder.deprecate("deprecated in proto"); - } - return builder.build(); - } - - static GraphQLObjectType convert( - Descriptors.Descriptor descriptor, - GraphQLInterfaceType nodeInterface) { - ImmutableList graphQLFieldDefinitions = - descriptor.getFields().stream() - .map(field -> convertField(field)) - .collect(toImmutableList()); - - // TODO: add back relay support - - // Optional relayId = - // descriptor.getFields().stream() - // .filter(field -> field.getOptions().hasExtension(RelayOptionsProto.relayOptions)) - // .map( - // field -> - // newFieldDefinition() - // .name("id") - // .type(new GraphQLNonNull(GraphQLID)) - // .description("Relay ID") - // .dataFetcher( - // data -> - // new Relay() - // .toGlobalId( - // getReferenceName(descriptor), - // data.getSource().getField(field).toString())) - // .build()) - // .findFirst(); - - // if (relayId.isPresent()) { - // return GraphQLObjectType.newObject() - // .name(getReferenceName(descriptor)) - // .withInterface(nodeInterface) - // .field(relayId.get()) - // .fields( - // graphQLFieldDefinitions - // .stream() - // .map( - // field -> - // field.getName().equals("id") - // ? GraphQLFieldDefinition.newFieldDefinition() - // .name("rawId") - // .description(field.getDescription()) - // .type(field.getType()) - // .dataFetcher(field.getDataFetcher()) - // .build() - // : field) - // .collect(ImmutableList.toImmutableList())) - // .build(); - // } - - return GraphQLObjectType.newObject() - .name(getReferenceName(descriptor)) - .fields(graphQLFieldDefinitions.isEmpty() ? STATIC_FIELD : graphQLFieldDefinitions) - .build(); - } - - static GraphQLEnumType convert( - Descriptors.EnumDescriptor descriptor) { - GraphQLEnumType.Builder builder = GraphQLEnumType.newEnum().name(getReferenceName(descriptor)); - for (Descriptors.EnumValueDescriptor value : descriptor.getValues()) { - builder.value( - value.getName(), - value.getName()); - } - return builder.build(); - } - - / ** Returns the GraphQL name of the supplied proto. */ - /*static String getReferenceName(Descriptors.GenericDescriptor descriptor) { - return CharMatcher.anyOf(".").replaceFrom(descriptor.getFullName(), "_"); - } - - / ** Returns a reference to the GraphQL type corresponding to the supplied proto. */ - /*static GraphQLTypeReference getReference(Descriptors.GenericDescriptor descriptor) { - return new GraphQLTypeReference(getReferenceName(descriptor)); - }*/ - companion object { - private val LOGGER = LoggerFactory.getLogger(BcoGraphQlApiSpringBootApplication::class.java) } } From 5427e0781cfd8827a31c7398f98c09ac07d03fe6 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Mon, 31 Jul 2023 02:07:03 +0200 Subject: [PATCH 20/70] Upgrade SSE dependencies. Stabelize Connection handling by force initiate reconnect after the SSE connection gets lost. Cleanup some deprecated code. --- module/device/openhab/build.gradle.kts | 13 ++-- .../OpenHABRestCommunicator.java | 27 +++---- .../communication/OpenHABRestConnection.java | 26 +++---- .../manager/service/OpenHABService.java | 77 ++----------------- .../lib/launch/AbstractRegistryLauncher.java | 4 +- versions.properties | 58 ++------------ 6 files changed, 48 insertions(+), 157 deletions(-) diff --git a/module/device/openhab/build.gradle.kts b/module/device/openhab/build.gradle.kts index 275940eb7f..15a8359c06 100644 --- a/module/device/openhab/build.gradle.kts +++ b/module/device/openhab/build.gradle.kts @@ -14,12 +14,13 @@ application { } dependencies { - api(project(":bco.dal.control")) - api("org.glassfish.jersey.core:jersey-client:_") - api("org.glassfish.jersey.inject:jersey-hk2:_") - api("org.glassfish.jersey.media:jersey-media-sse:_") - api("org.glassfish.jersey.security:oauth2-client:_") - api("org.openhab.core.bundles:org.openhab.core.io.rest.core:_") + implementation(project(":bco.dal.control")) + implementation("jakarta.activation:jakarta.activation-api:2.1.2") + implementation("org.glassfish.jersey.core:jersey-client:_") + implementation("org.glassfish.jersey.inject:jersey-hk2:_") + implementation("org.glassfish.jersey.media:jersey-media-sse:_") + implementation("org.glassfish.jersey.security:oauth2-client:_") + implementation("org.openhab.core.bundles:org.openhab.core.io.rest.core:_") } description = "BCO Openhab Device Manager" diff --git a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestCommunicator.java b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestCommunicator.java index 55aaea2927..97618bfd04 100644 --- a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestCommunicator.java +++ b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestCommunicator.java @@ -24,7 +24,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; +import jakarta.ws.rs.core.MediaType; import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.InitializationException; import org.openbase.jul.exception.InstantiationException; @@ -41,7 +43,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.MediaType; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -90,11 +91,11 @@ private OpenHABRestCommunicator() throws InstantiationException { // ========================================================================================================================================== public EnrichedThingDTO registerThing(final ThingDTO thingDTO) throws CouldNotPerformException { - return jsonToClass(jsonParser.parse(postJson(THINGS_TARGET, thingDTO)), EnrichedThingDTO.class); + return jsonToClass(JsonParser.parseString(postJson(THINGS_TARGET, thingDTO)), EnrichedThingDTO.class); } public EnrichedThingDTO updateThing(final EnrichedThingDTO enrichedThingDTO) throws CouldNotPerformException { - return jsonToClass(jsonParser.parse(putJson(THINGS_TARGET + SEPARATOR + enrichedThingDTO.UID, enrichedThingDTO)), EnrichedThingDTO.class); + return jsonToClass(JsonParser.parseString(putJson(THINGS_TARGET + SEPARATOR + enrichedThingDTO.UID, enrichedThingDTO)), EnrichedThingDTO.class); } public EnrichedThingDTO deleteThing(final EnrichedThingDTO enrichedThingDTO) throws CouldNotPerformException { @@ -102,19 +103,19 @@ public EnrichedThingDTO deleteThing(final EnrichedThingDTO enrichedThingDTO) thr } public EnrichedThingDTO deleteThing(final String thingUID) throws CouldNotPerformException { - return jsonToClass(jsonParser.parse(delete(THINGS_TARGET + SEPARATOR + thingUID)), EnrichedThingDTO.class); + return jsonToClass(JsonParser.parseString(delete(THINGS_TARGET + SEPARATOR + thingUID)), EnrichedThingDTO.class); } public EnrichedThingDTO getThing(final String thingUID) throws NotAvailableException { try { - return jsonToClass(jsonParser.parse(get(THINGS_TARGET + SEPARATOR + thingUID)), EnrichedThingDTO.class); + return jsonToClass(JsonParser.parseString(get(THINGS_TARGET + SEPARATOR + thingUID)), EnrichedThingDTO.class); } catch (CouldNotPerformException ex) { throw new NotAvailableException("Thing[" + thingUID + "]"); } } public List getThings() throws CouldNotPerformException { - return jsonElementToTypedList(jsonParser.parse(get(THINGS_TARGET)), EnrichedThingDTO.class); + return jsonElementToTypedList(JsonParser.parseString(get(THINGS_TARGET)), EnrichedThingDTO.class); } // ========================================================================================================================================== @@ -128,11 +129,11 @@ public ItemDTO registerItem(final ItemDTO itemDTO) throws CouldNotPerformExcepti } public List registerItems(final List itemDTOList) throws CouldNotPerformException { - return jsonElementToTypedList(jsonParser.parse(putJson(ITEMS_TARGET, itemDTOList)), ItemDTO.class); + return jsonElementToTypedList(JsonParser.parseString(putJson(ITEMS_TARGET, itemDTOList)), ItemDTO.class); } public ItemDTO updateItem(final ItemDTO itemDTO) throws CouldNotPerformException { - return jsonToClass(jsonParser.parse(putJson(ITEMS_TARGET + SEPARATOR + itemDTO.name, itemDTO)), ItemDTO.class); + return jsonToClass(JsonParser.parseString(putJson(ITEMS_TARGET + SEPARATOR + itemDTO.name, itemDTO)), ItemDTO.class); } public ItemDTO deleteItem(final ItemDTO itemDTO) throws CouldNotPerformException { @@ -141,16 +142,16 @@ public ItemDTO deleteItem(final ItemDTO itemDTO) throws CouldNotPerformException public ItemDTO deleteItem(final String itemName) throws CouldNotPerformException { LOGGER.warn("Delete item {}", itemName); - return jsonToClass(jsonParser.parse(delete(ITEMS_TARGET + SEPARATOR + itemName)), ItemDTO.class); + return jsonToClass(JsonParser.parseString(delete(ITEMS_TARGET + SEPARATOR + itemName)), ItemDTO.class); } public List getItems() throws CouldNotPerformException { - return jsonElementToTypedList(jsonParser.parse(get(ITEMS_TARGET)), EnrichedItemDTO.class); + return jsonElementToTypedList(JsonParser.parseString(get(ITEMS_TARGET)), EnrichedItemDTO.class); } public EnrichedItemDTO getItem(final String itemName) throws NotAvailableException { try { - return jsonToClass(jsonParser.parse(get(ITEMS_TARGET + SEPARATOR + itemName)), EnrichedItemDTO.class); + return jsonToClass(JsonParser.parseString(get(ITEMS_TARGET + SEPARATOR + itemName)), EnrichedItemDTO.class); } catch (CouldNotPerformException ex) { throw new NotAvailableException("Item with name[" + itemName + "]"); } @@ -194,7 +195,7 @@ public void deleteItemChannelLink(final String itemName, final String channelUID } public List getItemChannelLinks() throws CouldNotPerformException { - return jsonElementToTypedList(jsonParser.parse(get(LINKS_TARGET)), ItemChannelLinkDTO.class); + return jsonElementToTypedList(JsonParser.parseString(get(LINKS_TARGET)), ItemChannelLinkDTO.class); } // ========================================================================================================================================== @@ -222,7 +223,7 @@ public void approve(final String thingUID, final String label) throws CouldNotPe } public List getDiscoveryResults() throws CouldNotPerformException { - return jsonElementToTypedList(jsonParser.parse(get(INBOX_TARGET)), DiscoveryResultDTO.class); + return jsonElementToTypedList(JsonParser.parseString(get(INBOX_TARGET)), DiscoveryResultDTO.class); } // ========================================================================================================================================== diff --git a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestConnection.java b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestConnection.java index 34dbd5c94a..16ef2ccd5d 100644 --- a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestConnection.java +++ b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/communication/OpenHABRestConnection.java @@ -23,6 +23,15 @@ */ import com.google.gson.*; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.sse.InboundSseEvent; +import jakarta.ws.rs.sse.SseEventSource; import org.glassfish.jersey.client.oauth2.OAuth2ClientSupport; import org.openbase.bco.device.openhab.jp.JPOpenHABURI; import org.openbase.bco.registry.remote.Registries; @@ -48,16 +57,6 @@ import org.openhab.core.types.CommandDescription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.sse.InboundSseEvent; -import javax.ws.rs.sse.SseEventSource; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -89,7 +88,6 @@ public abstract class OpenHABRestConnection implements Shutdownable { private boolean shutdownInitiated = false; - protected final JsonParser jsonParser; protected final Gson gson; private ScheduledFuture connectionTask; @@ -114,7 +112,7 @@ public boolean shouldSkipClass(Class aClass) { return false; } }).create(); - this.jsonParser = new JsonParser(); + this.restClient = ClientBuilder.newClient(); try { restClient.register(OAuth2ClientSupport.feature(getToken())); @@ -230,7 +228,7 @@ private void initSSE() { final Consumer evenConsumer = inboundSseEvent -> { // dispatch event try { - final JsonObject payload = jsonParser.parse(inboundSseEvent.readData()).getAsJsonObject(); + final JsonObject payload = JsonParser.parseString(inboundSseEvent.readData()).getAsJsonObject(); for (Entry> topicObserverEntry : topicObservableMap.entrySet()) { try { if (payload.get(TOPIC_KEY).getAsString().matches(topicObserverEntry.getKey())) { @@ -251,7 +249,7 @@ private void initSSE() { }; final Runnable reconnectHandler = () -> { - checkConnectionState(); + setConnectState(State.RECONNECTING); }; sseSource.register(evenConsumer, errorHandler, reconnectHandler); diff --git a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/manager/service/OpenHABService.java b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/manager/service/OpenHABService.java index 6a0e46d6f2..ed5f92553a 100644 --- a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/manager/service/OpenHABService.java +++ b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/manager/service/OpenHABService.java @@ -37,9 +37,7 @@ import org.openbase.jul.exception.printer.LogLevel; import org.openbase.jul.processing.StringProcessor; import org.openbase.jul.schedule.FutureProcessor; -import org.openbase.jul.schedule.SyncObject; import org.openbase.type.domotic.action.ActionDescriptionType.ActionDescription; -import org.openbase.type.domotic.binding.openhab.OpenhabCommandType; import org.openbase.type.domotic.service.ServiceConfigType.ServiceConfig; import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate; import org.openhab.core.types.Command; @@ -54,27 +52,24 @@ public abstract class OpenHABService> implements Se private final Logger logger = LoggerFactory.getLogger(getClass()); private final String itemName; private final ServiceTemplate.ServiceType serviceType; - private final ServiceConfig config; - private final Future[] repeatCommandTasks; - private final SyncObject repeatLastCommandMonitor = new SyncObject("RepeatLastCommandMonitor"); - protected OpenhabCommandType.OpenhabCommand.Builder lastCommand; public OpenHABService(final ST unit) throws InstantiationException { try { this.unit = unit; - this.repeatCommandTasks = new Future[2]; this.serviceType = detectServiceType(); - this.config = loadServiceConfig(); + + loadServiceConfig(); + this.itemName = OpenHABItemProcessor.generateItemName(unit.getConfig(), serviceType); } catch (CouldNotPerformException ex) { throw new InstantiationException(this, ex); } } - private ServiceConfig loadServiceConfig() throws CouldNotPerformException { + private void loadServiceConfig() throws CouldNotPerformException { for (final ServiceConfig serviceConfig : unit.getConfig().getServiceConfigList()) { if (serviceConfig.getServiceDescription().getServiceType().equals(serviceType)) { - return serviceConfig; + return; } } throw new CouldNotPerformException("Could not detect service config! Service[" + serviceType.name() + "] is not configured in Unit[" + ((Unit) unit).getId() + "]!"); @@ -120,74 +115,14 @@ public Future setState(final Message serviceState) { } } - return FutureProcessor.completedFuture(ServiceStateProcessor.getResponsibleAction(serviceState, () -> ActionDescription.getDefaultInstance())); + return FutureProcessor.completedFuture(ServiceStateProcessor.getResponsibleAction(serviceState, ActionDescription::getDefaultInstance)); } catch (CouldNotPerformException ex) { return FutureProcessor.canceledFuture(ActionDescription.class, ex); } } -// public Future executeCommand(final Command... commands) { -// if (itemName == null) { -// throw new NotAvailableException("itemID"); -// } -// -// try { -// for (final Command command : commands) { -// OpenHABRestCommunicator.getInstance().postCommand(itemName, command.toString()); -// } -// } catch (CouldNotPerformException ex) { -// if (ex.getCause() instanceof NotAvailableException) { -// throw new CouldNotPerformException("Thing may not be configured or openHAB not reachable", ex); -// } -// throw ex; -// } -// return FutureProcessor.completedFuture(null); -// } - @Override public ServiceProvider getServiceProvider() { return unit; } - - /** - * Method repeats the given command in 5 seconds. - * Make sure the last command is always stored into the {@code lastCommand} variable. - */ -// public void repeatLastCommand() { -// synchronized (repeatLastCommandMonitor) { -// -// // cancel still running tasks -// if (repeatCommandTasks[REPEAT_TASK_1] != null && !repeatCommandTasks[REPEAT_TASK_1].isDone()) { -// // cancel if still scheduled but do to cancel if already executing. -// repeatCommandTasks[REPEAT_TASK_1].cancel(false); -// } -// if (repeatCommandTasks[REPEAT_TASK_2] != null && !repeatCommandTasks[REPEAT_TASK_2].isDone()) { -// // cancel if still scheduled but do to cancel if already executing. -// repeatCommandTasks[REPEAT_TASK_2].cancel(false); -// } -// -// // this is just a bug workaround because the philip hues are sometimes skip events. -// // So to make sure they are controlled like expected we repeat the command twice. -// -// // init repeat 1 -// repeatCommandTasks[REPEAT_TASK_1] = GlobalScheduledExecutorService.schedule(() -> { -// try { -// executeCommand(lastCommand).get(ACTION_EXECUTION_TIMEOUT, TimeUnit.SECONDS); -// logger.info("repeat successfully command[" + lastCommand + "]"); -// } catch (Exception ex) { -// ExceptionPrinter.printHistory("Could not repeat openhab command!", ex, logger); -// } -// }, ACTION_EXECUTION_REPEAT_DELAY_1, TimeUnit.MILLISECONDS); -// -// // init repeat 2 -// repeatCommandTasks[REPEAT_TASK_2] = GlobalScheduledExecutorService.schedule(() -> { -// try { -// executeCommand(lastCommand).get(ACTION_EXECUTION_TIMEOUT, TimeUnit.SECONDS); -// logger.info("repeat successfully command[" + lastCommand + "]"); -// } catch (Exception ex) { -// ExceptionPrinter.printHistory("Could not repeat openhab command!", ex, logger); -// } -// }, ACTION_EXECUTION_REPEAT_DELAY_2, TimeUnit.MILLISECONDS); -// } -// } } diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java index 2661b0bfe8..f9b11bbef3 100644 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java @@ -31,9 +31,9 @@ * @param * @author Divine Threepwood */ -public abstract class AbstractRegistryLauncher extends AbstractLauncher { +public abstract class AbstractRegistryLauncher> extends AbstractLauncher { - public AbstractRegistryLauncher(Class applicationClass, Class launchableClass) throws InstantiationException { + public AbstractRegistryLauncher(Class applicationClass, Class launchableClass) throws InstantiationException { super(applicationClass, launchableClass); } diff --git a/versions.properties b/versions.properties index eb13ab729b..85d7b468b7 100644 --- a/versions.properties +++ b/versions.properties @@ -32,57 +32,13 @@ version.mockk=1.13.4 version.org.jmdns..jmdns=3.5.7 -version.org.glassfish.jersey.security..oauth2-client=2.31 -## # available=2.32 -## # available=2.33 -## # available=2.34 -## # available=2.35 -## # available=3.0.0-M1 -## # available=3.0.0-M6 -## # available=3.0.0-RC2 -## # available=3.0.0 -## # available=3.0.1 -## # available=3.0.2 -## # available=3.0.3 - -version.org.glassfish.jersey.media..jersey-media-sse=2.31 -## # available=2.32 -## # available=2.33 -## # available=2.34 -## # available=2.35 -## # available=3.0.0-M1 -## # available=3.0.0-M6 -## # available=3.0.0-RC2 -## # available=3.0.0 -## # available=3.0.1 -## # available=3.0.2 -## # available=3.0.3 - -version.org.glassfish.jersey.inject..jersey-hk2=2.31 -## # available=2.32 -## # available=2.33 -## # available=2.34 -## # available=2.35 -## # available=3.0.0-M1 -## # available=3.0.0-M6 -## # available=3.0.0-RC2 -## # available=3.0.0 -## # available=3.0.1 -## # available=3.0.2 -## # available=3.0.3 - -version.org.glassfish.jersey.core..jersey-client=2.31 -## # available=2.32 -## # available=2.33 -## # available=2.34 -## # available=2.35 -## # available=3.0.0-M1 -## # available=3.0.0-M6 -## # available=3.0.0-RC2 -## # available=3.0.0 -## # available=3.0.1 -## # available=3.0.2 -## # available=3.0.3 +version.org.glassfish.jersey.security..oauth2-client=3.1.3 + +version.org.glassfish.jersey.media..jersey-media-sse=3.1.3 + +version.org.glassfish.jersey.inject..jersey-hk2=3.1.3 + +version.org.glassfish.jersey.core..jersey-client=3.1.3 version.org.apache.commons..commons-math3=3.6.1 From 7030aa5d6d49643bd6f398a92821ecf8c560ec60 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 1 Aug 2023 18:38:36 +0200 Subject: [PATCH 21/70] switch to latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index c042bac636..f1c3e42404 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit c042bac636025c440dfb5eaf0cdbd0f3b7f767a2 +Subproject commit f1c3e42404d54552a62776345e7ee1932c0a1f75 From 66c8b9cc5396007bd9f573e55a1997723bd7f273 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 19:18:41 +0200 Subject: [PATCH 22/70] establish jul compatibility again. --- .../bco/registry/lib/launch/AbstractRegistryLauncher.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java index 2661b0bfe8..b716eb475b 100644 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -29,9 +29,10 @@ /** * @param + * * @author Divine Threepwood */ -public abstract class AbstractRegistryLauncher extends AbstractLauncher { +public abstract class AbstractRegistryLauncher> extends AbstractLauncher { public AbstractRegistryLauncher(Class applicationClass, Class launchableClass) throws InstantiationException { super(applicationClass, launchableClass); From b5c292212bd9f26fc1e3eb1a4de84922c9ce3ced Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 19:23:45 +0200 Subject: [PATCH 23/70] reestablish jul compatebility --- lib/jul | 2 +- .../bco/registry/lib/launch/AbstractRegistryLauncher.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jul b/lib/jul index f1c3e42404..630cb342bb 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit f1c3e42404d54552a62776345e7ee1932c0a1f75 +Subproject commit 630cb342bb2335b60b7b9703028987751e68a798 diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java index b716eb475b..4994c437d5 100644 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/launch/AbstractRegistryLauncher.java @@ -34,7 +34,7 @@ */ public abstract class AbstractRegistryLauncher> extends AbstractLauncher { - public AbstractRegistryLauncher(Class applicationClass, Class launchableClass) throws InstantiationException { + public AbstractRegistryLauncher(Class applicationClass, Class launchableClass) throws InstantiationException { super(applicationClass, launchableClass); } From 8a5076eea02bdfb406d0fb309e7cf8fda7392d03 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 19:35:58 +0200 Subject: [PATCH 24/70] setup pr branch auto update --- .github/workflows/auto-branch-update.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/auto-branch-update.yaml diff --git a/.github/workflows/auto-branch-update.yaml b/.github/workflows/auto-branch-update.yaml new file mode 100644 index 0000000000..32924ac4ce --- /dev/null +++ b/.github/workflows/auto-branch-update.yaml @@ -0,0 +1,17 @@ +name: "Autoupdate PR branches" + +on: + push: + branches: + - dev + - "epic/**" + +jobs: + autoupdate: + name: autoupdate + runs-on: ubuntu-22.04 + steps: + - uses: docker://chinthakagodawita/autoupdate-action:v1 + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + MERGE_CONFLICT_ACTION: "fail" From 2795e614553a2f6bb776848be1feffa4685a8c6d Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 19:38:58 +0200 Subject: [PATCH 25/70] configure auto update. --- .github/workflows/auto-branch-update.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-branch-update.yaml b/.github/workflows/auto-branch-update.yaml index 32924ac4ce..28e0e7923f 100644 --- a/.github/workflows/auto-branch-update.yaml +++ b/.github/workflows/auto-branch-update.yaml @@ -14,4 +14,4 @@ jobs: - uses: docker://chinthakagodawita/autoupdate-action:v1 env: GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' - MERGE_CONFLICT_ACTION: "fail" + MERGE_CONFLICT_ACTION: "ignore" From 4a715c6762449c232090a7a4fb11bbcd60eeabbc Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 20:40:05 +0200 Subject: [PATCH 26/70] fix version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2359f0396c..3cc1dcf6cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version = 3.1-SNAPSHOT +version = 3.1.1 org.gradle.caching = true org.gradle.parallel = false org.gradle.daemon = true From 11a83fec3211972f0c35f4b820434412bb466bbd Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 21:07:14 +0200 Subject: [PATCH 27/70] setup review docker deployment --- .github/workflows/deploy-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 5ac31ca543..667076f69a 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -33,8 +33,8 @@ jobs: # generate Docker tags based on the following events/attributes tags: | type=schedule + type=ref,value=review,enable=${{ contains(github.event.pull_request.labels.*.name, 'prebuild docker image') }} type=ref,event=branch - type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} From 158718315e17a1e3e963b59804b6f5ac9ac341bf Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 21:47:23 +0200 Subject: [PATCH 28/70] make unit tests compile again. --- .../bco/dal/test/layer/unit/connection/ConnectionRemoteTest.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/connection/ConnectionRemoteTest.kt b/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/connection/ConnectionRemoteTest.kt index 92f0814f0c..7397ef8a6a 100644 --- a/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/connection/ConnectionRemoteTest.kt +++ b/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/connection/ConnectionRemoteTest.kt @@ -1,7 +1,6 @@ package org.openbase.bco.dal.test.layer.unit.connection import io.kotest.matchers.comparables.shouldBeEqualComparingTo -import org.junit.jupiter.api.RepeatedTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.openbase.bco.dal.lib.state.States @@ -53,7 +52,7 @@ class ConnectionRemoteTest : AbstractBCOLocationManagerTest() { .getUnitConfigByAlias(MockRegistry.ALIAS_REED_SWITCH_HEAVEN_STAIRWAY_GATE) val reedContactController = reedContactConfig - .let { deviceManagerLauncher.launchable.unitControllerRegistry[it.id] } + .let { deviceManagerLauncher.launchable!!.unitControllerRegistry[it.id] } val reedContact = reedContactConfig .let { Units.getUnit(it, true, Units.REED_CONTACT) } From 62a00a19738802af6ff9f4e0778b498d6798191f Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 22:35:00 +0200 Subject: [PATCH 29/70] fix docker build --- .github/workflows/deploy-docker.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 667076f69a..c28df5efcd 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -33,8 +33,9 @@ jobs: # generate Docker tags based on the following events/attributes tags: | type=schedule - type=ref,value=review,enable=${{ contains(github.event.pull_request.labels.*.name, 'prebuild docker image') }} + type=raw,value=review,enable=${{ contains(github.event.pull_request.labels.*.name, 'prebuild docker image') }} type=ref,event=branch + type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}} From 8226f54aa1a2ba1f1af557b352b7f4d9e827eb6b Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 3 Aug 2023 22:53:56 +0200 Subject: [PATCH 30/70] fix concurrent modification of remote instances. --- .../java/org/openbase/bco/dal/remote/action/RemoteAction.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java index 8f0bcb61ea..5bdb822d1b 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java @@ -56,6 +56,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.*; @@ -73,7 +74,7 @@ public class RemoteAction implements Action { private final ActionParameter actionParameter; private final ObservableImpl actionDescriptionObservable; private final AuthToken authToken; - private final List impactedRemoteActions = new ArrayList<>(); + private final List impactedRemoteActions = Collections.synchronizedList(new ArrayList<>()); private final Observer impactActionObserver = new Observer() { @Override public void update(RemoteAction source, ActionDescription observable) throws Exception { From 75f064a1af99dd7065a0c4959a3563b4db07fbd8 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Fri, 4 Aug 2023 00:10:34 +0200 Subject: [PATCH 31/70] enable review tag for all version --- .github/workflows/deploy-docker.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index c28df5efcd..28629b6887 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -83,6 +83,7 @@ jobs: # generate Docker tags based on the following events/attributes tags: | type=schedule + type=raw,value=review,enable=${{ contains(github.event.pull_request.labels.*.name, 'prebuild docker image') }} type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} @@ -116,6 +117,7 @@ jobs: # generate Docker tags based on the following events/attributes tags: | type=schedule + type=raw,value=review,enable=${{ contains(github.event.pull_request.labels.*.name, 'prebuild docker image') }} type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} From 3b77fb190e815d128e08294009fc90f9f776be9d Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sun, 6 Aug 2023 14:31:48 +0200 Subject: [PATCH 32/70] link jul to feature branch --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 630cb342bb..f5ecc08665 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 630cb342bb2335b60b7b9703028987751e68a798 +Subproject commit f5ecc08665d8ea67aaa941462ef2f08899094638 From 70e8762c81bbdb30609f4640792eec5a806632ac Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sun, 6 Aug 2023 14:43:51 +0200 Subject: [PATCH 33/70] update gradle and refresh plugin --- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 ++- gradlew | 12 ++++++++---- gradlew.bat | 1 + settings.gradle.kts | 2 +- versions.properties | 2 +- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36524 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%0DGK?7GHTj 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 VwlMN}`%er%aGR6olj~j${vQ;P=LY}) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb702f0..17a8ddce2d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb6c2..65dcd68d65 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,10 +80,10 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' @@ -143,12 +143,16 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/gradlew.bat b/gradlew.bat index 53a6b238d4..6689b85bee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/settings.gradle.kts b/settings.gradle.kts index b120ccbdb4..210d260513 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -114,7 +114,7 @@ includeBuild("lib/jul") pluginManagement { plugins { - id("de.fayard.refreshVersions") version "0.40.2" + id("de.fayard.refreshVersions") version "0.51.0" } } diff --git a/versions.properties b/versions.properties index 85d7b468b7..494a89132a 100644 --- a/versions.properties +++ b/versions.properties @@ -1,5 +1,5 @@ #### Dependencies and Plugin versions with their available updates. -#### Generated by `./gradlew refreshVersions` version 0.40.2 +#### Generated by `./gradlew refreshVersions` version 0.51.0 #### #### Don't manually edit or split the comments that start with four hashtags (####), #### they will be overwritten by refreshVersions. From ae949e90deea366b0f4ee9b46554271dad8a6b8b Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sun, 6 Aug 2023 14:45:21 +0200 Subject: [PATCH 34/70] update jul --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index f5ecc08665..7ecb3b1329 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit f5ecc08665d8ea67aaa941462ef2f08899094638 +Subproject commit 7ecb3b1329453af680d816326845349dbb3daade From 25701c1ab421ec54b8abe2c8f12b33945cd8f132 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Sun, 6 Aug 2023 17:11:54 +0200 Subject: [PATCH 35/70] update deps --- gradle.properties | 10 +- lib/jul | 2 +- module/api/graphql/build.gradle.kts | 10 +- .../graphql/BcoApiGraphQlSpringLaunchable.kt | 17 ++-- .../BcoGraphQlApiSpringBootApplication.kt | 12 ++- .../context/BCOGraphQLWebsocketContext.kt | 4 +- .../context/DefaultBCOGraphQLContext.kt | 2 +- .../graphql/discovery/ServiceAdvertiser.kt | 72 +++++++------- .../graphql/discovery/ServiceDiscoveryDemo.kt | 83 ---------------- .../subscriptions/AbstractObserverMapper.kt | 8 +- .../src/main/resources/application.yaml | 2 +- module/app/base/build.gradle.kts | 2 +- .../bco/app/cloudconnector/SocketWrapper.java | 26 ++--- module/app/util/build.gradle.kts | 2 +- .../app/util/launch/BCOSystemValidator.java | 2 +- settings.gradle.kts | 20 ++-- versions.properties | 99 ++++--------------- 17 files changed, 110 insertions(+), 263 deletions(-) delete mode 100644 module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceDiscoveryDemo.kt diff --git a/gradle.properties b/gradle.properties index 3cc1dcf6cf..cdc9192c92 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ -version = 3.1.1 -org.gradle.caching = true -org.gradle.parallel = false -org.gradle.daemon = true -org.gradle.jvmargs = -Xmx2000m +version=3.1.2 +org.gradle.caching=true +org.gradle.parallel=false +org.gradle.daemon=true +org.gradle.jvmargs=-Xmx2000m diff --git a/lib/jul b/lib/jul index 7ecb3b1329..8a1164e1b6 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 7ecb3b1329453af680d816326845349dbb3daade +Subproject commit 8a1164e1b699331156a99b49786bf8b083f636cf diff --git a/module/api/graphql/build.gradle.kts b/module/api/graphql/build.gradle.kts index da062d47d8..c74a1de7bc 100644 --- a/module/api/graphql/build.gradle.kts +++ b/module/api/graphql/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("org.openbase.bco") id("org.springframework.boot") - id("io.spring.dependency-management") version "1.0.11.RELEASE" + id("io.spring.dependency-management") version "1.1.2" } dependencies { @@ -12,12 +12,17 @@ dependencies { exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") } api("org.springframework.boot:spring-boot-starter-jetty:_") - implementation("org.springframework.boot:spring-boot-starter-websocket:_") { + api("org.springframework.boot:spring-boot-starter-websocket:_") { exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") } api(Spring.boot.webflux) api("org.springframework:spring-webmvc:_") + api("org.eclipse.jetty:jetty-server:11.0.14") + api("jakarta.servlet:jakarta.servlet-api:6.0.0") + + + api(rootProject.files("lib/external/rejoiner-0.5.0-bco.jar")) api(rootProject.files("lib/external/rejoiner-guice-0.5.0-bco.jar")) // disabled since rejoiner is linked locally. @@ -35,7 +40,6 @@ dependencies { api("com.google.inject:guice:_") api("com.google.guava:guava:_") api("net.javacrumbs.future-converter:future-converter-java8-guava:_") - api("org.jmdns:jmdns:_") api(ReactiveX.rxJava2) } diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoApiGraphQlSpringLaunchable.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoApiGraphQlSpringLaunchable.kt index 7873332a77..2ced63f922 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoApiGraphQlSpringLaunchable.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoApiGraphQlSpringLaunchable.kt @@ -11,7 +11,6 @@ import org.slf4j.LoggerFactory import org.springframework.boot.SpringApplication import org.springframework.context.ConfigurableApplicationContext import java.util.* -import javax.jmdns.ServiceInfo /*- * #%L @@ -55,19 +54,19 @@ import javax.jmdns.ServiceInfo LOGGER.info("Start webserver...") context = SpringApplication.run(BcoGraphQlApiSpringBootApplication::class.java, *JPService.getArgs()) LOGGER.info("Advertise graphql service...") - val qualifiedNameMap = HashMap() - qualifiedNameMap[ServiceInfo.Fields.Application] = "http" - qualifiedNameMap[ServiceInfo.Fields.Instance] = "graphql-bco-openbase" - qualifiedNameMap[ServiceInfo.Fields.Subtype] = "graphql" +// val qualifiedNameMap = HashMap() +// qualifiedNameMap[ServiceInfo.Fields.Application] = "http" +// qualifiedNameMap[ServiceInfo.Fields.Instance] = "graphql-bco-openbase" +// qualifiedNameMap[ServiceInfo.Fields.Subtype] = "graphql" val propertyMap = HashMap() propertyMap["bco-uuid"] = UUID.randomUUID().toString() propertyMap["path"] = "graphql" // lookup port - context?.getEnvironment()?.getProperty("server.port")?.toInt()?.let { port -> - // register service advertising - serviceAdvertiser!!.register(qualifiedNameMap, port, 0, 0, false, propertyMap) - } +// context?.getEnvironment()?.getProperty("server.port")?.toInt()?.let { port -> +// // register service advertising +// serviceAdvertiser!!.register(qualifiedNameMap, port, 0, 0, false, propertyMap) +// } } override fun deactivate() { diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt index 830e5f7b00..6cb6233d6e 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt @@ -1,7 +1,9 @@ package org.openbase.bco.api.graphql import com.google.api.graphql.execution.GuavaListenableFutureSupport -import com.google.api.graphql.rejoiner.* +import com.google.api.graphql.rejoiner.GqlInputConverter +import com.google.api.graphql.rejoiner.Schema +import com.google.api.graphql.rejoiner.SchemaProviderModule import com.google.inject.Guice import com.google.inject.Injector import com.google.inject.Key @@ -12,6 +14,10 @@ import graphql.kickstart.execution.context.DefaultGraphQLContext import graphql.kickstart.execution.context.GraphQLKickstartContext import graphql.kickstart.servlet.context.GraphQLServletContextBuilder import graphql.schema.* +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import jakarta.websocket.Session +import jakarta.websocket.server.HandshakeRequest import org.dataloader.DataLoader import org.dataloader.DataLoaderRegistry import org.openbase.bco.api.graphql.batchloader.BCOUnitBatchLoader @@ -30,10 +36,6 @@ import org.openbase.type.domotic.unit.UnitFilterType import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.context.annotation.Bean -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse -import javax.websocket.Session -import javax.websocket.server.HandshakeRequest /*- * #%L diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/BCOGraphQLWebsocketContext.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/BCOGraphQLWebsocketContext.kt index 28519f4c65..9d96a1376a 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/BCOGraphQLWebsocketContext.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/BCOGraphQLWebsocketContext.kt @@ -1,10 +1,10 @@ package org.openbase.bco.api.graphql.context import graphql.kickstart.execution.context.GraphQLKickstartContext +import jakarta.websocket.Session +import jakarta.websocket.server.HandshakeRequest import org.dataloader.DataLoaderRegistry import java.util.* -import javax.websocket.Session -import javax.websocket.server.HandshakeRequest /*- * #%L diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/DefaultBCOGraphQLContext.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/DefaultBCOGraphQLContext.kt index 8353c4cb83..fd07caacb3 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/DefaultBCOGraphQLContext.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/DefaultBCOGraphQLContext.kt @@ -1,8 +1,8 @@ package org.openbase.bco.api.graphql.context +import jakarta.servlet.http.HttpServletRequest import org.dataloader.DataLoaderRegistry import java.util.* -import javax.servlet.http.HttpServletRequest /*- * #%L diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceAdvertiser.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceAdvertiser.kt index cb4ec55219..a68764e2fd 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceAdvertiser.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceAdvertiser.kt @@ -1,18 +1,14 @@ package org.openbase.bco.api.graphql.discovery import org.openbase.jps.core.JPService -import org.openbase.jul.exception.CouldNotPerformException import org.openbase.jul.exception.InstantiationException import org.openbase.jul.iface.Shutdownable import org.slf4j.LoggerFactory -import java.io.IOException import java.net.InetAddress import java.net.NetworkInterface import java.net.SocketException import java.net.UnknownHostException import java.util.* -import javax.jmdns.JmDNS -import javax.jmdns.ServiceInfo /*- @@ -36,10 +32,10 @@ import javax.jmdns.ServiceInfo * . * #L% */ class ServiceAdvertiser private constructor() : Shutdownable { - private val domainNameServices: List +// private val domainNameServices: List init { - domainNameServices = ArrayList() +// domainNameServices = ArrayList() // skip advertising in debug mode if (!JPService.debugMode()) { @@ -69,40 +65,40 @@ import javax.jmdns.ServiceInfo } } - @Throws(CouldNotPerformException::class) - fun register( - qualifiedNameMap: HashMap?, - port: Int, - weight: Int, - priority: Int, - persistent: Boolean, - props: Map?, - ): List { - return try { - val serviceInfoList: MutableList = ArrayList() - for (domainNameService in domainNameServices) { - - // Register the service - val serviceInfo = ServiceInfo.create(qualifiedNameMap, port, weight, priority, false, props) - domainNameService.registerService(serviceInfo) - serviceInfoList.add(serviceInfo) - } - serviceInfoList - } catch (ex: IOException) { - throw CouldNotPerformException("Could not register service!", ex) - } - } - - fun deregisterService(serviceInfo: ServiceInfo?) { - for (domainNameService in domainNameServices) { - domainNameService.unregisterService(serviceInfo) - } - } +// @Throws(CouldNotPerformException::class) +// fun register( +// qualifiedNameMap: HashMap?, +// port: Int, +// weight: Int, +// priority: Int, +// persistent: Boolean, +// props: Map?, +// ): List { +// return try { +// val serviceInfoList: MutableList = ArrayList() +// for (domainNameService in domainNameServices) { +// +// // Register the service +// val serviceInfo = ServiceInfo.create(qualifiedNameMap, port, weight, priority, false, props) +// domainNameService.registerService(serviceInfo) +// serviceInfoList.add(serviceInfo) +// } +// serviceInfoList +// } catch (ex: IOException) { +// throw CouldNotPerformException("Could not register service!", ex) +// } +// } + +// fun deregisterService(serviceInfo: ServiceInfo?) { +// for (domainNameService in domainNameServices) { +// domainNameService.unregisterService(serviceInfo) +// } +// } override fun shutdown() { - for (domainNameService in domainNameServices) { - domainNameService.unregisterAllServices() - } +// for (domainNameService in domainNameServices) { +// domainNameService.unregisterAllServices() +// } } companion object { diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceDiscoveryDemo.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceDiscoveryDemo.kt deleted file mode 100644 index 0d10a4b94f..0000000000 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/discovery/ServiceDiscoveryDemo.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.openbase.bco.api.graphql.discovery - -import org.openbase.jul.processing.StringProcessor -import java.io.IOException -import java.net.InetAddress -import java.net.UnknownHostException -import java.util.* -import javax.jmdns.JmDNS -import javax.jmdns.ServiceEvent -import javax.jmdns.ServiceListener - -/*- - * #%L - * BCO GraphQL API - * %% - * Copyright (C) 2020 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ object ServiceDiscoveryDemo { - @Throws(InterruptedException::class) - fun disabledMain(args: Array?) { - try { - // Create a JmDNS instance - val jmdns = JmDNS.create(InetAddress.getLocalHost()) - - // Add a service listener - jmdns.addServiceListener("_http._tcp.local.", SampleListener("graphql-bco-openbase")) - - // Wait a bit - Thread.sleep(30000) - } catch (e: UnknownHostException) { - println(e.message) - } catch (e: IOException) { - println(e.message) - } - } - - private class SampleListener : ServiceListener { - private val serviceNameFilter: String? - - constructor(serviceNameFilter: String?) { - this.serviceNameFilter = serviceNameFilter - } - - constructor() { - serviceNameFilter = null - } - - override fun serviceAdded(event: ServiceEvent) {} - override fun serviceRemoved(event: ServiceEvent) { - if (serviceNameFilter == null || event.name == serviceNameFilter) { - println( - "Offline: " + event.name + "@" + event.info.server + ":" + event.info.port + " - " + StringProcessor.transformCollectionToString( - Arrays.asList(*event.info.hostAddresses.clone()), ", " - ) - ) - } - } - - override fun serviceResolved(event: ServiceEvent) { - if (serviceNameFilter == null || event.name == serviceNameFilter) { - println( - "Online: " + event.name + "@" + event.info.server + ":" + event.info.port + " - " + StringProcessor.transformCollectionToString( - Arrays.asList(*event.info.hostAddresses.clone()), ", " - ) - ) - } - } - } -} diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/AbstractObserverMapper.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/AbstractObserverMapper.kt index a8230f6d6c..602e346f8f 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/AbstractObserverMapper.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/AbstractObserverMapper.kt @@ -26,8 +26,9 @@ import java.util.function.Consumer * License along with this program. If not, see * . * #L% - */ abstract class AbstractObserverMapper : Observer { + */ abstract class AbstractObserverMapper : Observer { private var emitter: ObservableEmitter? = null + @Throws(Exception::class) override fun update(source: S, target: T) { if (emitter == null) { @@ -42,6 +43,7 @@ import java.util.function.Consumer @Throws(Exception::class) abstract fun mapData(source: S, data: T): E + @Throws(CouldNotPerformException::class) open fun doAfterRemoveObserver() { } @@ -51,10 +53,10 @@ import java.util.function.Consumer } companion object { - fun createObservable( + fun createObservable( addObserver: Consumer>, removeObserver: Consumer>, - obs: AbstractObserverMapper + obs: AbstractObserverMapper, ): Observable { var observable = Observable.create { emitter: ObservableEmitter? -> obs.setEmitter(emitter) diff --git a/module/api/graphql/src/main/resources/application.yaml b/module/api/graphql/src/main/resources/application.yaml index 486cb8af8a..d24a777ae9 100644 --- a/module/api/graphql/src/main/resources/application.yaml +++ b/module/api/graphql/src/main/resources/application.yaml @@ -17,4 +17,4 @@ graphql: allowed-methods: "*" allowed-headers: "*" # if you want to @ExceptionHandler annotation for custom GraphQLErrors - exception-handlers-enabled: true \ No newline at end of file + exception-handlers-enabled: true diff --git a/module/app/base/build.gradle.kts b/module/app/base/build.gradle.kts index ef00c97fcb..307037bf4b 100644 --- a/module/app/base/build.gradle.kts +++ b/module/app/base/build.gradle.kts @@ -53,7 +53,7 @@ dependencies { api(project(":bco.app.cloud.connector")) api(project(":bco.app.influxdb.connector")) api(project(":bco.api.graphql")) - api("commons-collections:commons-collections:_") + api("org.apache.commons:commons-collections4:_") testImplementation(project(":bco.dal.test")) } diff --git a/module/app/cloudconnector/src/main/java/org/openbase/bco/app/cloudconnector/SocketWrapper.java b/module/app/cloudconnector/src/main/java/org/openbase/bco/app/cloudconnector/SocketWrapper.java index 1973d98d54..45d617d75f 100644 --- a/module/app/cloudconnector/src/main/java/org/openbase/bco/app/cloudconnector/SocketWrapper.java +++ b/module/app/cloudconnector/src/main/java/org/openbase/bco/app/cloudconnector/SocketWrapper.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -25,7 +25,6 @@ import com.google.gson.*; import io.socket.client.Ack; import io.socket.client.IO; -import io.socket.client.IO.Options; import io.socket.client.Manager; import io.socket.client.Socket; import io.socket.engineio.client.Transport; @@ -165,14 +164,15 @@ public void init() throws InitializationException { } }); }); + // add listener to socket events socket.on(Socket.EVENT_CONNECT, objects -> { // when socket is connected LOGGER.info("Socket of user[" + userId + "] connected"); login(); - }).on(Socket.EVENT_MESSAGE, objects -> { - // handle request - handleRequest(objects[0], (Ack) objects[objects.length - 1]); +// }).on(Socket.EVENT_MESSAGE, objects -> { // todo this seems to be not supported anymore +// // handle request +// handleRequest(objects[0], (Ack) objects[objects.length - 1]); }).on(Socket.EVENT_DISCONNECT, objects -> { // reconnection is automatically done by the socket API, just print that disconnected LOGGER.info("Socket of user[" + userId + "] disconnected"); @@ -188,16 +188,8 @@ public void init() throws InitializationException { handleRelocating(objects[0], (Ack) objects[objects.length - 1]); }).on(INTENT_RENAMING, objects -> { handleRenaming(objects[0], (Ack) objects[objects.length - 1]); - }).on(Socket.EVENT_RECONNECT_ATTEMPT, objects -> { - LOGGER.debug("Attempt to reconnect socket of user {}", userId); - }).on(Socket.EVENT_RECONNECT_ERROR, objects -> { - LOGGER.debug("Reconnection error for socket of user {} because {}", userId, objects.length > 0 ? objects[0] : 1); - }).on(Socket.EVENT_RECONNECT_FAILED, objects -> { - LOGGER.debug("Reconnection failed for socket of user {} because {}", userId, objects.length > 0 ? objects[0] : 1); - }).on(Socket.EVENT_RECONNECTING, objects -> { - LOGGER.info("Reconnection event for user {}", userId); - }).on(Socket.EVENT_RECONNECT, objects -> { - LOGGER.info("Socket of user {} reconnected!", userId); + }).on(Socket.EVENT_CONNECT_ERROR, objects -> { + LOGGER.debug("Connection error for socket of user {} because {}", userId, objects.length > 0 ? objects[0] : 1); }); // add observer to registry that triggers sync requests on changes @@ -921,7 +913,7 @@ public void activate() throws CouldNotPerformException { // make sure socket.connect is never called twice since there is still an open issue which can cause a broken connection. // https://github.com/socketio/socket.io-client-java/issues/576 - if(active) { + if (active) { return; } diff --git a/module/app/util/build.gradle.kts b/module/app/util/build.gradle.kts index 46a4ce9566..8ebb23f953 100644 --- a/module/app/util/build.gradle.kts +++ b/module/app/util/build.gradle.kts @@ -125,7 +125,7 @@ dependencies { api(project(":bco.app.influxdb.connector")) api(project(":bco.api.graphql")) api(project(":bco.dal.visual")) - api("commons-collections:commons-collections:_") + api("org.apache.commons:commons-collections4:_") testImplementation(project(":bco.dal.test")) } diff --git a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOSystemValidator.java b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOSystemValidator.java index 1234c969b1..62f0ede3db 100644 --- a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOSystemValidator.java +++ b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOSystemValidator.java @@ -22,7 +22,7 @@ * #L% */ -import org.apache.commons.collections.comparators.BooleanComparator; +import org.apache.commons.collections4.comparators.BooleanComparator; import org.openbase.bco.app.util.launch.jp.JPExitOnError; import org.openbase.bco.app.util.launch.jp.JPWaitForData; import org.openbase.bco.authentication.lib.BCO; diff --git a/settings.gradle.kts b/settings.gradle.kts index 210d260513..1f08013f17 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,15 @@ rootProject.name = "bco" +pluginManagement { + plugins { + id("de.fayard.refreshVersions") version "0.51.0" + } +} + +plugins { + id("de.fayard.refreshVersions") +} + include("authentication") include("registry") include("dal") @@ -111,13 +121,3 @@ project(":bco.device.openhab").projectDir = file("module/device/openhab") // includeBuild("lib/type") // not supported yet since its not a gradle project includeBuild("lib/jul") - -pluginManagement { - plugins { - id("de.fayard.refreshVersions") version "0.51.0" - } -} - -plugins { - id("de.fayard.refreshVersions") -} diff --git a/versions.properties b/versions.properties index 494a89132a..23734b561c 100644 --- a/versions.properties +++ b/versions.properties @@ -6,67 +6,30 @@ #### #### suppress inspection "SpellCheckingInspection" for whole file #### suppress inspection "UnusedProperty" for whole file - version.com.adarshr..gradle-test-logger-plugin=3.2.0 - -version.com.graphql-java-kickstart..graphql-spring-boot-starter=14.0.0 - -version.io.quarkus..quarkus-junit4-mock=2.9.1.Final - +version.com.graphql-java-kickstart..graphql-spring-boot-starter=15.0.0 +version.io.quarkus..quarkus-junit4-mock=3.2.3.Final ## unused -version.junit.jupiter=5.9.2 - -version.kotest=5.5.5 -## # available=5.6.0.1123-SNAPSHOT -## # available=5.6.0.1125-SNAPSHOT -## # available=5.6.0.1126-SNAPSHOT -## # available=5.6.0.1127-SNAPSHOT -## # available=5.6.0.1128-SNAPSHOT -## # available=5.6.0.1129-SNAPSHOT -## # available=5.6.0.1131-SNAPSHOT - -version.kotlinx.coroutines=1.6.4 -## # available=1.7.0-Beta - -version.mockk=1.13.4 - -version.org.jmdns..jmdns=3.5.7 - +version.junit.jupiter=5.10.0 +version.kotest=5.6.2 +version.kotlinx.coroutines=1.7.3 +version.mockk=1.13.5 version.org.glassfish.jersey.security..oauth2-client=3.1.3 - version.org.glassfish.jersey.media..jersey-media-sse=3.1.3 - version.org.glassfish.jersey.inject..jersey-hk2=3.1.3 - version.org.glassfish.jersey.core..jersey-client=3.1.3 - version.org.apache.commons..commons-math3=3.6.1 - version.net.javacrumbs.future-converter..future-converter-java8-guava=1.2.0 - -version.io.socket..socket.io-client=1.0.0 -## # available=1.0.1 -## # available=2.0.0 -## # available=2.0.1 - +version.io.socket..socket.io-client=2.0.1 version.io.reactivex.rxjava2..rxjava=2.2.21 - -version.commons-collections..commons-collections=3.2.2 -## # available=20030418.083655 -## # available=20031027.000000 -## # available=20040102.233541 -## # available=20040616 - +version.commons-collections..commons-collections4=4.4 ## unused version.com.influxdb..influxdb-client-java=[5.0,5.1-alpha) - version.com.google.inject.extensions..guice-multibindings=4.2.3 - version.com.google.inject..guice=5.0.1 ## # available=5.0.2-SNAPSHOT ## # available=5.1.0 ## # available=5.1.1-SNAPSHOT - version.com.google.guava..guava=28.0-jre ## # available=28.1-android ## # available=28.1-jre @@ -84,102 +47,74 @@ version.com.google.guava..guava=28.0-jre ## # available=31.0-jre ## # available=31.0.1-android ## # available=31.0.1-jre - version.org.openbase..jul.communication.mqtt.test=3.3-SNAPSHOT - version.org.openbase..jul.transformation=3.3-SNAPSHOT - -version.org.testcontainers..junit-jupiter=1.17.2 - -version.org.testcontainers..testcontainers=1.17.1 - -version.org.springframework.boot..spring-boot-starter-webflux=2.6.3 - -version.org.springframework.boot..spring-boot-starter-jetty=2.6.3 - -version.org.springframework.boot..spring-boot-starter-websocket=2.6.3 - -version.org.springframework..spring-webmvc=5.3.15 - +version.org.testcontainers..junit-jupiter=1.18.3 +version.org.testcontainers..testcontainers=1.18.3 +version.org.springframework.boot..spring-boot-starter-webflux=3.1.2 +version.org.springframework.boot..spring-boot-starter-jetty=3.1.2 +version.org.springframework.boot..spring-boot-starter-websocket=3.1.2 +version.org.springframework..spring-webmvc=6.0.11 version.org.openhab.core.bundles..org.openhab.core.io.rest.core=3.1.0 ## # available=3.1.1 ## # available=3.2.0 - -plugin.org.springframework.boot=2.6.2 -## # available=2.6.3 - -plugin.io.spring.dependency-management=1.0.11.RELEASE - -version.kotlin=1.7.0 - +plugin.org.springframework.boot=3.1.2 +plugin.io.spring.dependency-management=1.1.2 +version.kotlin=1.9.0 version.org.openbase..jul.communication.controller=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.communication.mqtt=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.exception=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.extension.protobuf=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.extension.type.processing=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.extension.type.storage=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.extension.type.transform=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.extension.type.util=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.pattern.launch=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.pattern.trigger=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.processing=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.storage=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.visual.javafx=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.org.openbase..jul.visual.swing=3.3-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 - version.rxjava2.rxjava=2.2.21 From 7131a26d0176ab8d444341d6ba0c5ff79742ee15 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 23 Aug 2023 12:43:05 +0200 Subject: [PATCH 36/70] link against latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 8a1164e1b6..00675ad064 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 8a1164e1b699331156a99b49786bf8b083f636cf +Subproject commit 00675ad06484adf749ae3b4231820c0f6bdd2cc5 From eaff071003dbb56b0d3cae81d63273386d1b0579 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 23 Aug 2023 14:51:50 +0200 Subject: [PATCH 37/70] fix lock and registry typos, finish implementation of message regsitry. --- .../graphql/schema/RegistrySchemaModule.kt | 21 +++- .../app/util/launch/BCORegistryValidator.java | 4 +- .../lib/CachedAuthenticationRemote.java | 13 ++- .../remote/CachedActivityRegistryRemote.java | 10 +- .../remote/CachedClassRegistryRemote.java | 10 +- .../remote/CachedMessageRegistryRemote.java | 19 ++- .../bco/registry/remote/Registries.java | 110 +++++++++++++----- .../remote/CachedTemplateRegistryRemote.java | 10 +- .../unit/remote/CachedUnitRegistryRemote.java | 11 +- 9 files changed, 137 insertions(+), 71 deletions(-) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt index aa0f40b500..cc3ffad2d6 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt @@ -21,6 +21,7 @@ import org.openbase.jul.extension.type.processing.LabelProcessor.getBestMatch import org.openbase.jul.extension.type.processing.LabelProcessor.replace import org.openbase.type.configuration.EntryType import org.openbase.type.configuration.MetaConfigType +import org.openbase.type.domotic.communication.UserMessageType.UserMessage import org.openbase.type.domotic.service.ServiceTemplateType import org.openbase.type.domotic.unit.UnitConfigType import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig @@ -56,7 +57,8 @@ import java.util.concurrent.* * License along with this program. If not, see * . * #L% - */ class RegistrySchemaModule : SchemaModule() { + */ +class RegistrySchemaModule : SchemaModule() { /** * Check if an authentication token retrieved by the login method is still valid. * @@ -438,5 +440,22 @@ import java.util.concurrent.* } catch (ex: InterruptedException) { throw GenericError(ex) } + + @Throws(BCOGraphQLError::class) + fun getUserMessages( + ): ImmutableList = try { + ImmutableList.copyOf( + Registries.getMessageRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ).userMessages + ) + } catch (ex: RuntimeException) { + throw GenericError(ex) + } catch (ex: CouldNotPerformException) { + throw GenericError(ex) + } catch (ex: InterruptedException) { + throw GenericError(ex) + } } } diff --git a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCORegistryValidator.java b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCORegistryValidator.java index 27bc0d38da..3e58ab20cd 100644 --- a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCORegistryValidator.java +++ b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCORegistryValidator.java @@ -91,8 +91,8 @@ public static void validateRegistries() throws CouldNotPerformException, Interru System.out.println("=== " + AnsiColor.colorize("Check registries" + (JPService.getValue(JPWaitForData.class, false) ? " and wait for data." : ""), AnsiColor.ANSI_BLUE) + " ===\n"); // check - final List registries = Registries.getRegistries(JPService.getValue(JPWaitForData.class, false)); - for (final RegistryRemote registry : registries) { + final List> registries = Registries.getRegistries(JPService.getValue(JPWaitForData.class, false)); + for (final RegistryRemote registry : registries) { if (!check(registry, TimeUnit.SECONDS.toMillis(2))) { // in case we should wait diff --git a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/CachedAuthenticationRemote.java b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/CachedAuthenticationRemote.java index d61d0b8a25..525c26bebe 100644 --- a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/CachedAuthenticationRemote.java +++ b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/CachedAuthenticationRemote.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 * . @@ -23,7 +23,10 @@ */ import org.openbase.jps.core.JPService; -import org.openbase.jul.exception.*; +import org.openbase.jul.exception.CouldNotPerformException; +import org.openbase.jul.exception.ExceptionProcessor; +import org.openbase.jul.exception.NotAvailableException; +import org.openbase.jul.exception.ShutdownInProgressException; import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.iface.Shutdownable; import org.openbase.jul.schedule.SyncObject; @@ -40,7 +43,7 @@ public class CachedAuthenticationRemote { private static AuthenticationRemote authenticationRemote; private static volatile boolean shutdown = false; private static final SyncObject REMOTE_LOCK = new SyncObject("CachedAuthenticationRemote"); - private static final SyncObject REGISTY_LOCK = new SyncObject("RegistyLock"); + private static final SyncObject REGISTRY_LOCK = new SyncObject("RegistryLock"); /** * Setup shutdown hook @@ -76,7 +79,7 @@ public static AuthenticationRemote getRemote() throws NotAvailableException { return authenticationRemote; } - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { if (authenticationRemote == null) { try { authenticationRemote = new AuthenticationRemote(); diff --git a/module/registry/activity-registry/remote/src/main/java/org/openbase/bco/registry/activity/remote/CachedActivityRegistryRemote.java b/module/registry/activity-registry/remote/src/main/java/org/openbase/bco/registry/activity/remote/CachedActivityRegistryRemote.java index d8a4505862..ef320c974a 100644 --- a/module/registry/activity-registry/remote/src/main/java/org/openbase/bco/registry/activity/remote/CachedActivityRegistryRemote.java +++ b/module/registry/activity-registry/remote/src/main/java/org/openbase/bco/registry/activity/remote/CachedActivityRegistryRemote.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 * . @@ -44,7 +44,7 @@ public class CachedActivityRegistryRemote { private static final Logger LOGGER = LoggerFactory.getLogger(CachedActivityRegistryRemote.class); private static final SyncObject REMOTE_LOCK = new SyncObject("CachedActivityRegistryRemoteLock"); - private static final SyncObject REGISTY_LOCK = new SyncObject("RegistyLock"); + private static final SyncObject REGISTRY_LOCK = new SyncObject("RegistryLock"); private static ActivityRegistryRemote registryRemote; private static volatile boolean shutdown = false; @@ -75,7 +75,7 @@ public static void reinitialize() throws InterruptedException, CouldNotPerformEx try { // only call re-init if the registry was activated and initialized in the first place if (registryRemote != null) { - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { getRegistry().reinit(REMOTE_LOCK); } } @@ -119,7 +119,7 @@ public static ActivityRegistryRemote getRegistry() throws NotAvailableException return registryRemote; } - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { if (registryRemote == null) { try { registryRemote = new ActivityRegistryRemote(); diff --git a/module/registry/class-registry/remote/src/main/java/org/openbase/bco/registry/clazz/remote/CachedClassRegistryRemote.java b/module/registry/class-registry/remote/src/main/java/org/openbase/bco/registry/clazz/remote/CachedClassRegistryRemote.java index f9edead19a..27328025c9 100644 --- a/module/registry/class-registry/remote/src/main/java/org/openbase/bco/registry/clazz/remote/CachedClassRegistryRemote.java +++ b/module/registry/class-registry/remote/src/main/java/org/openbase/bco/registry/clazz/remote/CachedClassRegistryRemote.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 * . @@ -44,7 +44,7 @@ public class CachedClassRegistryRemote { private static final Logger LOGGER = LoggerFactory.getLogger(CachedClassRegistryRemote.class); private static final SyncObject REMOTE_LOCK = new SyncObject("CachedClassRegistryRemoteLock"); - private static final SyncObject REGISTY_LOCK = new SyncObject("RegistyLock"); + private static final SyncObject REGISTRY_LOCK = new SyncObject("RegistryLock"); private static ClassRegistryRemote registryRemote; private static volatile boolean shutdown = false; @@ -75,7 +75,7 @@ public static void reinitialize() throws InterruptedException, CouldNotPerformEx try { // only call re-init if the registry was activated and initialized in the first place if (registryRemote != null) { - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { getRegistry().reinit(REMOTE_LOCK); } } @@ -119,7 +119,7 @@ public static ClassRegistryRemote getRegistry() throws NotAvailableException { return registryRemote; } - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { if (registryRemote == null) { try { registryRemote = new ClassRegistryRemote(); diff --git a/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/CachedMessageRegistryRemote.java b/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/CachedMessageRegistryRemote.java index 77ece7b378..1f7d7268d9 100644 --- a/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/CachedMessageRegistryRemote.java +++ b/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/CachedMessageRegistryRemote.java @@ -10,25 +10,23 @@ * 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% */ -import org.openbase.bco.registry.clazz.remote.ClassRegistryRemote; import org.openbase.bco.registry.message.lib.MessageRegistry; import org.openbase.jps.core.JPService; import org.openbase.jul.exception.*; import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.iface.Shutdownable; -import org.openbase.jul.iface.Shutdownable.ShutdownDaemon; import org.openbase.jul.schedule.SyncObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +44,7 @@ public class CachedMessageRegistryRemote { private static final Logger LOGGER = LoggerFactory.getLogger(CachedMessageRegistryRemote.class); private static final SyncObject REMOTE_LOCK = new SyncObject("CachedMessageRegistryRemoteLock"); - private static final SyncObject REGISTY_LOCK = new SyncObject("RegistyLock"); + private static final SyncObject REGISTRY_LOCK = new SyncObject("RegistryLock"); private static MessageRegistryRemote registryRemote; private static volatile boolean shutdown = false; @@ -77,7 +75,7 @@ public static void reinitialize() throws InterruptedException, CouldNotPerformEx try { // only call re-init if the registry was activated and initialized in the first place if (registryRemote != null) { - if (registryRemote != null) { + synchronized (REGISTRY_LOCK) { getRegistry().reinit(REMOTE_LOCK); } } @@ -121,7 +119,7 @@ public static MessageRegistryRemote getRegistry() throws NotAvailableException { return registryRemote; } - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { if (registryRemote == null) { try { registryRemote = new MessageRegistryRemote(); @@ -134,13 +132,13 @@ public static MessageRegistryRemote getRegistry() throws NotAvailableException { registryRemote.shutdown(); registryRemote = null; } - throw ExceptionPrinter.printHistoryAndReturnThrowable(new CouldNotPerformException("Could not start cached unit registry remote!", ex), LOGGER); + throw ExceptionPrinter.printHistoryAndReturnThrowable(new CouldNotPerformException("Could not start cached message registry remote!", ex), LOGGER); } } return registryRemote; } } catch (CouldNotPerformException ex) { - throw new NotAvailableException("Cached unit registry is not available!", ex); + throw new NotAvailableException("cached message registry", ex); } } @@ -176,9 +174,8 @@ public static void waitUntilReady() throws InterruptedException, CouldNotPerform getRegistry().waitUntilReady(); } - public void prepare() throws CouldNotPerformException, InterruptedException { + public static void prepare() throws CouldNotPerformException { synchronized (REMOTE_LOCK) { - // handle legal operation if (registryRemote == null && shutdown == false) { getRegistry(); diff --git a/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java b/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java index eaaf4a79ac..00fd75f212 100644 --- a/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java +++ b/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -30,6 +30,9 @@ import org.openbase.bco.registry.clazz.lib.ClassRegistry; import org.openbase.bco.registry.clazz.remote.CachedClassRegistryRemote; import org.openbase.bco.registry.clazz.remote.ClassRegistryRemote; +import org.openbase.bco.registry.message.lib.MessageRegistry; +import org.openbase.bco.registry.message.remote.CachedMessageRegistryRemote; +import org.openbase.bco.registry.message.remote.MessageRegistryRemote; import org.openbase.bco.registry.template.lib.TemplateRegistry; import org.openbase.bco.registry.template.remote.CachedTemplateRegistryRemote; import org.openbase.bco.registry.template.remote.TemplateRegistryRemote; @@ -93,8 +96,8 @@ public class Registries { * @throws CouldNotPerformException is throw if at least one registry is not available. * @throws InterruptedException is thrown if thread is externally interrupted. */ - public static List getRegistries(final boolean waitForData) throws CouldNotPerformException, InterruptedException { - final List registryList = new ArrayList<>(); + public static List> getRegistries(final boolean waitForData) throws CouldNotPerformException, InterruptedException { + final List> registryList = new ArrayList<>(); registryList.add(getTemplateRegistry(waitForData)); registryList.add(getClassRegistry(waitForData)); registryList.add(getActivityRegistry(waitForData)); @@ -109,7 +112,7 @@ public static List getRegistries(final boolean waitForData) thro * * @throws CouldNotPerformException is throw if at least one registry is not available. */ - public static List getRegistries() throws CouldNotPerformException { + public static List> getRegistries() throws CouldNotPerformException { try { return getRegistries(false); } catch (InterruptedException ex) { @@ -184,6 +187,19 @@ public static TemplateRegistryRemote getTemplateRegistry(final long timeout, fin return registry; } + /** + * Returns an initialized and activated remote registry. + * + * @return the remote registry instance. + * + * @throws NotAvailableException + */ + public static MessageRegistryRemote getMessageRegistry(final long timeout, final TimeUnit timeUnit) throws CouldNotPerformException, InterruptedException { + final MessageRegistryRemote registry = CachedMessageRegistryRemote.getRegistry(); + registry.waitForData(timeout, timeUnit); + return registry; + } + /** * Returns an initialized and activated remote registry. @@ -218,6 +234,17 @@ public static TemplateRegistryRemote getTemplateRegistry() throws NotAvailableEx return CachedTemplateRegistryRemote.getRegistry(); } + /** + * Returns an initialized and activated remote registry. + * + * @return the remote registry instance. + * + * @throws NotAvailableException + */ + public static MessageRegistryRemote getMessageRegistry() throws NotAvailableException { + return CachedMessageRegistryRemote.getRegistry(); + } + /** * Returns an initialized and activated remote registry. * @@ -302,6 +329,27 @@ public static TemplateRegistryRemote getTemplateRegistry(final boolean waitForDa } } + /** + * Returns an initialized and activated remote registry. + * + * @param waitForData defines if this call should block until the registry data is available. + * + * @return the remote registry instance. + * + * @throws NotAvailableException + * @throws InterruptedException is thrown if thread is externally interrupted. + */ + public static MessageRegistryRemote getMessageRegistry(final boolean waitForData) throws CouldNotPerformException, InterruptedException { + try { + if (waitForData) { + CachedMessageRegistryRemote.getRegistry().waitForData(); + } + return CachedMessageRegistryRemote.getRegistry(); + } catch (CouldNotPerformException ex) { + throw new NotAvailableException(MessageRegistry.class, ex); + } + } + /** * Method shutdown all registry instances. *

@@ -315,6 +363,7 @@ public static void shutdown() { CachedActivityRegistryRemote.shutdown(); CachedClassRegistryRemote.shutdown(); CachedTemplateRegistryRemote.shutdown(); + CachedMessageRegistryRemote.shutdown(); } public static void prepare() throws CouldNotPerformException { @@ -322,15 +371,17 @@ public static void prepare() throws CouldNotPerformException { CachedActivityRegistryRemote.prepare(); CachedClassRegistryRemote.prepare(); CachedTemplateRegistryRemote.prepare(); + CachedMessageRegistryRemote.prepare(); } public static Future requestData() { try { return FutureProcessor.allOf( - CachedUnitRegistryRemote.getRegistry().requestData(), - CachedActivityRegistryRemote.getRegistry().requestData(), - CachedClassRegistryRemote.getRegistry().requestData(), - CachedTemplateRegistryRemote.getRegistry().requestData() + CachedUnitRegistryRemote.getRegistry().requestData(), + CachedActivityRegistryRemote.getRegistry().requestData(), + CachedClassRegistryRemote.getRegistry().requestData(), + CachedTemplateRegistryRemote.getRegistry().requestData(), + CachedMessageRegistryRemote.getRegistry().requestData() ); } catch (NotAvailableException ex) { return FutureProcessor.canceledFuture(ex); @@ -348,6 +399,7 @@ public static void waitForData() throws CouldNotPerformException, InterruptedExc CachedClassRegistryRemote.waitForData(); CachedActivityRegistryRemote.waitForData(); CachedUnitRegistryRemote.waitForData(); + CachedMessageRegistryRemote.waitForData(); } /** @@ -361,6 +413,7 @@ public static void waitForData(long timeout, TimeUnit timeUnit) throws CouldNotP CachedClassRegistryRemote.waitForData(timeout, timeUnit); CachedActivityRegistryRemote.waitForData(timeout, timeUnit); CachedUnitRegistryRemote.waitForData(timeout, timeUnit); + CachedMessageRegistryRemote.waitForData(timeout, timeUnit); } public static boolean isDataAvailable() { @@ -368,7 +421,8 @@ public static boolean isDataAvailable() { return CachedUnitRegistryRemote.getRegistry().isDataAvailable() && CachedTemplateRegistryRemote.getRegistry().isDataAvailable() && CachedClassRegistryRemote.getRegistry().isDataAvailable() - && CachedActivityRegistryRemote.getRegistry().isDataAvailable(); + && CachedActivityRegistryRemote.getRegistry().isDataAvailable() + && CachedMessageRegistryRemote.getRegistry().isDataAvailable(); } catch (NotAvailableException ex) { // at least one remote is not available. } @@ -386,6 +440,7 @@ public static void reinitialize() throws CouldNotPerformException, InterruptedEx CachedClassRegistryRemote.reinitialize(); CachedActivityRegistryRemote.reinitialize(); CachedUnitRegistryRemote.reinitialize(); + CachedMessageRegistryRemote.reinitialize(); } /** @@ -401,6 +456,7 @@ public static void waitUntilReady() throws InterruptedException, CouldNotPerform CachedClassRegistryRemote.waitUntilReady(); CachedActivityRegistryRemote.waitUntilReady(); CachedUnitRegistryRemote.waitUntilReady(); + CachedMessageRegistryRemote.waitUntilReady(); } /** @@ -619,7 +675,8 @@ private static Object invokeMethod(final String methodName, final MessageOrBuild final AbstractRemoteClient remote = getRegistryRemoteByType(messageOrBuilder); Method method = remote.getClass().getMethod(methodName, classes); return method.invoke(remote, parameters); - } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | + InvocationTargetException ex) { throw new CouldNotPerformException("Could not invoke method[" + methodName + "] for type[" + messageOrBuilder.getDescriptorForType().getName() + "]", ex); } } @@ -632,25 +689,16 @@ private static String getMethodNameForType(final MessageOrBuilder messageOrBuild return messageOrBuilder.getDescriptorForType().getName(); } - private static AbstractRemoteClient getRegistryRemoteByType(final MessageOrBuilder messageOrBuilder) throws CouldNotPerformException { - switch (messageOrBuilder.getDescriptorForType().getName()) { - case "UnitConfig": - return getUnitRegistry(); - case "DeviceClass": - case "AgentClass": - case "AppClass": - case "GatewayClass": - return getClassRegistry(); - case "UnitTemplate": - case "ServiceTemplate": - case "ActivityTemplate": - return getTemplateRegistry(); - case "ActivityConfig": - return getActivityRegistry(); - default: - throw new NotAvailableException("Registry remote for type [" + messageOrBuilder.getDescriptorForType().getName() + "]"); - - } + private static AbstractRemoteClient getRegistryRemoteByType(final MessageOrBuilder messageOrBuilder) throws CouldNotPerformException { + return switch (messageOrBuilder.getDescriptorForType().getName()) { + case "UnitConfig" -> getUnitRegistry(); + case "DeviceClass", "AgentClass", "AppClass", "GatewayClass" -> getClassRegistry(); + case "UnitTemplate", "ServiceTemplate", "ActivityTemplate" -> getTemplateRegistry(); + case "ActivityConfig" -> getActivityRegistry(); + case "UserMessage" -> getMessageRegistry(); + default -> + throw new NotAvailableException("Registry remote for type [" + messageOrBuilder.getDescriptorForType().getName() + "]"); + }; } /** @@ -664,7 +712,7 @@ private static AbstractRemoteClient getRegistryRemoteByType(final MessageOrBuild * * @throws InstantiationException is thrown if the registry is not available. */ - public static UnitRegistryRemote getUnitRegistry(final Class clazz) throws InstantiationException { + public static UnitRegistryRemote getUnitRegistry(final Class clazz) throws InstantiationException { try { return Registries.getUnitRegistry(); } catch (NotAvailableException ex) { diff --git a/module/registry/template-registry/remote/src/main/java/org/openbase/bco/registry/template/remote/CachedTemplateRegistryRemote.java b/module/registry/template-registry/remote/src/main/java/org/openbase/bco/registry/template/remote/CachedTemplateRegistryRemote.java index 4c83bed0ad..b6f966d55c 100644 --- a/module/registry/template-registry/remote/src/main/java/org/openbase/bco/registry/template/remote/CachedTemplateRegistryRemote.java +++ b/module/registry/template-registry/remote/src/main/java/org/openbase/bco/registry/template/remote/CachedTemplateRegistryRemote.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 * . @@ -44,7 +44,7 @@ public class CachedTemplateRegistryRemote { private static final Logger LOGGER = LoggerFactory.getLogger(CachedTemplateRegistryRemote.class); private static final SyncObject REMOTE_LOCK = new SyncObject("CachedTemplateRegistryRemoteLock"); - private static final SyncObject REGISTY_LOCK = new SyncObject("RegistyLock"); + private static final SyncObject REGISTRY_LOCK = new SyncObject("RegistryLock"); private static TemplateRegistryRemote registryRemote; private static volatile boolean shutdown = false; @@ -75,7 +75,7 @@ public static void reinitialize() throws InterruptedException, CouldNotPerformEx try { // only call re-init if the registry was activated and initialized in the first place if (registryRemote != null) { - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { getRegistry().reinit(REMOTE_LOCK); } } @@ -119,7 +119,7 @@ public static TemplateRegistryRemote getRegistry() throws NotAvailableException return registryRemote; } - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { if (registryRemote == null) { try { registryRemote = new TemplateRegistryRemote(); diff --git a/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/CachedUnitRegistryRemote.java b/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/CachedUnitRegistryRemote.java index 9398cdc99d..8db0f0d283 100644 --- a/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/CachedUnitRegistryRemote.java +++ b/module/registry/unit-registry/remote/src/main/java/org/openbase/bco/registry/unit/remote/CachedUnitRegistryRemote.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 * . @@ -27,7 +27,6 @@ import org.openbase.jul.exception.*; import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.iface.Shutdownable; -import org.openbase.jul.iface.Shutdownable.ShutdownDaemon; import org.openbase.jul.schedule.SyncObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,7 +44,7 @@ public class CachedUnitRegistryRemote { private static final Logger LOGGER = LoggerFactory.getLogger(CachedUnitRegistryRemote.class); private static final SyncObject REMOTE_LOCK = new SyncObject("CachedUnitRegistryRemoteLock"); - private static final SyncObject REGISTY_LOCK = new SyncObject("RegistyLock"); + private static final SyncObject REGISTRY_LOCK = new SyncObject("RegistryLock"); private static UnitRegistryRemote registryRemote; private static volatile boolean shutdown = false; @@ -76,7 +75,7 @@ public static void reinitialize() throws InterruptedException, CouldNotPerformEx try { // only call re-init if the registry was activated and initialized in the first place if (registryRemote != null) { - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { getRegistry().reinit(REMOTE_LOCK); } } @@ -120,7 +119,7 @@ public static UnitRegistryRemote getRegistry() throws NotAvailableException { return registryRemote; } - synchronized (REGISTY_LOCK) { + synchronized (REGISTRY_LOCK) { if (registryRemote == null) { try { registryRemote = new UnitRegistryRemote(); From c4a8d5c339bdc3f321a8f159410ebd19b47bc654 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 23 Aug 2023 14:56:50 +0200 Subject: [PATCH 38/70] introduce user message gql query --- .../openbase/bco/api/graphql/schema/RegistrySchemaModule.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt index cc3ffad2d6..a85d3d1034 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt @@ -159,6 +159,10 @@ class RegistrySchemaModule : SchemaModule() { @Arg("includeDisabledUnits") includeDisabledUnits: Boolean?, ): ImmutableList = getUnitConfigs(unitFilter, includeDisabledUnits) + @Query("userMessages") + @Throws(BCOGraphQLError::class) + fun userMessages(): ImmutableList = getUserMessages() + @Query("gatewayClasses") @Throws(CouldNotPerformException::class, InterruptedException::class) fun gatewayClasses(): ImmutableList = From 5ccb18a1399e6d29ea41e7a22fbd0317331ae07b Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Thu, 12 Oct 2023 00:45:14 +0200 Subject: [PATCH 39/70] configure beta pr review deployment --- .github/workflows/deploy-docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml index 28629b6887..4cbc067ce6 100644 --- a/.github/workflows/deploy-docker.yml +++ b/.github/workflows/deploy-docker.yml @@ -11,6 +11,7 @@ on: pull_request: branches: - dev + - beta types: [opened, labeled, synchronize, reopened, ready_for_review] jobs: From 3f2fde9a10a6a6bafcf0e9796c37219202ec7ac4 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 1 Nov 2023 17:15:02 +0100 Subject: [PATCH 40/70] upgrade to openhab 4.0.3A --- versions.properties | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/versions.properties b/versions.properties index 7fb4e31771..e63b0356c2 100644 --- a/versions.properties +++ b/versions.properties @@ -56,9 +56,7 @@ version.org.springframework.boot..spring-boot-starter-webflux=3.1.2 version.org.springframework.boot..spring-boot-starter-jetty=3.1.2 version.org.springframework.boot..spring-boot-starter-websocket=3.1.2 version.org.springframework..spring-webmvc=6.0.11 -version.org.openhab.core.bundles..org.openhab.core.io.rest.core=3.2.0 -## # available=3.1.1 -## # available=3.2.0 +version.org.openhab.core.bundles..org.openhab.core.io.rest.core=4.0.3 plugin.org.springframework.boot=3.1.2 plugin.io.spring.dependency-management=1.1.2 version.kotlin=1.9.0 From e09d60608e193c02890794fc223ba24d695dcd94 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Mon, 6 Nov 2023 23:56:37 +0100 Subject: [PATCH 41/70] fix health check configuration --- docker/Dockerfile | 5 +++-- docker/bco-demo/Dockerfile | 4 ++-- docker/device-manager/openhab/Dockerfile | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5c7958491d..c246598051 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -75,7 +75,8 @@ ENV \ BCO_HOME="/home/bco/data" \ BCO_LOGS="/home/bco/data/log" \ BCO_BINARY="/usr/bin/bco" \ - BCO_OPTIONS="" + MQTT_BROKER="mqtt-broker" \ + BCO_OPTIONS="--host ${MQTT_BROKER}" # Basic build-time metadata as defined at http://label-schema.org LABEL org.label-schema.build-date=$BUILD_DATE \ @@ -111,7 +112,7 @@ ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] # Configure healthcheck # todo: make sure only the registry availability is checks since devices are not maintained by this instance. -HEALTHCHECK --interval=15m --timeout=2m CMD bco-validate >/dev/null || exit 1 +HEALTHCHECK --interval=15m --timeout=2m CMD bco-validate --host ${MQTT_BROKER} > /dev/null || exit 1 # switch to root, let the entrypoint drop back to bco user USER root diff --git a/docker/bco-demo/Dockerfile b/docker/bco-demo/Dockerfile index 1a7a5d11e0..ee63d55f56 100644 --- a/docker/bco-demo/Dockerfile +++ b/docker/bco-demo/Dockerfile @@ -23,7 +23,7 @@ FROM openbaseorg/bco:${BCO_BASE_IMAGE_VERSION} ENV \ JAVA_OPTS="" \ OPENHAB_CONF="/etc/openhab2" \ - BCO_OPTIONS="--db /tmp/bco/db" \ + BCO_OPTIONS="--db /tmp/bco/db --host ${MQTT_BROKER}" \ BCO_MODULE_PREPARE_SCRIPT="bco-module-prepare.sh" # Basic build-time metadata as defined at http://label-schema.org @@ -51,7 +51,7 @@ COPY --from=builder /workspace/db /usr/share/bco/db/ # Configure healthcheck # todo: make sure only device offered by this device manager are checked -HEALTHCHECK --interval=15m --timeout=2m CMD bco-validate >/dev/null || exit 1 +HEALTHCHECK --interval=15m --timeout=2m CMD bco-validate --host ${MQTT_BROKER} >/dev/null || exit 1 # switch to root, let the entrypoint drop back to bco user USER root diff --git a/docker/device-manager/openhab/Dockerfile b/docker/device-manager/openhab/Dockerfile index 16fbd837cc..02ce876f34 100644 --- a/docker/device-manager/openhab/Dockerfile +++ b/docker/device-manager/openhab/Dockerfile @@ -6,7 +6,6 @@ FROM openbaseorg/bco:${BCO_BASE_IMAGE_VERSION} ENV \ JAVA_OPTS="" \ OPENHAB_CONF="/etc/openhab2" \ - BCO_OPTIONS="" \ BCO_MODULE_PREPARE_SCRIPT="bco-module-prepare.sh" # Basic build-time metadata as defined at http://label-schema.org @@ -33,7 +32,7 @@ RUN ln -s /usr/local/bin/${BCO_MODULE_PREPARE_SCRIPT} && \ # Configure healthcheck # todo: make sure only device offered by this device manager are checked -HEALTHCHECK --interval=15m --timeout=2m CMD bco-validate >/dev/null || exit 1 +HEALTHCHECK --interval=15m --timeout=2m CMD bco-validate --host ${MQTT_BROKER} >/dev/null || exit 1 # switch to root, let the entrypoint drop back to bco user USER root From b59bbafc6795590608b0d4cefdae4b5fcec12301 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 7 Nov 2023 01:08:39 +0100 Subject: [PATCH 42/70] bump openhab version to 4.0.4 --- versions.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.properties b/versions.properties index e63b0356c2..c4377848b5 100644 --- a/versions.properties +++ b/versions.properties @@ -56,7 +56,7 @@ version.org.springframework.boot..spring-boot-starter-webflux=3.1.2 version.org.springframework.boot..spring-boot-starter-jetty=3.1.2 version.org.springframework.boot..spring-boot-starter-websocket=3.1.2 version.org.springframework..spring-webmvc=6.0.11 -version.org.openhab.core.bundles..org.openhab.core.io.rest.core=4.0.3 +version.org.openhab.core.bundles..org.openhab.core.io.rest.core=4.0.4 plugin.org.springframework.boot=3.1.2 plugin.io.spring.dependency-management=1.1.2 version.kotlin=1.9.0 From d4ad0876b28aa1f2b4041d6d77c9716562cf7251 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 7 Nov 2023 21:56:42 +0100 Subject: [PATCH 43/70] Fix concurrent modification exception in RemoteAction by taking care that the impacted remote actions are only accessed once the controller initialization is done. --- .../org/openbase/bco/dal/remote/action/RemoteAction.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java index 5bdb822d1b..0ea35e8516 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java @@ -643,6 +643,11 @@ public boolean isRunning() { */ @Override public boolean isDone() { + + if (futureObservationTask != null && !futureObservationTask.isDone()) { + return false; + } + try { if (getActionDescription().getIntermediary()) { for (final RemoteAction impactedRemoteAction : impactedRemoteActions) { From d7b731ffa62e9f341dcb68b7b911af5ab2b8c4d4 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Mon, 20 Nov 2023 19:40:35 +0100 Subject: [PATCH 44/70] switch back to snapshot version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cdc9192c92..1c62fbf0fc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=3.1.2 +version=3.1-SNAPSHOT org.gradle.caching=true org.gradle.parallel=false org.gradle.daemon=true From 91461f4a3e30a71ba2f905481d9872599dd1e72a Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 26 Dec 2023 15:55:56 +0100 Subject: [PATCH 45/70] Implement message registry and minor bug fixes. --- lib/jul | 2 +- lib/type | 2 +- .../context/AbstractBCOGraphQLContext.kt | 2 +- .../graphql/schema/RegistrySchemaModule.kt | 91 +++- .../subscriptions/SubscriptionModule.kt | 12 +- .../bco/app/preset/DeviceNotificationApp.kt | 149 +++++++ .../openbase/bco/app/preset/TemplateApp.kt | 67 +++ .../app/preset/DeviceNotificationAppTest.kt | 112 +++++ .../test/agent/AbstractBCOAppManagerTest.java | 91 ---- .../test/agent/AbstractBCOAppManagerTest.kt | 124 ++++++ .../openbase/app/test/agent/BCOAppTest.java | 6 + .../bco/app/util/launch/BCOLauncher.java | 8 +- .../bco/app/util/launch/BCOTestLauncher.java | 9 +- .../AbstractAuthorizedBaseUnitController.java | 15 +- .../control/layer/unit/InfluxDbProcessor.java | 8 +- .../unit/app/AppControllerFactoryImpl.java | 22 +- .../layer/unit/scene/SceneControllerImpl.java | 19 +- .../bco/dal/control/message/MessageManager.kt | 106 +++++ .../control/message/MessageManagerLauncher.kt | 32 ++ .../layer/service/ServiceStateProcessor.java | 26 +- .../openbase/bco/dal/lib/state/States.java | 18 +- .../bco/dal/remote/action/RemoteAction.java | 5 + .../dal/remote/detector/PresenceDetector.java | 4 +- .../dal/remote/layer/unit/CustomUnitPool.kt | 10 +- .../remote/layer/unit/CustomUnitPoolTest.java | 79 ---- .../remote/layer/unit/CustomUnitPoolTest.kt | 77 ++++ .../bco/dal/test/action/KotlinTest.kt | 4 - .../bco/device/openhab/jp/JPOpenHABURI.java | 9 +- .../lib/com/AbstractRegistryController.java | 6 +- .../core/MessageRegistryController.java | 79 ++-- .../message/remote/MessageRegistryRemote.java | 34 +- .../MessageRegistryRemoteAuthExtensions.kt | 39 ++ ...tionGroupClassGroupConsistencyHandler.java | 9 +- .../auth/AuthorizationWithTokenHelper.java | 347 --------------- .../lib/auth/AuthorizationWithTokenHelper.kt | 421 ++++++++++++++++++ .../bco/registry/mock/MockRegistry.java | 28 +- 36 files changed, 1424 insertions(+), 648 deletions(-) create mode 100644 module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt create mode 100644 module/app/preset/src/main/java/org/openbase/bco/app/preset/TemplateApp.kt create mode 100644 module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt delete mode 100644 module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.java create mode 100644 module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.kt create mode 100644 module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt create mode 100644 module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManagerLauncher.kt delete mode 100644 module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.java create mode 100644 module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt delete mode 100644 module/dal/test/src/test/java/org/openbase/bco/dal/test/action/KotlinTest.kt create mode 100644 module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemoteAuthExtensions.kt delete mode 100644 module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.java create mode 100644 module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt diff --git a/lib/jul b/lib/jul index 00675ad064..7296390421 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 00675ad06484adf749ae3b4231820c0f6bdd2cc5 +Subproject commit 72963904216da0c780dc950d8a17499a46195900 diff --git a/lib/type b/lib/type index 99e866c784..05eba9bc57 160000 --- a/lib/type +++ b/lib/type @@ -1 +1 @@ -Subproject commit 99e866c78450e7ae410f2bc594a239b5b53ab970 +Subproject commit 05eba9bc579a8b7fcd006eda89f462d11a6a0fd1 diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/AbstractBCOGraphQLContext.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/AbstractBCOGraphQLContext.kt index 5dc614b01f..f43f611efd 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/AbstractBCOGraphQLContext.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/context/AbstractBCOGraphQLContext.kt @@ -34,7 +34,7 @@ abstract class AbstractBCOGraphQLContext( abstract val languageCode: String? val auth: AuthTokenType.AuthToken? - get() = AuthTokenType.AuthToken.newBuilder().setAuthenticationToken(token).build() + get() = token?.let { AuthTokenType.AuthToken.newBuilder().setAuthenticationToken(it).build() } companion object { const val DATA_LOADER_UNITS = "units" diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt index a85d3d1034..53c5c4f24b 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/schema/RegistrySchemaModule.kt @@ -10,6 +10,9 @@ import org.openbase.bco.api.graphql.error.GenericError import org.openbase.bco.api.graphql.error.ServerError import org.openbase.bco.authentication.lib.SessionManager import org.openbase.bco.authentication.lib.iface.BCOSession +import org.openbase.bco.registry.message.remote.registerUserMessageAuthenticated +import org.openbase.bco.registry.message.remote.removeUserMessageAuthenticated +import org.openbase.bco.registry.message.remote.updateUserMessageAuthenticated import org.openbase.bco.registry.remote.Registries import org.openbase.bco.registry.remote.session.BCOSessionImpl import org.openbase.bco.registry.unit.remote.registerUnitConfigAuthenticated @@ -407,7 +410,7 @@ class RegistrySchemaModule : SchemaModule() { break } } - if (!entry.value.isEmpty()) { + if (entry.value.isNotEmpty()) { metaConfigBuilder.addEntry(entry) } @@ -425,6 +428,92 @@ class RegistrySchemaModule : SchemaModule() { throw GenericError(ex) } + @Mutation("updateUserMessage") + @Throws(BCOGraphQLError::class) + fun updateUserMessage( + @Arg("userMessage") userMessage: UserMessage, + env: DataFetchingEnvironment, + ): UserMessage = try { + val userMessageBuilder = Registries.getMessageRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ) + .getUserMessageById(userMessage.id) + .toBuilder() + userMessageBuilder.mergeFromWithoutRepeatedFields(userMessage) + Registries.getMessageRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ).updateUserMessageAuthenticated( + userMessageBuilder.build(), + env.context.auth + )[ServerError.BCO_TIMEOUT_SHORT, ServerError.BCO_TIMEOUT_TIME_UNIT] + } catch (ex: RuntimeException) { + throw GenericError(ex) + } catch (ex: CouldNotPerformException) { + throw GenericError(ex) + } catch (ex: InterruptedException) { + throw GenericError(ex) + } catch (ex: ExecutionException) { + throw GenericError(ex) + } catch (ex: TimeoutException) { + throw GenericError(ex) + } + + @Mutation("removeUserMessage") + @Throws(BCOGraphQLError::class) + fun removeUserMessage( + @Arg("unitId") unitId: String?, + env: DataFetchingEnvironment, + ): UserMessage = try { + val userMessage = Registries.getMessageRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ).getUserMessageById(unitId) + Registries.getMessageRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ).removeUserMessageAuthenticated( + userMessage, + env.context.auth + )[ServerError.BCO_TIMEOUT_SHORT, ServerError.BCO_TIMEOUT_TIME_UNIT] + } catch (ex: RuntimeException) { + throw GenericError(ex) + } catch (ex: CouldNotPerformException) { + throw GenericError(ex) + } catch (ex: InterruptedException) { + throw GenericError(ex) + } catch (ex: ExecutionException) { + throw GenericError(ex) + } catch (ex: TimeoutException) { + throw GenericError(ex) + } + + @Mutation("registerUserMessage") + @Throws(BCOGraphQLError::class) + fun registerUserMessage( + @Arg("userMessage") userMessage: UserMessage?, + env: DataFetchingEnvironment, + ): UserMessage = try { + Registries.getMessageRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ).registerUserMessageAuthenticated( + userMessage, + env.context.auth + )[ServerError.BCO_TIMEOUT_SHORT, ServerError.BCO_TIMEOUT_TIME_UNIT] + } catch (ex: RuntimeException) { + throw GenericError(ex) + } catch (ex: CouldNotPerformException) { + throw GenericError(ex) + } catch (ex: InterruptedException) { + throw GenericError(ex) + } catch (ex: ExecutionException) { + throw GenericError(ex) + } catch (ex: TimeoutException) { + throw GenericError(ex) + } + companion object { @Throws(BCOGraphQLError::class) fun getUnitConfigs( diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt index a877304693..26fadb2a03 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt @@ -8,6 +8,7 @@ import org.openbase.bco.api.graphql.error.GenericError import org.openbase.bco.api.graphql.error.ServerError import org.openbase.bco.api.graphql.schema.RegistrySchemaModule import org.openbase.bco.dal.lib.layer.unit.Unit +import org.openbase.bco.dal.lib.layer.unit.UnitRemote import org.openbase.bco.dal.remote.layer.unit.CustomUnitPool import org.openbase.bco.registry.remote.Registries import org.openbase.jul.exception.CouldNotPerformException @@ -20,7 +21,6 @@ import org.openbase.type.domotic.unit.UnitDataType import org.openbase.type.domotic.unit.UnitFilterType.UnitFilter import org.reactivestreams.Publisher import org.slf4j.LoggerFactory -import java.util.function.Consumer /*- * #%L @@ -51,15 +51,15 @@ import java.util.function.Consumer @Throws(BCOGraphQLError::class) fun subscribeUnits(unitFilter: UnitFilter): Publisher { return try { - val subscriptionUnitPool = CustomUnitPool() + val subscriptionUnitPool = CustomUnitPool>() subscriptionUnitPool.init(unitFilter) AbstractObserverMapper.createObservable( - Consumer { observer: Observer, Message> -> + { observer: Observer, Message> -> subscriptionUnitPool.addDataObserver( observer ) }, - Consumer { observer: Observer, Message> -> + { observer: Observer, Message> -> subscriptionUnitPool.removeDataObserver(observer) }, object : AbstractObserverMapper, Message, UnitDataType.UnitData>() { @@ -100,12 +100,12 @@ import java.util.function.Consumer ServerError.BCO_TIMEOUT_TIME_UNIT ) AbstractObserverMapper.createObservable( - Consumer { observer: Observer, UnitRegistryData> -> + { observer: Observer, UnitRegistryData> -> unitRegistry.addDataObserver( observer ) }, - Consumer { observer: Observer, UnitRegistryData> -> + { observer: Observer, UnitRegistryData> -> unitRegistry.removeDataObserver(observer) }, observer diff --git a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt new file mode 100644 index 0000000000..7cdb269a5d --- /dev/null +++ b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt @@ -0,0 +1,149 @@ +package org.openbase.bco.app.preset + +import org.openbase.bco.dal.control.layer.unit.app.AbstractAppController +import org.openbase.bco.dal.lib.layer.service.ServiceStateProcessor +import org.openbase.bco.dal.remote.layer.unit.BatteryRemote +import org.openbase.bco.dal.remote.layer.unit.CustomUnitPool +import org.openbase.bco.registry.message.remote.registerUserMessageAuthenticated +import org.openbase.bco.registry.remote.Registries +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.printer.ExceptionPrinter +import org.openbase.jul.extension.type.processing.LabelProcessor +import org.openbase.jul.extension.type.processing.MultiLanguageTextProcessor +import org.openbase.jul.extension.type.processing.TimestampProcessor +import org.openbase.jul.schedule.GlobalScheduledExecutorService +import org.openbase.type.domotic.action.ActionDescriptionType.ActionDescription +import org.openbase.type.domotic.communication.UserMessageType.UserMessage +import org.openbase.type.domotic.service.ServiceStateDescriptionType.ServiceStateDescription +import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServiceType +import org.openbase.type.domotic.state.ActivationStateType.ActivationState +import org.openbase.type.domotic.state.BatteryStateType.BatteryState +import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig +import org.openbase.type.domotic.unit.UnitFilterType.UnitFilter +import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType +import org.openbase.type.domotic.unit.dal.BatteryDataType.BatteryData +import org.openbase.type.language.MultiLanguageTextType.MultiLanguageText +import org.slf4j.LoggerFactory +import java.time.Duration +import java.util.* +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit + +/** + * An app that notifies users about devices with empty batteries or offline states. + */ +class DeviceNotificationApp : AbstractAppController() { + + private val batteryPool = CustomUnitPool() + private var task: ScheduledFuture<*>? = null + + init { + batteryPool.init( + UnitFilter.newBuilder().setProperties(UnitConfig.newBuilder().setUnitType(UnitType.BATTERY)).build() + ) + } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun execute(activationState: ActivationState): ActionDescription = + activationState.responsibleAction.also { + batteryPool.activate() + task?.cancel(false) + task = GlobalScheduledExecutorService.scheduleWithFixedDelay( + ::checkDevices, + INITIAL_VALIDATION_DELAY.toMillis(), + VALIDATION_PERIOD.toMillis(), + TimeUnit.MILLISECONDS + ) + LOGGER.trace(getLabel() + " is running.") + } + + @Throws(InterruptedException::class, CouldNotPerformException::class) + override fun stop(activationState: ActivationState) = + super.stop(activationState).also { + batteryPool.deactivate() + task?.cancel(true) + LOGGER.trace(getLabel() + " has been stopped.") + } + + fun checkDevices() { + try { + logger.error("#@# check device states") + batteryPool.internalUnitList + .onEach { remote -> remote.waitForData() } + .filter { remote -> + when (remote.data.batteryState.value) { + BatteryState.State.LOW, BatteryState.State.CRITICAL, BatteryState.State.UNKNOWN -> true + else -> false + } + } + .map { remote -> + val messageBuilder: UserMessage.Builder = UserMessage.newBuilder() + val textBuilder: MultiLanguageText.Builder = MultiLanguageText.newBuilder() + + MultiLanguageTextProcessor.addMultiLanguageText( + textBuilder, + Locale.ENGLISH, + "Battery level of ${ + LabelProcessor.getBestMatch( + Locale.ENGLISH, + remote.config.label + ) + } is ${remote.data.batteryState.value}" + ) + + MultiLanguageTextProcessor.addMultiLanguageText( + textBuilder, + Locale.GERMAN, + "Batteriezustand von ${ + LabelProcessor.getBestMatch( + Locale.GERMAN, + remote.config.label + ) + } ist ${remote.data.batteryState.value}" + ) + + messageBuilder.id = UUID.randomUUID().toString() + messageBuilder.messageType = UserMessage.MessageType.WARNING + messageBuilder.timestamp = TimestampProcessor.getCurrentTimestamp() + messageBuilder.text = textBuilder.build() + messageBuilder.senderId = userConfig.id + messageBuilder.addCondition( + with(ServiceStateDescription.newBuilder()) { + setUnitId(remote.id) + setServiceType(ServiceType.BATTERY_STATE_SERVICE) + setServiceStateClassName(BatteryState::class.java.name) + setServiceState( + ServiceStateProcessor.serializeServiceState( + BatteryState.newBuilder().setValue(remote.data.batteryState.value).build(), + false, + ) + ) + }.also { ServiceStateProcessor.deserializeServiceState(it) } + ) + + // validate if message already exist + messageBuilder.build() to Registries.getMessageRegistry() + .getUserMessagesByText( + MultiLanguageTextProcessor.getBestMatch( + Locale.ENGLISH, messageBuilder.text + ), Locale.ENGLISH + ).isNotEmpty() + } + .filterNot { (_, exist) -> exist } + .forEach { (message, _) -> + Registries.getMessageRegistry() + .registerUserMessageAuthenticated(message, token) + .get(5, TimeUnit.SECONDS) + } + } catch (e: CouldNotPerformException) { + ExceptionPrinter.printHistory("Could not check device states!", e, logger) + } + } + + companion object { + private val LOGGER = LoggerFactory.getLogger(TemplateApp::class.java) + + private val VALIDATION_PERIOD: Duration = Duration.ofHours(24) + private val INITIAL_VALIDATION_DELAY: Duration = Duration.ofHours(1) + } +} diff --git a/module/app/preset/src/main/java/org/openbase/bco/app/preset/TemplateApp.kt b/module/app/preset/src/main/java/org/openbase/bco/app/preset/TemplateApp.kt new file mode 100644 index 0000000000..13f5e0dbdb --- /dev/null +++ b/module/app/preset/src/main/java/org/openbase/bco/app/preset/TemplateApp.kt @@ -0,0 +1,67 @@ +package org.openbase.bco.app.preset + +import org.openbase.bco.dal.control.layer.unit.app.AbstractAppController +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.type.domotic.action.ActionDescriptionType.ActionDescription +import org.openbase.type.domotic.state.ActivationStateType +import org.openbase.type.domotic.state.ActivationStateType.ActivationState +import org.openbase.type.domotic.unit.UnitConfigType +import org.slf4j.LoggerFactory + +/* +* #%L +* BCO App Preset +* %% +* Copyright (C) 2018 - 2021 openbase.org +* %% +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU 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 Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program. If not, see +* . +* #L% +*/ /** + * A class that can be used as template for new apps. + */ +class TemplateApp : AbstractAppController() { + + private var executing = false + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun applyConfigUpdate(config: UnitConfigType.UnitConfig): UnitConfigType.UnitConfig = + getManageWriteLockInterruptible(this).use { + super.applyConfigUpdate(config).also { + LOGGER.info(getLabel() + " config has been changed.") + } + } + + override fun shutdown() = super.shutdown().also { + LOGGER.info(getLabel() + " is shutting down.") + } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun execute(activationState: ActivationStateType.ActivationState): ActionDescription = + activationState.responsibleAction.also { + executing = true + LOGGER.info(getLabel() + " is running.") + } + + @Throws(InterruptedException::class, CouldNotPerformException::class) + override fun stop(activationState: ActivationState) = + super.stop(activationState).also { + executing = false + LOGGER.info(getLabel() + " has been stopped.") + } + + companion object { + private val LOGGER = LoggerFactory.getLogger(TemplateApp::class.java) + } +} diff --git a/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt b/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt new file mode 100644 index 0000000000..700afc72ea --- /dev/null +++ b/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt @@ -0,0 +1,112 @@ +package org.openbase.bco.app.preset + +import io.kotest.matchers.equals.shouldBeEqual +import org.junit.jupiter.api.Test +import org.openbase.app.test.agent.AbstractBCOAppManagerTest +import org.openbase.bco.dal.control.layer.unit.BatteryController +import org.openbase.bco.dal.lib.layer.unit.Unit +import org.openbase.bco.dal.lib.state.States +import org.openbase.bco.dal.remote.layer.unit.BatteryRemote +import org.openbase.bco.dal.remote.layer.unit.Units +import org.openbase.bco.dal.remote.layer.unit.util.UnitStateAwaiter +import org.openbase.bco.registry.mock.MockRegistry +import org.openbase.bco.registry.remote.Registries +import org.openbase.jul.extension.type.processing.MultiLanguageTextProcessor +import org.openbase.type.domotic.communication.UserMessageType.UserMessage +import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServiceType +import org.openbase.type.domotic.state.ActivationStateType.ActivationState +import org.openbase.type.domotic.state.BatteryStateType.BatteryState +import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig +import org.openbase.type.domotic.unit.UnitTemplateType +import org.openbase.type.domotic.unit.app.AppDataType.AppData +import java.util.* +import java.util.concurrent.TimeUnit + +class DeviceNotificationAppTest : AbstractBCOAppManagerTest() { + + private val APP_ALIAS = "Device_Notification_App_Test" + override fun getAppClass() = DeviceNotificationApp::class.java + + override fun getAppConfig(): UnitConfig.Builder = MockRegistry.generateAppConfig( + APP_ALIAS, + MockRegistry.ALIAS_LOCATION_ROOT_PARADISE + ) + + private fun getUserMessagesOfUnit(unit: Unit<*>): List = run { + Registries.getMessageRegistry().requestData().get(5, TimeUnit.SECONDS) + }.let { + Registries.getMessageRegistry().userMessages + }.filter { it.conditionList.any { condition -> condition.unitId == unit.id } } + + @Test + fun testLowBatteryNotification() { + println("testAbsenceEnergySavingAgent") + + val unitStateAwaiter = UnitStateAwaiter(appRemote) + unitStateAwaiter.waitForState { data: AppData -> + data.activationState.getValue() == ActivationState.State.ACTIVE + } + + val location = Units.getUnitByAlias(MockRegistry.ALIAS_LOCATION_STAIRWAY_TO_HEAVEN, true, Units.LOCATION) + val batteryRemote: BatteryRemote = + location.getUnits(UnitTemplateType.UnitTemplate.UnitType.BATTERY, true, Units.BATTERY).first() + + val batteryStateAwaiter = UnitStateAwaiter(batteryRemote) + val batteryController = + deviceManagerLauncher.launchable!!.unitControllerRegistry.get(batteryRemote.getId()) as BatteryController + + // verify ground truth + getUserMessagesOfUnit(batteryRemote) shouldBeEqual emptyList() + + appController!!.checkDevices() +// messageManagerLauncher.launchable!!.removeOutdatedMessages() + Thread.sleep(2000) + + // verify unknown battery state notification + getUserMessagesOfUnit(batteryRemote).size shouldBeEqual 1 + + batteryController.applyServiceState(States.Battery.OK, ServiceType.BATTERY_STATE_SERVICE) + batteryStateAwaiter.waitForState { it.batteryState.value == BatteryState.State.OK } + batteryRemote.requestData().get(5, TimeUnit.SECONDS) + + + appController!!.checkDevices() +// messageManagerLauncher.launchable!!.removeOutdatedMessages() + + Thread.sleep(2000) + + // verify everything is ok again + getUserMessagesOfUnit(batteryRemote) shouldBeEqual emptyList() + + batteryController.applyServiceState(States.Battery.CRITICAL, ServiceType.BATTERY_STATE_SERVICE) + batteryStateAwaiter.waitForState { it.batteryState.value == BatteryState.State.CRITICAL } + + appController!!.checkDevices() + messageManagerLauncher.launchable!!.removeOutdatedMessages() + + // verify critical battery state notification + getUserMessagesOfUnit(batteryRemote) + .also { it.size shouldBeEqual 1 } + .first().apply { + messageType shouldBeEqual UserMessage.MessageType.WARNING + MultiLanguageTextProcessor.getBestMatch( + Locale.GERMAN, + text + ) shouldBeEqual "Batteriezustand von F Motion Sensor Device Stairway ist CRITICAL" + MultiLanguageTextProcessor.getBestMatch( + Locale.ENGLISH, + text + ) shouldBeEqual "Battery level of F Motion Sensor Device Stairway is CRITICAL" + + } + + batteryController.applyServiceState(States.Battery.OK, ServiceType.BATTERY_STATE_SERVICE) + batteryStateAwaiter.waitForState { it.batteryState.value == BatteryState.State.OK } + + appController!!.checkDevices() + messageManagerLauncher.launchable!!.removeOutdatedMessages() + + // verify all messages are removed after the battery has been replaced. + getUserMessagesOfUnit(batteryRemote) shouldBeEqual emptyList() + } +} diff --git a/module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.java b/module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.java deleted file mode 100644 index 76ac4a44bc..0000000000 --- a/module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.openbase.app.test.agent; - -/*- - * #%L - * BCO App Test Framework - * %% - * Copyright (C) 2018 - 2021 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Timeout; -import org.openbase.bco.dal.remote.layer.unit.Units; -import org.openbase.bco.dal.remote.layer.unit.app.AppRemote; -import org.openbase.bco.dal.remote.layer.unit.util.UnitStateAwaiter; -import org.openbase.bco.registry.remote.Registries; -import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.exception.printer.ExceptionPrinter; -import org.openbase.jul.extension.type.processing.LabelProcessor; -import org.openbase.type.domotic.state.ActivationStateType; -import org.openbase.type.domotic.state.ConnectionStateType.ConnectionState; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.unit.UnitTemplateType; -import org.openbase.type.domotic.unit.app.AppClassType.AppClass; -import org.slf4j.LoggerFactory; - -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -public abstract class AbstractBCOAppManagerTest extends BCOAppTest { - - private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AbstractBCOAgentManagerTest.class); - - protected AppClass appClass = null; - protected UnitConfig appConfig = null; - protected AppRemote appRemote = null; - - @BeforeEach - @Timeout(30) - public void prepareAppManager() throws Exception { - try { - // setup and register app class - AppClass.Builder appClassBuilder = AppClass.newBuilder(); - LabelProcessor.addLabel(appClassBuilder.getLabelBuilder(), Locale.ENGLISH, getAppClass().getSimpleName().replace("App", "")); - this.appClass = Registries.getClassRegistry().registerAppClass(appClassBuilder.build()).get(5, TimeUnit.SECONDS); - - UnitConfig.Builder appConfigBuilder = getAppConfig(); - appConfigBuilder.getAppConfigBuilder().setAppClassId(this.appClass.getId()); - appConfigBuilder.setUnitType(UnitTemplateType.UnitTemplate.UnitType.APP); - // register app - this.appConfig = Registries.getUnitRegistry().registerUnitConfig(appConfigBuilder.build()).get(5, TimeUnit.SECONDS); - // retrieve remote and activate app - this.appRemote = Units.getUnit(this.appConfig, true, Units.APP); - if (!this.appConfig.getAppConfig().getAutostart()) { - // activate app if not in auto start - waitForExecution(this.appRemote.setActivationState(ActivationStateType.ActivationState.newBuilder().setValue(ActivationStateType.ActivationState.State.ACTIVE).build())); - } else { - // wait until active - new UnitStateAwaiter<>(this.appRemote).waitForState(data -> data.getActivationState().getValue() == ActivationStateType.ActivationState.State.ACTIVE); - } - } catch (Exception ex) { - throw ExceptionPrinter.printHistoryAndReturnThrowable(ex, LOGGER); - } - } - - @AfterEach - @Timeout(30) - public void removeAgent() throws Exception { - Registries.getUnitRegistry().removeUnitConfig(appConfig); - appRemote.waitForConnectionState(ConnectionState.State.DISCONNECTED); - } - - public abstract Class getAppClass(); - - public abstract UnitConfig.Builder getAppConfig() throws CouldNotPerformException; -} diff --git a/module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.kt b/module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.kt new file mode 100644 index 0000000000..7e2d96cf84 --- /dev/null +++ b/module/app/test/src/main/java/org/openbase/app/test/agent/AbstractBCOAppManagerTest.kt @@ -0,0 +1,124 @@ +package org.openbase.app.test.agent + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Timeout +import org.openbase.bco.dal.control.layer.unit.app.AbstractAppController +import org.openbase.bco.dal.remote.layer.unit.Units +import org.openbase.bco.dal.remote.layer.unit.app.AppRemote +import org.openbase.bco.dal.remote.layer.unit.util.UnitStateAwaiter +import org.openbase.bco.registry.remote.Registries +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.printer.ExceptionPrinter +import org.openbase.jul.extension.type.processing.LabelProcessor.addLabel +import org.openbase.type.domotic.state.ActivationStateType +import org.openbase.type.domotic.state.ConnectionStateType +import org.openbase.type.domotic.unit.UnitConfigType +import org.openbase.type.domotic.unit.UnitTemplateType +import org.openbase.type.domotic.unit.app.AppClassType +import org.openbase.type.domotic.unit.app.AppDataType +import org.slf4j.LoggerFactory +import java.util.* +import java.util.concurrent.TimeUnit + +/*- +* #%L +* BCO App Test Framework +* %% +* Copyright (C) 2018 - 2021 openbase.org +* %% +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU 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 Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program. If not, see +* . +* #L% +*/ +abstract class AbstractBCOAppManagerTest : BCOAppTest() { + protected var appClass: AppClassType.AppClass? = null + protected var appConfig: UnitConfigType.UnitConfig? = null + protected var appRemote: AppRemote? = null + protected var appController: APP_CLASS? = null + + @BeforeEach + @Timeout(30) + @Throws(Exception::class) + fun prepareAppManager() { + try { + // setup and register app class + val appClassBuilder = AppClassType.AppClass.newBuilder() + addLabel( + appClassBuilder.getLabelBuilder(), + Locale.ENGLISH, + getAppClass().getSimpleName().replace("App", "") + ) + val appClass = Registries.getClassRegistry().registerAppClass(appClassBuilder.build())[5, TimeUnit.SECONDS] + val appConfigBuilder = getAppConfig() + appConfigBuilder.getAppConfigBuilder().setAppClassId(appClass.getId()) + appConfigBuilder.setUnitType(UnitTemplateType.UnitTemplate.UnitType.APP) + // register app + val appConfig = Registries.getUnitRegistry() + .registerUnitConfig(appConfigBuilder.build())[5, TimeUnit.SECONDS] + + Registries.waitUntilReady() + + // retrieve remote and activate app + val appRemote = Units.getUnit(appConfig, true, Units.APP) + if (!appConfig.appConfig.autostart) { + // activate app if not in auto start + waitForExecution( + appRemote.setActivationState( + ActivationStateType.ActivationState.newBuilder().setValue( + ActivationStateType.ActivationState.State.ACTIVE + ).build() + ) + ) + } else { + // wait until active + UnitStateAwaiter(appRemote).waitForState { data: AppDataType.AppData -> data.activationState.getValue() == ActivationStateType.ActivationState.State.ACTIVE } + } + + this.appClass = appClass + this.appConfig = appConfig + this.appRemote = appRemote + this.appController = + appManagerLauncher.launchable!!.appControllerRegistry.get(appConfig?.getId()) as APP_CLASS + } catch (ex: Exception) { + throw ExceptionPrinter.printHistoryAndReturnThrowable(ex, LOGGER) + } + } + + @AfterEach + @Timeout(30) + @Throws(Exception::class) + fun removeAgent() { + if (appConfig != null) { + Registries.getUnitRegistry().removeUnitConfig(appConfig).get() + } + + if (appRemote != null) { + appRemote!!.waitForConnectionState(ConnectionStateType.ConnectionState.State.DISCONNECTED) + } + + if (appClass != null) { + Registries.getClassRegistry().removeAppClass(appClass).get() + } + } + + abstract fun getAppClass(): Class + + @Throws(CouldNotPerformException::class) + abstract fun getAppConfig(): UnitConfigType.UnitConfig.Builder + + companion object { + private val LOGGER = LoggerFactory.getLogger(AbstractBCOAgentManagerTest::class.java) + } +} diff --git a/module/app/test/src/main/java/org/openbase/app/test/agent/BCOAppTest.java b/module/app/test/src/main/java/org/openbase/app/test/agent/BCOAppTest.java index 4e76fc78ad..2fd9c73e49 100644 --- a/module/app/test/src/main/java/org/openbase/app/test/agent/BCOAppTest.java +++ b/module/app/test/src/main/java/org/openbase/app/test/agent/BCOAppTest.java @@ -30,6 +30,7 @@ import org.openbase.bco.dal.control.layer.unit.app.AppManagerLauncher; import org.openbase.bco.dal.control.layer.unit.device.DeviceManagerLauncher; import org.openbase.bco.dal.control.layer.unit.location.LocationManagerLauncher; +import org.openbase.bco.dal.control.message.MessageManagerLauncher; import org.openbase.bco.dal.lib.layer.unit.UnitController; import org.openbase.bco.dal.test.AbstractBCOTest; import org.openbase.bco.registry.remote.Registries; @@ -46,6 +47,7 @@ public class BCOAppTest extends AbstractBCOTest { protected static AppManagerLauncher appManagerLauncher; protected static DeviceManagerLauncher deviceManagerLauncher; protected static LocationManagerLauncher locationManagerLauncher; + protected static MessageManagerLauncher messageManagerLauncher; @BeforeAll @Timeout(30) @@ -67,6 +69,10 @@ public static void setupBcoApp() throws Throwable { appManagerLauncher = new AppManagerLauncher(); appManagerLauncher.launch().get(); + LOGGER.trace("Start message manager..."); + messageManagerLauncher = new MessageManagerLauncher(); + messageManagerLauncher.launch().get(); + LOGGER.trace("Finally wait for registry..."); Registries.waitForData(); diff --git a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOLauncher.java b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOLauncher.java index 25d5495524..4a2d932a90 100644 --- a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOLauncher.java +++ b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOLauncher.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -32,6 +32,7 @@ import org.openbase.bco.dal.control.layer.unit.location.LocationManagerLauncher; import org.openbase.bco.dal.control.layer.unit.scene.SceneManagerLauncher; import org.openbase.bco.dal.control.layer.unit.user.UserManagerLauncher; +import org.openbase.bco.dal.control.message.MessageManagerLauncher; import org.openbase.bco.device.openhab.OpenHABDeviceManagerLauncher; import org.openbase.bco.device.openhab.registry.OpenHABConfigSynchronizerLauncher; import org.openbase.bco.device.openhab.sitemap.OpenHABSitemapSynchronizerLauncher; @@ -57,7 +58,7 @@ public static void main(final String[] args) { BCO.printLogo(); // create dynamic launcher container - ArrayList> launcher = new ArrayList<>(); + ArrayList>> launcher = new ArrayList<>(); /** * Configure Authenticator Launcher @@ -82,6 +83,7 @@ public static void main(final String[] args) { launcher.add(LocationManagerLauncher.class); launcher.add(SceneManagerLauncher.class); launcher.add(UserManagerLauncher.class); + launcher.add(MessageManagerLauncher.class); /** * API Launcher diff --git a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOTestLauncher.java b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOTestLauncher.java index dbc69570fd..0e8e219702 100644 --- a/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOTestLauncher.java +++ b/module/app/util/src/main/java/org/openbase/bco/app/util/launch/BCOTestLauncher.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -24,22 +24,22 @@ import org.openbase.bco.api.graphql.BcoApiGraphQlLauncher; import org.openbase.bco.authentication.core.AuthenticatorLauncher; +import org.openbase.bco.authentication.lib.BCO; import org.openbase.bco.dal.control.layer.unit.agent.AgentManagerLauncher; import org.openbase.bco.dal.control.layer.unit.app.AppManagerLauncher; import org.openbase.bco.dal.control.layer.unit.device.DeviceManagerLauncher; import org.openbase.bco.dal.control.layer.unit.location.LocationManagerLauncher; import org.openbase.bco.dal.control.layer.unit.scene.SceneManagerLauncher; import org.openbase.bco.dal.control.layer.unit.user.UserManagerLauncher; +import org.openbase.bco.dal.control.message.MessageManagerLauncher; import org.openbase.bco.dal.lib.jp.JPProviderControlMode; import org.openbase.bco.registry.activity.core.ActivityRegistryLauncher; import org.openbase.bco.registry.clazz.core.ClassRegistryLauncher; -import org.openbase.bco.authentication.lib.BCO; import org.openbase.bco.registry.message.core.MessageRegistryLauncher; import org.openbase.bco.registry.template.core.TemplateRegistryLauncher; import org.openbase.bco.registry.unit.core.UnitRegistryLauncher; import org.openbase.jps.core.JPService; import org.openbase.jul.pattern.launch.AbstractLauncher; -import org.openbase.jul.pattern.launch.jp.JPPrintLauncher; /** * @author Divine Threepwood @@ -76,6 +76,7 @@ public static void main(final String[] args) { LocationManagerLauncher.class, SceneManagerLauncher.class, UserManagerLauncher.class, + MessageManagerLauncher.class, /* * API Launcher diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java index 8b63a196e5..fec9a639fb 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -63,7 +63,9 @@ public abstract class AbstractAuthorizedBaseUnitController observedTaskList; + protected UnitConfig userConfig = null; + + private final ArrayList observedTaskList; private final static ProtoBufJSonProcessor protoBufJSonProcessor = new ProtoBufJSonProcessor(); @@ -93,10 +95,10 @@ public UnitConfig applyConfigUpdate(final UnitConfig config) throws CouldNotPerf private AuthToken requestAuthToken(final UnitConfig unitConfig) throws CouldNotPerformException, InterruptedException { try { - final UnitConfig userUnitConfig = UnitUserCreationPlugin.findUser(unitConfig.getId(), Registries.getUnitRegistry().getUnitConfigsByUnitType(UnitType.USER)); - final AuthenticationToken authenticationToken = AuthenticationToken.newBuilder().setUserId(userUnitConfig.getId()).build(); + userConfig = UnitUserCreationPlugin.findUser(unitConfig.getId(), Registries.getUnitRegistry(true).getUnitConfigsByUnitType(UnitType.USER)); + final AuthenticationToken authenticationToken = AuthenticationToken.newBuilder().setUserId(userConfig.getId()).build(); final SessionManager sessionManager = new SessionManager(); - sessionManager.loginUser(userUnitConfig.getId(), true); + sessionManager.loginUser(userConfig.getId(), true); final AuthenticatedValue authenticatedValue = sessionManager.initializeRequest(authenticationToken, null); return AuthToken.newBuilder().setAuthenticationToken(new AuthenticatedValueFuture<>( Registries.getUnitRegistry().requestAuthenticationTokenAuthenticated(authenticatedValue), @@ -149,6 +151,7 @@ protected AuthToken getToken() { * Additionally, the auth token of this controller is passed to the remote action and the action auto extension routine is enabled. * * @param futureAction used to identify the action to observe. + * * @return a ready to use action remote instance. */ protected RemoteAction observe(final Future futureAction) { diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java index 22432b8452..93f10865dd 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -78,7 +78,7 @@ public class InfluxDbProcessor { public static final String INFLUXDB_BATCH_LIMIT = "INFLUXDB_BATCH_LIMIT"; public static final String INFLUXDB_BATCH_LIMIT_DEFAULT = "100"; public static final String INFLUXDB_URL = "INFLUXDB_URL"; - public static final String INFLUXDB_URL_DEFAULT = "http://localhost:8086"; + public static final String INFLUXDB_URL_DEFAULT = "http://influxdb:8086"; public static final String INFLUXDB_ORG = "INFLUXDB_ORG"; public static final String INFLUXDB_ORG_DEFAULT = "openbase"; public static final String INFLUXDB_TOKEN = "INFLUXDB_TOKEN"; @@ -458,7 +458,7 @@ public static Future queryAggregatedServiceState(final Q return FutureProcessor.completedFuture(newAggregatedServiceState); } catch (CouldNotPerformException ex) { - return FutureProcessor.canceledFuture(AggregatedServiceState.class,new CouldNotPerformException("Could not query aggregated service state", ex)); + return FutureProcessor.canceledFuture(AggregatedServiceState.class, new CouldNotPerformException("Could not query aggregated service state", ex)); } } } diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppControllerFactoryImpl.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppControllerFactoryImpl.java index 355ec29991..9c96835574 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppControllerFactoryImpl.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/app/AppControllerFactoryImpl.java @@ -10,35 +10,35 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . * #L% */ + import org.openbase.bco.dal.lib.layer.unit.app.App; import org.openbase.bco.dal.lib.layer.unit.app.AppController; import org.openbase.bco.dal.lib.layer.unit.app.AppControllerFactory; import org.openbase.bco.registry.remote.Registries; import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.NotAvailableException; +import org.openbase.jul.extension.type.processing.LabelProcessor; import org.openbase.jul.processing.StringProcessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; import org.openbase.type.domotic.unit.app.AppClassType.AppClass; -import org.openbase.jul.extension.type.processing.LabelProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; import java.util.Locale; /** - * * @author Tamino Huxohl */ public class AppControllerFactoryImpl implements AppControllerFactory { @@ -73,9 +73,11 @@ public AppController newInstance(final UnitConfig appUnitConfig) throws org.open try { // try to load preset app String className = PRESET_APP_PACKAGE_PREFIX - + "." + LabelProcessor.getLabelByLanguage(Locale.ENGLISH, appClass.getLabel()) + "App"; + + "." + StringProcessor.removeWhiteSpaces(LabelProcessor.getLabelByLanguage(Locale.ENGLISH, appClass.getLabel())) + "App"; app = (AppController) Thread.currentThread().getContextClassLoader().loadClass(className).getConstructor().newInstance(); - } catch (CouldNotPerformException | ClassNotFoundException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | NoSuchMethodException | InvocationTargetException ex) { + } catch (CouldNotPerformException | ClassNotFoundException | SecurityException | InstantiationException | + IllegalAccessException | IllegalArgumentException | NoSuchMethodException | + InvocationTargetException ex) { // try to load custom app String className = CUSTOM_APP_PACKAGE_PREFIX + "." + StringProcessor.removeWhiteSpaces(LabelProcessor.getLabelByLanguage(Locale.ENGLISH, appClass.getLabel())).toLowerCase() @@ -84,7 +86,9 @@ public AppController newInstance(final UnitConfig appUnitConfig) throws org.open } logger.debug("Creating app of type [" + LabelProcessor.getBestMatch(appClass.getLabel()) + "]"); app.init(appUnitConfig); - } catch (CouldNotPerformException | ClassNotFoundException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InterruptedException | NoSuchMethodException | InvocationTargetException ex) { + } catch (CouldNotPerformException | ClassNotFoundException | SecurityException | InstantiationException | + IllegalAccessException | IllegalArgumentException | InterruptedException | NoSuchMethodException | + InvocationTargetException ex) { throw new org.openbase.jul.exception.InstantiationException(App.class, appUnitConfig.getId(), ex); } return app; diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/scene/SceneControllerImpl.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/scene/SceneControllerImpl.java index 8062e2903a..817ddd693c 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/scene/SceneControllerImpl.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/scene/SceneControllerImpl.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -55,6 +55,7 @@ import org.openbase.jul.schedule.TimeoutSplitter; import org.openbase.type.domotic.action.ActionDescriptionType.ActionDescription; import org.openbase.type.domotic.action.ActionParameterType.ActionParameter; +import org.openbase.type.domotic.action.ActionPriorityType.ActionPriority.Priority; import org.openbase.type.domotic.action.ActionReferenceType.ActionReference; import org.openbase.type.domotic.authentication.AuthenticatedValueType.AuthenticatedValue; import org.openbase.type.domotic.service.ServiceStateDescriptionType.ServiceStateDescription; @@ -72,6 +73,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.time.Duration; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.CancellationException; @@ -195,7 +197,8 @@ public UnitConfig applyConfigUpdate(UnitConfig config) throws CouldNotPerformExc final ActionParameter actionParameterPrototype = ActionParameter.newBuilder() .setInterruptible(true) .setSchedulable(true) - .setExecutionTimePeriod(Long.MAX_VALUE).build(); + .setPriority(Priority.HIGH) + .setExecutionTimePeriod(Duration.ofMinutes(30).toMillis()).build(); requiredActionPool.initViaServiceStateDescription(config.getSceneConfig().getRequiredServiceStateDescriptionList(), actionParameterPrototype, () -> getActivationState().getValue() == ActivationState.State.ACTIVE); optionalActionPool.initViaServiceStateDescription(config.getSceneConfig().getOptionalServiceStateDescriptionList(), actionParameterPrototype, () -> getActivationState().getValue() == ActivationState.State.ACTIVE); return config; @@ -272,7 +275,7 @@ public synchronized Future setActivationState(final Activatio // shutdown all existing action observer to not let old observation interfere with new activations. - if(actionObserver != null) { + if (actionObserver != null) { actionObserver.shutdown(); } @@ -436,7 +439,7 @@ private RequiredActionObserver(final List requiredActionImpact, private void verifyAllStates() { try { - logger.trace(() -> "verify "+unitAndRequiredServiceStateMap.entrySet().size()+ " states of "+ getLabel("?")); + logger.trace(() -> "verify " + unitAndRequiredServiceStateMap.entrySet().size() + " states of " + getLabel("?")); for (Entry, RequiredServiceDescription> unitActionReferenceEntry : unitAndRequiredServiceStateMap.entrySet()) { try { // skip unit in case its offline, since then the verification is automatically @@ -457,7 +460,7 @@ private void verifyAllStates() { private void verifyState(final ServiceProvider unit, final Message serviceState) throws VerificationFailedException { // skip verification on destroyed required action observer! - if(destroy) { + if (destroy) { return; } @@ -466,13 +469,13 @@ private void verifyState(final ServiceProvider unit, final Me } // skip in case no service state was delivered - if(serviceState.toString().isBlank()) { + if (serviceState.toString().isBlank()) { return; } if (!Services.equalServiceStates(unitAndRequiredServiceStateMap.get(unit).getServiceState(), serviceState)) { logger.trace(() -> unitAndRequiredServiceStateMap.get(unit).getServiceState() + " is not equals " + serviceState.toString().substring(0, 20) + " and will cancel: " + SceneControllerImpl.this.getLabel("?")); - if(Actions.validateInitialAction(serviceState)) { + if (Actions.validateInitialAction(serviceState)) { throw new VerificationFailedException("State of " + unit + "not meet!"); } } diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt new file mode 100644 index 0000000000..91f90d6ac7 --- /dev/null +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt @@ -0,0 +1,106 @@ +package org.openbase.bco.dal.control.message + +import com.google.protobuf.Message +import org.openbase.bco.dal.lib.layer.service.Services +import org.openbase.bco.dal.lib.layer.unit.UnitRemote +import org.openbase.bco.dal.remote.layer.unit.Units +import org.openbase.bco.registry.remote.Registries +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.InitializationException +import org.openbase.jul.iface.Launchable +import org.openbase.jul.iface.VoidInitializable +import org.openbase.jul.pattern.Observer +import org.openbase.jul.pattern.provider.DataProvider +import org.openbase.jul.schedule.RecurrenceEventFilter +import org.openbase.type.domotic.registry.MessageRegistryDataType.MessageRegistryData +import org.slf4j.LoggerFactory +import java.time.Duration +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.write + +class MessageManager : Launchable, VoidInitializable { + + private var logger = LoggerFactory.getLogger(MessageManager::class.java) + + private val unitsOfConditionsLock = ReentrantReadWriteLock() + + private val maxUpdateInterval: Duration = Duration.ofSeconds(1) + + private var active = false + + init { + logger.error("#@# start message manager") + } + + private val removeOutdatedMessagesTask: RecurrenceEventFilter = + object : RecurrenceEventFilter(maxUpdateInterval.toMillis()) { + override fun relay() { + removeOutdatedMessages() + } + } + + fun removeOutdatedMessages() { + logger.error("#@# removeOutdatedMessages") + Registries.getMessageRegistry().userMessages + .filterNot { message -> + message.conditionList.any { condition -> + Units.getUnit(condition.unitId, true).let { unit -> + Services.equalServiceStates( + unit.getServiceState(condition.serviceType), + Services.deserializeServiceState(condition) + ) + } + } + } + .forEach { Registries.getMessageRegistry().removeUserMessage(it).get(5, TimeUnit.SECONDS) } + } + + private var unitsOfConditions: List>? = null + + private val conditionObserver: Observer, Message> = + Observer { _, _ -> removeOutdatedMessagesTask.trigger() } + + private val messageRegistryChangeObserver: Observer, MessageRegistryData> = + Observer { _, data -> + unitsOfConditionsLock.write { + unitsOfConditions?.forEach { it.removeDataObserver(conditionObserver) } + unitsOfConditions = data.userMessageList.let { userMessages -> + userMessages.flatMap { it.conditionList } + .map { it.unitId } + .distinct() + .mapNotNull { unitId -> + try { + Units.getUnit(unitId, false) + } catch (e: CouldNotPerformException) { + logger.warn("Could not resolve unit with id $unitId", e) + null + } + } + } + unitsOfConditions?.forEach { it.addDataObserver(conditionObserver) } + } + } + + @Throws(InitializationException::class, InterruptedException::class) + override fun init() { + // this overwrite is needed to overwrite the default implementation! + } + + override fun activate() { + logger.error("#@# activate message manager") + active = true + Registries.getMessageRegistry().addDataObserver(messageRegistryChangeObserver) + } + + override fun deactivate() { + Registries.getMessageRegistry().removeDataObserver(messageRegistryChangeObserver) + unitsOfConditionsLock.write { + unitsOfConditions?.forEach { it.removeDataObserver(conditionObserver) } + unitsOfConditions = null + } + active = false + } + + override fun isActive(): Boolean = active +} diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManagerLauncher.kt b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManagerLauncher.kt new file mode 100644 index 0000000000..8f0cf54aa9 --- /dev/null +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManagerLauncher.kt @@ -0,0 +1,32 @@ +package org.openbase.bco.dal.control.message + +import org.openbase.bco.authentication.lib.BCO +import org.openbase.bco.authentication.lib.jp.JPBCODistributionDirectory +import org.openbase.bco.dal.lib.jp.JPBenchmarkMode +import org.openbase.bco.dal.lib.jp.JPHardwareSimulationMode +import org.openbase.bco.dal.lib.jp.JPProviderControlMode +import org.openbase.jps.core.JPService +import org.openbase.jul.pattern.launch.AbstractLauncher + +class MessageManagerLauncher : + AbstractLauncher(MessageManager::class.java, MessageManager::class.java) { + public override fun loadProperties() { + JPService.registerProperty(JPBCODistributionDirectory::class.java) + JPService.registerProperty(JPHardwareSimulationMode::class.java) + JPService.registerProperty(JPBenchmarkMode::class.java) + JPService.registerProperty(JPProviderControlMode::class.java) + } + + companion object { + @Throws(Throwable::class) + @JvmStatic + fun main(args: Array) { + BCO.printLogo() + main( + BCO::class.java, + MessageManager::class.java, args, + MessageManagerLauncher::class.java + ) + } + } +} diff --git a/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/layer/service/ServiceStateProcessor.java b/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/layer/service/ServiceStateProcessor.java index 2c2a900730..53edebebef 100644 --- a/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/layer/service/ServiceStateProcessor.java +++ b/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/layer/service/ServiceStateProcessor.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 * . @@ -34,6 +34,7 @@ import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.FatalImplementationErrorException; import org.openbase.jul.exception.NotAvailableException; +import org.openbase.jul.exception.VerificationFailedException; import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.extension.protobuf.ProtoBufBuilderProcessor; import org.openbase.jul.extension.protobuf.processing.ProtoBufFieldProcessor; @@ -48,7 +49,10 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationTargetException; -import java.util.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.function.Supplier; public class ServiceStateProcessor { @@ -181,7 +185,8 @@ public static void updateLatestValueOccurrence(final EnumValueDescriptor enumVal entryMessageBuilder.setField(valueDescriptor, timestamp); entryMessageBuilder.setField(keyDescriptor, enumValueDescriptor); entryMessage = entryMessageBuilder.build(); - } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | ClassCastException ex) { + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | + ClassCastException ex) { throw new CouldNotPerformException("Could not build service state entry!", ex); } @@ -317,7 +322,7 @@ private static Set computeActionImpact(final ServiceStateDesc // handle termination: // make sure duplicated service states are only processed ones. - if(processedServiceStates.contains(serviceStateDescription)) { + if (processedServiceStates.contains(serviceStateDescription)) { return Collections.emptySet(); } else { processedServiceStates.add(serviceStateDescription); @@ -377,7 +382,7 @@ private static Set computeActionImpact(final ServiceStateDesc } // register location itself as impact in case it's affected by child units through aggregation - if(!locationChildUnits.isEmpty()) { + if (!locationChildUnits.isEmpty()) { actionImpactList.add(buildActionImpact(serviceStateDescription).setIntermediary(true).build()); } break; @@ -440,6 +445,15 @@ public static String serializeServiceState(final Message serviceState, final boo } public static Message deserializeServiceState(final ServiceStateDescriptionOrBuilder serviceStateDescriptionOrBuilder) throws CouldNotPerformException { + + if (!serviceStateDescriptionOrBuilder.hasServiceState() || serviceStateDescriptionOrBuilder.getServiceState().isEmpty()) { + throw new VerificationFailedException("ServiceStateDescription does not provide any service state: " + serviceStateDescriptionOrBuilder); + } + + if (!serviceStateDescriptionOrBuilder.hasServiceStateClassName() || serviceStateDescriptionOrBuilder.getServiceStateClassName().isEmpty()) { + throw new VerificationFailedException("ServiceStateDescription does not provide any service state class name: " + serviceStateDescriptionOrBuilder); + } + return deserializeServiceState(serviceStateDescriptionOrBuilder.getServiceState(), serviceStateDescriptionOrBuilder.getServiceStateClassName()); } diff --git a/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/state/States.java b/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/state/States.java index a1ea493e92..ba0733bab1 100644 --- a/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/state/States.java +++ b/module/dal/lib/src/main/java/org/openbase/bco/dal/lib/state/States.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 * . @@ -23,8 +23,7 @@ */ import org.openbase.type.domotic.state.ActivationStateType.ActivationState; -import org.openbase.type.domotic.state.ActivationStateType.ActivationState.State; -import org.openbase.type.domotic.state.BlindStateType; +import org.openbase.type.domotic.state.BatteryStateType.BatteryState; import org.openbase.type.domotic.state.BlindStateType.BlindState; import org.openbase.type.domotic.state.BrightnessStateType.BrightnessState; import org.openbase.type.domotic.state.ColorStateType.ColorState; @@ -33,7 +32,6 @@ import org.openbase.type.domotic.state.MotionStateType.MotionState; import org.openbase.type.domotic.state.PowerStateType.PowerState; import org.openbase.type.vision.ColorType; -import org.openbase.type.vision.ColorType.Color; import org.openbase.type.vision.ColorType.Color.Type; import org.openbase.type.vision.HSBColorType.HSBColor; @@ -125,4 +123,14 @@ public static class Contact { public static final ContactState OPEN = ContactState.newBuilder().setValue(ContactState.State.OPEN).build(); public static final ContactState CLOSED = ContactState.newBuilder().setValue(ContactState.State.CLOSED).build(); } + + /** + * Battery State Prototypes + */ + public static class Battery { + public static final BatteryState OK = BatteryState.newBuilder().setValue(BatteryState.State.OK).build(); + public static final BatteryState LOW = BatteryState.newBuilder().setValue(BatteryState.State.LOW).build(); + public static final BatteryState CRITICAL = BatteryState.newBuilder().setValue(BatteryState.State.CRITICAL).build(); + public static final BatteryState INSUFFICIENT = BatteryState.newBuilder().setValue(BatteryState.State.INSUFFICIENT).build(); + } } diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java index 5bdb822d1b..0ea35e8516 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/action/RemoteAction.java @@ -643,6 +643,11 @@ public boolean isRunning() { */ @Override public boolean isDone() { + + if (futureObservationTask != null && !futureObservationTask.isDone()) { + return false; + } + try { if (getActionDescription().getIntermediary()) { for (final RemoteAction impactedRemoteAction : impactedRemoteActions) { diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java index 51e86b5bec..9fc1aa24ba 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java @@ -74,8 +74,8 @@ public class PresenceDetector implements Manageable, DataProvider, LocationData> locationDataObserver; private Location location; private final ObservableImpl, PresenceState> presenceStateObservable; - private final CustomUnitPool buttonUnitPool; - private final CustomUnitPool connectionUnitPool; + private final CustomUnitPool buttonUnitPool; + private final CustomUnitPool connectionUnitPool; private boolean active; private boolean shutdownInitiated = false; diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt index 209264df21..2aaa885cce 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPool.kt @@ -45,12 +45,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock * #L% */ -class CustomUnitPool : Manageable>> { +class CustomUnitPool> : Manageable>> { private val UNIT_REMOTE_REGISTRY_LOCK = ReentrantReadWriteLock() private var unitRegistryDataObserver: Observer, UnitRegistryDataType.UnitRegistryData> - private var unitDataObserver: Observer, Message> + private var unitDataObserver: Observer, M> private var serviceStateObserver: Observer, out Message> - private var unitRemoteRegistry: RemoteControllerRegistry> + private var unitRemoteRegistry: RemoteControllerRegistry private var unitConfigDiff: ProtobufListDiff private var filterSet: MutableSet> private var unitDataObservable: ObservableImpl, Message> @@ -184,7 +184,7 @@ class CustomUnitPool : Manageable>> FatalImplementationErrorException("unid remote registered but pool was never activated!", this) } val unitRemote: UnitRemote = Units.getUnit(unitId, false) - unitRemoteRegistry.register(unitRemote) + unitRemoteRegistry.register(unitRemote as U) for (serviceType in unitRemote.availableServiceTypes) { unitRemote.addServiceStateObserver(serviceType, serviceStateObserver) } @@ -298,7 +298,7 @@ class CustomUnitPool : Manageable>> override fun isActive(): Boolean = active - val internalUnitList: List> + val internalUnitList: List get() = unitRemoteRegistry.entries companion object { diff --git a/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.java b/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.java deleted file mode 100644 index b90e840077..0000000000 --- a/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.openbase.bco.dal.remote.layer.unit; - -/*- - * #%L - * BCO DAL Test - * %% - * Copyright (C) 2014 - 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% - */ - -import static org.junit.jupiter.api.Assertions.*; -import com.google.protobuf.Message; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; -import org.openbase.bco.dal.lib.layer.unit.UnitRemote; -import org.openbase.bco.dal.test.AbstractBCODeviceManagerTest; -import org.openbase.bco.registry.remote.Registries; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType; - -import java.util.List; -import java.util.concurrent.TimeUnit; - - - -public class CustomUnitPoolTest extends AbstractBCODeviceManagerTest { - - /** - * Test of getBatteryLevel method, of class BatteryRemote. - * - * @throws java.lang.Exception - */ - @Test - @Timeout(10) - public void testUnitPool() throws Exception { - final CustomUnitPool customUnitPool = new CustomUnitPool(); - assertEquals(false, customUnitPool.isActive(), "pool is active while never activated"); - - customUnitPool.activate(); - - customUnitPool.init( - unitConfig -> unitConfig.hasId(), - unitConfig -> unitConfig.getUnitType() == UnitType.BUTTON); - - customUnitPool.activate(); - - for (UnitRemote unitRemote : customUnitPool.getInternalUnitList()) { - assertEquals(UnitType.BUTTON, unitRemote.getUnitType(), "pool contains actually filtered entry!"); - System.out.println("is button: "+ unitRemote.getLabel()); - } - - final List buttonUnitConfig = Registries.getUnitRegistry().getUnitConfigsByUnitType(UnitType.BUTTON); - Registries.getUnitRegistry().updateUnitConfig(buttonUnitConfig.get(0).toBuilder().addAlias("MyButtonTestUnit").build()).get(5, TimeUnit.SECONDS); - - final List lightUnitConfig = Registries.getUnitRegistry().getUnitConfigsByUnitType(UnitType.COLORABLE_LIGHT); - Registries.getUnitRegistry().updateUnitConfig(lightUnitConfig.get(0).toBuilder().addAlias("MyLightestUnit").build()).get(5, TimeUnit.SECONDS); - - for (UnitRemote unitRemote : customUnitPool.getInternalUnitList()) { - assertEquals(UnitType.BUTTON, unitRemote.getUnitType(), "pool contains actually filtered entry!"); - System.out.println("is button: "+ unitRemote.getLabel()); - } - - customUnitPool.shutdown(); - } -} diff --git a/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt b/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt new file mode 100644 index 0000000000..521d47975e --- /dev/null +++ b/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt @@ -0,0 +1,77 @@ +package org.openbase.bco.dal.remote.layer.unit + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.openbase.bco.dal.test.AbstractBCODeviceManagerTest +import org.openbase.bco.registry.remote.Registries +import org.openbase.jul.pattern.Filter +import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig +import org.openbase.type.domotic.unit.UnitTemplateType +import java.util.concurrent.TimeUnit + +/*- +* #%L +* BCO DAL Test +* %% +* Copyright (C) 2014 - 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% +*/ +class CustomUnitPoolTest : AbstractBCODeviceManagerTest() { + /** + * Test of getBatteryLevel method, of class BatteryRemote. + * + * @throws java.lang.Exception + */ + @Test + @Timeout(10) + @Throws(Exception::class) + fun testUnitPool() { + val customUnitPool: CustomUnitPool<*, *> = CustomUnitPool() + Assertions.assertEquals(false, customUnitPool.isActive(), "pool is active while never activated") + customUnitPool.activate() + customUnitPool.init( + Filter { unitConfig: Any -> unitConfig.hasId() }, + Filter { unitConfig: Any -> unitConfig.getUnitType() === UnitTemplateType.UnitTemplate.UnitType.BUTTON }) + customUnitPool.activate() + for (unitRemote in customUnitPool.internalUnitList) { + Assertions.assertEquals( + UnitTemplateType.UnitTemplate.UnitType.BUTTON, + unitRemote.getUnitType(), + "pool contains actually filtered entry!" + ) + println("is button: " + unitRemote.getLabel()) + } + val buttonUnitConfig = + Registries.getUnitRegistry().getUnitConfigsByUnitType(UnitTemplateType.UnitTemplate.UnitType.BUTTON) + Registries.getUnitRegistry() + .updateUnitConfig(buttonUnitConfig[0].toBuilder().addAlias("MyButtonTestUnit").build())[5, TimeUnit.SECONDS] + val lightUnitConfig = Registries.getUnitRegistry() + .getUnitConfigsByUnitType(UnitTemplateType.UnitTemplate.UnitType.COLORABLE_LIGHT) + Registries.getUnitRegistry() + .updateUnitConfig(lightUnitConfig[0].toBuilder().addAlias("MyLightestUnit").build())[5, TimeUnit.SECONDS] + for (unitRemote in customUnitPool.internalUnitList) { + Assertions.assertEquals( + UnitTemplateType.UnitTemplate.UnitType.BUTTON, + unitRemote.getUnitType(), + "pool contains actually filtered entry!" + ) + println("is button: " + unitRemote.getLabel()) + } + customUnitPool.shutdown() + } +} diff --git a/module/dal/test/src/test/java/org/openbase/bco/dal/test/action/KotlinTest.kt b/module/dal/test/src/test/java/org/openbase/bco/dal/test/action/KotlinTest.kt deleted file mode 100644 index 715a56e404..0000000000 --- a/module/dal/test/src/test/java/org/openbase/bco/dal/test/action/KotlinTest.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.openbase.bco.dal.test.action - -class KotlinTest { -} diff --git a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/jp/JPOpenHABURI.java b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/jp/JPOpenHABURI.java index 0b07dfc370..f579407fe0 100644 --- a/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/jp/JPOpenHABURI.java +++ b/module/device/openhab/src/main/java/org/openbase/bco/device/openhab/jp/JPOpenHABURI.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -24,7 +24,6 @@ import org.openbase.jps.core.AbstractJavaProperty; -import java.io.File; import java.net.URI; import java.util.List; @@ -36,7 +35,7 @@ public class JPOpenHABURI extends AbstractJavaProperty { private static final String[] ARGUMENT_IDENTIFIERS = {"URI"}; private static final String[] COMMAND_IDENTIFIERS = {"--openhab-url", "--openhab-uri", "--uri"}; - private static final String DEFAULT_URI = "http://localhost:8080"; + private static final String DEFAULT_URI = "http://openhab:8080"; public static final String SYSTEM_VARIABLE_OPENHAB_PORT = "OPENHAB_HTTP_PORT"; @@ -56,7 +55,7 @@ protected URI getPropertyDefaultValue() { // use system variable if defined String systemDefinedPort = System.getenv(SYSTEM_VARIABLE_OPENHAB_PORT); if (systemDefinedPort != null) { - return URI.create("http://localhost:"+systemDefinedPort); + return URI.create("http://localhost:" + systemDefinedPort); } return URI.create(DEFAULT_URI); diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/com/AbstractRegistryController.java b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/com/AbstractRegistryController.java index ecb4f1efc5..5a804588d8 100644 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/com/AbstractRegistryController.java +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/com/AbstractRegistryController.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -190,7 +190,7 @@ public void activate() throws InterruptedException, CouldNotPerformException { performInitialConsistencyCheck(); } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not activate "+this+" registry!", ex); + throw new CouldNotPerformException("Could not activate " + this + " registry!", ex); } } diff --git a/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java b/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java index da7a5366f5..d6c183e210 100644 --- a/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java +++ b/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java @@ -10,24 +10,21 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . * #L% */ -import com.google.protobuf.Descriptors.FieldDescriptor; import org.openbase.bco.authentication.lib.AuthenticatedServiceProcessor; -import org.openbase.bco.authentication.lib.AuthorizationHelper; import org.openbase.bco.authentication.lib.AuthorizationHelper.PermissionType; import org.openbase.bco.registry.lib.com.AbstractRegistryController; -import org.openbase.bco.registry.lib.jp.JPBCODatabaseDirectory; import org.openbase.bco.registry.message.lib.MessageRegistry; import org.openbase.bco.registry.message.lib.generator.UserMessageIdGenerator; import org.openbase.bco.registry.message.lib.jp.JPMessageRegistryScope; @@ -40,16 +37,13 @@ import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.InstantiationException; import org.openbase.jul.exception.NotAvailableException; -import org.openbase.jul.pattern.ListFilter; import org.openbase.jul.schedule.GlobalCachedExecutorService; import org.openbase.jul.storage.registry.ProtoBufFileSynchronizedRegistry; import org.openbase.type.domotic.authentication.AuthenticatedValueType.AuthenticatedValue; -import org.openbase.type.domotic.authentication.UserClientPairType.UserClientPair; import org.openbase.type.domotic.communication.UserMessageType.UserMessage; import org.openbase.type.domotic.registry.MessageRegistryDataType.MessageRegistryData; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.Future; @@ -131,10 +125,7 @@ public void notifyChange() throws CouldNotPerformException, InterruptedException @Override public Future registerUserMessage(final UserMessage userMessage) { - return GlobalCachedExecutorService.submit(() -> { - UserMessage result = userMessageRegistry.register(userMessage); - return result; - }); + return GlobalCachedExecutorService.submit(() -> userMessageRegistry.register(userMessage)); } @Override @@ -226,7 +217,7 @@ public Future removeUserMessage(final UserMessage userMessage) { public Future removeUserMessageAuthenticated(final AuthenticatedValue authenticatedValue) { return GlobalCachedExecutorService.submit(() -> AuthenticatedServiceProcessor.authenticatedAction(authenticatedValue, UserMessage.class, this, (userMessage, authenticationBaseData) -> { - // verify write permissions for the old user message + // verify write permissions for the user message to remove final UserMessage old = userMessageRegistry.getMessage(userMessage.getId()); AuthorizationWithTokenHelper.canDo(authenticationBaseData, old, PermissionType.WRITE, CachedUnitRegistryRemote.getRegistry()); return userMessageRegistry.remove(userMessage); @@ -252,35 +243,35 @@ public Boolean isUserMessageRegistryConsistent() { protected void registerRemoteRegistries() { } - @Override - protected MessageRegistryData filterDataForUser(final MessageRegistryData.Builder dataBuilder, final UserClientPair userClientPair) throws CouldNotPerformException { - // Create a filter which removes all user messages from a list without read permissions to its location by the user - final ListFilter readFilter = userMessage -> { - try { - boolean senderPermission = AuthorizationHelper.canRead(CachedUnitRegistryRemote.getRegistry().getUnitConfigById(userMessage.getSenderId()), userClientPair, CachedUnitRegistryRemote.getRegistry().getAuthorizationGroupUnitConfigRemoteRegistry(true).getEntryMap(), CachedUnitRegistryRemote.getRegistry().getLocationUnitConfigRemoteRegistry(true).getEntryMap()); - boolean receiverPermission = AuthorizationHelper.canRead(CachedUnitRegistryRemote.getRegistry().getUnitConfigById(userMessage.getRecipientId()), userClientPair, CachedUnitRegistryRemote.getRegistry().getAuthorizationGroupUnitConfigRemoteRegistry(true).getEntryMap(), CachedUnitRegistryRemote.getRegistry().getLocationUnitConfigRemoteRegistry(true).getEntryMap()); - return !(senderPermission || receiverPermission); - } catch (CouldNotPerformException e) { - // if id could not resolved, than we filter the element. - return true; - } - }; - // iterate over all fields of unit registry data - for (FieldDescriptor fieldDescriptor : dataBuilder.getAllFields().keySet()) { - // only filter repeated fields - if (!fieldDescriptor.isRepeated()) { - continue; - } - - // only filter fields of type UserMessage - if (!fieldDescriptor.getMessageType().getName().equals(UserMessage.getDescriptor().getName())) { - continue; - } - - // copy list, filter it and set as new list for the field - dataBuilder.setField(fieldDescriptor, readFilter.filter(new ArrayList<>((List) dataBuilder.getField(fieldDescriptor)))); - } - - return dataBuilder.build(); - } +// @Override +// protected MessageRegistryData filterDataForUser(final MessageRegistryData.Builder dataBuilder, final UserClientPair userClientPair) throws CouldNotPerformException { +// // Create a filter which removes all user messages from a list without read permissions to its location by the user +// final ListFilter readFilter = userMessage -> { +// try { +// boolean senderPermission = AuthorizationHelper.canRead(CachedUnitRegistryRemote.getRegistry().getUnitConfigById(userMessage.getSenderId()), userClientPair, CachedUnitRegistryRemote.getRegistry().getAuthorizationGroupUnitConfigRemoteRegistry(true).getEntryMap(), CachedUnitRegistryRemote.getRegistry().getLocationUnitConfigRemoteRegistry(true).getEntryMap()); +// boolean receiverPermission = AuthorizationHelper.canRead(CachedUnitRegistryRemote.getRegistry().getUnitConfigById(userMessage.getRecipientId()), userClientPair, CachedUnitRegistryRemote.getRegistry().getAuthorizationGroupUnitConfigRemoteRegistry(true).getEntryMap(), CachedUnitRegistryRemote.getRegistry().getLocationUnitConfigRemoteRegistry(true).getEntryMap()); +// return !(senderPermission || receiverPermission); +// } catch (CouldNotPerformException e) { +// // if id could not resolved, than we filter the element. +// return true; +// } +// }; +// // iterate over all fields of unit registry data +// for (FieldDescriptor fieldDescriptor : dataBuilder.getAllFields().keySet()) { +// // only filter repeated fields +// if (!fieldDescriptor.isRepeated()) { +// continue; +// } +// +// // only filter fields of type UserMessage +// if (!fieldDescriptor.getMessageType().getName().equals(UserMessage.getDescriptor().getName())) { +// continue; +// } +// +// // copy list, filter it and set as new list for the field +// dataBuilder.setField(fieldDescriptor, readFilter.filter(new ArrayList<>((List) dataBuilder.getField(fieldDescriptor)))); +// } +// +// return dataBuilder.build(); +// } } diff --git a/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemote.java b/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemote.java index cf469f312f..05c3ed056b 100644 --- a/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemote.java +++ b/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemote.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 * . @@ -36,13 +36,17 @@ import org.openbase.jul.exception.InstantiationException; import org.openbase.jul.exception.InvalidStateException; import org.openbase.jul.exception.NotAvailableException; +import org.openbase.jul.exception.printer.ExceptionPrinter; +import org.openbase.jul.extension.type.processing.MultiLanguageTextProcessor; import org.openbase.jul.extension.type.util.TransactionSynchronizationFuture; import org.openbase.jul.storage.registry.RegistryRemote; import org.openbase.type.domotic.authentication.AuthenticatedValueType.AuthenticatedValue; import org.openbase.type.domotic.communication.UserMessageType.UserMessage; import org.openbase.type.domotic.registry.MessageRegistryDataType.MessageRegistryData; +import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.concurrent.Future; /** @@ -88,6 +92,7 @@ public SynchronizedRemoteRegistry getU } return userMessageRemoteRegistry; } + /** * {@inheritDoc} * @@ -124,6 +129,27 @@ public UserMessage getUserMessageById(final String userMessageId) throws NotAvai } } + public List getUserMessagesByText(final String text, final Locale locale) { + try { + validateData(); + return userMessageRemoteRegistry + .getMessages().stream() + .filter(userMessage -> { + try { + return MultiLanguageTextProcessor + .getBestMatch(locale, userMessage.getText()) + .equals(text); + } catch (NotAvailableException ex) { + return false; + } + }) + .toList(); + } catch (CouldNotPerformException ex) { + ExceptionPrinter.printHistory(new NotAvailableException("UserMessages", ex), logger); + return Collections.emptyList(); + } + } + @Override public List getUserMessages() throws CouldNotPerformException { try { @@ -156,7 +182,7 @@ public Boolean containsUserMessageById(final String userMessageId) { @Override public Future updateUserMessage(final UserMessage userMessage) { - return AuthenticatedServiceProcessor.requestAuthenticatedAction(userMessage, UserMessage.class, SessionManager.getInstance(), authenticatedValue -> updateUserMessageAuthenticated(authenticatedValue)); + return AuthenticatedServiceProcessor.requestAuthenticatedAction(userMessage, UserMessage.class, SessionManager.getInstance(), this::updateUserMessageAuthenticated); } @Override @@ -166,7 +192,7 @@ public Future updateUserMessageAuthenticated(final Authentic @Override public Future removeUserMessage(final UserMessage userMessage) { - return AuthenticatedServiceProcessor.requestAuthenticatedAction(userMessage, UserMessage.class, SessionManager.getInstance(), authenticatedValue -> removeUserMessageAuthenticated(authenticatedValue)); + return AuthenticatedServiceProcessor.requestAuthenticatedAction(userMessage, UserMessage.class, SessionManager.getInstance(), this::removeUserMessageAuthenticated); } @Override diff --git a/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemoteAuthExtensions.kt b/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemoteAuthExtensions.kt new file mode 100644 index 0000000000..0b1b8639cf --- /dev/null +++ b/module/registry/message-registry/remote/src/main/java/org/openbase/bco/registry/message/remote/MessageRegistryRemoteAuthExtensions.kt @@ -0,0 +1,39 @@ +package org.openbase.bco.registry.message.remote + +import org.openbase.bco.authentication.lib.SessionManager +import org.openbase.bco.authentication.lib.future.AuthenticatedValueFuture +import org.openbase.type.domotic.authentication.AuthTokenType +import org.openbase.type.domotic.authentication.AuthenticatedValueType +import org.openbase.type.domotic.communication.UserMessageType +import java.io.Serializable +import java.util.concurrent.Future + +fun MessageRegistryRemote.updateUserMessageAuthenticated( + userMessage: UserMessageType.UserMessage?, + auth: AuthTokenType.AuthToken?, +): Future = authRequest(userMessage, ::updateUserMessageAuthenticated, auth) + +fun MessageRegistryRemote.removeUserMessageAuthenticated( + userMessage: UserMessageType.UserMessage?, + auth: AuthTokenType.AuthToken?, +): Future = authRequest(userMessage, ::removeUserMessageAuthenticated, auth) + +fun MessageRegistryRemote.registerUserMessageAuthenticated( + userMessage: UserMessageType.UserMessage?, + auth: AuthTokenType.AuthToken?, +): Future = authRequest(userMessage, ::registerUserMessageAuthenticated, auth) + +inline fun MessageRegistryRemote.authRequest( + value: T?, + origin: (AuthenticatedValueType.AuthenticatedValue) -> Future, + auth: AuthTokenType.AuthToken?, +): Future = with(SessionManager.getInstance()) { + initializeRequest(value, auth).let { value -> + AuthenticatedValueFuture( + origin(value), + T::class.java, + value.ticketAuthenticatorWrapper, + this + ) + } +} diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupClassGroupConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupClassGroupConsistencyHandler.java index 22400d9f0c..a72891e97f 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupClassGroupConsistencyHandler.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupClassGroupConsistencyHandler.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -58,7 +58,8 @@ public class AuthorizationGroupClassGroupConsistencyHandler extends AbstractProt public AuthorizationGroupClassGroupConsistencyHandler( final ProtoBufRegistry userRegistry, final ProtoBufRegistry agentRegistry, - final ProtoBufRegistry appRegistry) { + final ProtoBufRegistry appRegistry + ) { this.userRegistry = userRegistry; typeRegistryMap = new HashMap<>(); typeRegistryMap.put(UnitType.AGENT, agentRegistry); @@ -129,7 +130,7 @@ public void processData(final String id, } } - // remove all users which should not longer be part of the group + // remove all users which should no longer be part of the group for (String memberId : authorizationGroupConfig.getMemberIdList()) { if (!userIdList.contains(memberId)) { modification = true; diff --git a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.java b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.java deleted file mode 100644 index 4a87d2f2db..0000000000 --- a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.java +++ /dev/null @@ -1,347 +0,0 @@ -package org.openbase.bco.registry.unit.lib.auth; - -/*- - * #%L - * BCO Registry Unit Library - * %% - * Copyright (C) 2014 - 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% - */ - -import com.google.protobuf.ProtocolStringList; -import org.openbase.bco.authentication.lib.AuthPair; -import org.openbase.bco.authentication.lib.AuthenticationBaseData; -import org.openbase.bco.authentication.lib.AuthorizationHelper; -import org.openbase.bco.authentication.lib.AuthorizationHelper.PermissionType; -import org.openbase.bco.registry.lib.util.UnitConfigProcessor; -import org.openbase.bco.registry.template.lib.TemplateRegistry; -import org.openbase.bco.registry.template.remote.CachedTemplateRegistryRemote; -import org.openbase.bco.registry.unit.lib.UnitRegistry; -import org.openbase.jul.exception.*; -import org.openbase.jul.exception.MultiException.ExceptionStack; -import org.openbase.type.domotic.authentication.AuthorizationTokenType.AuthorizationToken; -import org.openbase.type.domotic.authentication.AuthorizationTokenType.AuthorizationToken.PermissionRule; -import org.openbase.type.domotic.authentication.PermissionType.Permission; -import org.openbase.type.domotic.authentication.UserClientPairType.UserClientPair; -import org.openbase.type.domotic.communication.UserMessageType.UserMessage; -import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServiceType; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.HashSet; -import java.util.Set; - -/** - * @author Tamino Huxohl - */ -public class AuthorizationWithTokenHelper { - - private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationWithTokenHelper.class); - - /** - * Verify an authorization token. This is done in two steps. - * By checking if all entered ids match either a UnitConfig, ServiceTemplate or UnitTemplate. - * If the id matches a unit config it is checked if the user defined in the token has at least as many permissions - * for the unit as the token would grant. - * - * @param authorizationToken the authorization token that is checked - * @param unitRegistry registry used to resolve authorization groups and locations to check permissions - * - * @throws CouldNotPerformException thrown if the token is invalid - */ - public static void verifyAuthorizationToken(final AuthorizationToken authorizationToken, final UnitRegistry unitRegistry) throws CouldNotPerformException { - try { - for (final PermissionRule permissionRule : authorizationToken.getPermissionRuleList()) { - // make sure the unit with the given id exists - final UnitConfig unitConfig; - try { - unitConfig = unitRegistry.getUnitConfigById(permissionRule.getUnitId()); - - // make sure the unit template with the given id exists - if (permissionRule.hasUnitTemplateId()) { - CachedTemplateRegistryRemote.getRegistry().getUnitTemplateById(permissionRule.getUnitTemplateId()); - } - - // make sure the service template with the given id exists - if (permissionRule.hasServiceTemplateId()) { - CachedTemplateRegistryRemote.getRegistry().getServiceTemplateById(permissionRule.getServiceTemplateId()); - } - } catch (CouldNotPerformException ex) { - throw new RejectedException("Invalid unit id, service template id or unit template id", ex); - } - - // a filter reduces permissions so it can be granted anyways - if (permissionRule.getFilter()) { - continue; - } - - // evaluate the permissions the given user has for the unit defined in the token - final Permission permission = AuthorizationHelper.getPermission(unitConfig, authorizationToken.getUserId(), unitRegistry.getAuthorizationGroupMap(), unitRegistry.getLocationMap()); - - // reject the token if the user tries to give more permissions than he has - if (!AuthorizationHelper.isSubPermission(permission, permissionRule.getPermission())) { - throw new RejectedException("User[" + authorizationToken.getUserId() + "] has not enough permissions to create an authorizationToken with permissions[" + permissionRule.getPermission() + "] for unit[" + UnitConfigProcessor.getDefaultAlias(unitConfig, "?") + "]"); - } - } - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could verify access token", ex); - } - } - - /** - * Perform a permission check by validating that the actor is either the receiver or the sender of the message. - * - * @param authenticationBaseData the authentication data including who is authenticated and tokens used for authorization - * @param userMessage the user message for which permissions are checked - * @param permissionType the permission type which is checked - * @param unitRegistry unit registry used to resolve ids - * - * @return a string representing the authorized user, this is either just the username of the authenticated user - * or the username of the authenticated user followed by the username of the issuer of the authorization token - * - * @throws CouldNotPerformException thrown if the user does not have permissions or if the check fails - */ - public static AuthPair canDo( - final AuthenticationBaseData authenticationBaseData, - final UserMessage userMessage, - final PermissionType permissionType, - final UnitRegistry unitRegistry) throws CouldNotPerformException { - - try { - // validate sender - return canDo(authenticationBaseData, unitRegistry.getUnitConfigById(userMessage.getSenderId()), permissionType, unitRegistry, null, null); - } catch (CouldNotPerformException ex) { - ExceptionStack exceptionStack = null; - exceptionStack = MultiException.push(AuthorizationWithTokenHelper.class, ex, exceptionStack); - - // validate receiver if sender validation failed. - try { - return canDo(authenticationBaseData, unitRegistry.getUnitConfigById(userMessage.getRecipientId()), permissionType, unitRegistry, null, null); - } catch (CouldNotPerformException exx) { - exceptionStack = MultiException.push(AuthorizationWithTokenHelper.class, exx, exceptionStack); - MultiException.checkAndThrow(() -> "Permission denied!", exceptionStack); - } - } - throw new FatalImplementationErrorException("ExceptionStack empty in error case.", AuthorizationWithTokenHelper.class); - } - - /** - * Perform a permission check according to {@link #canDo(AuthenticationBaseData, UnitConfig, PermissionType, UnitRegistry, UnitType, ServiceType)} - * by ignoring unit type and service type permissions. - * - * @param authenticationBaseData the authentication data including who is authenticated and tokens used for authorization - * @param unitConfig the unit config for which permissions are checked - * @param permissionType the permission type which is checked - * @param unitRegistry unit registry used to resolve ids - * - * @return a string representing the authorized user, this is either just the username of the authenticated user - * or the username of the authenticated user followed by the username of the issuer of the authorization token - * - * @throws CouldNotPerformException thrown if the user does not have permissions or if the check fails - */ - public static AuthPair canDo( - final AuthenticationBaseData authenticationBaseData, - final UnitConfig unitConfig, - final PermissionType permissionType, - final UnitRegistry unitRegistry) throws CouldNotPerformException { - return canDo(authenticationBaseData, unitConfig, permissionType, unitRegistry, null, null); - } - - - /** - * Perform a permission check for authentication data including tokens. - * - * @param authenticationBaseData the authentication data including who is authenticated and tokens used for authorization - * @param unitConfig the unit config for which permissions are checked - * @param permissionType the permission type which is checked - * @param unitRegistry unit registry used to resolve ids - * @param unitType the unit type for which is checked if the authorization tokens gives permissions for it, if it is null - * it will be ignored - * @param serviceType the service type for which is checked if the authorization tokens gives permissions for it, if it is null - * it will be ignored - * - * @return a string representing the authorized user, this is either just the username of the authenticated user - * or the username of the authenticated user followed by the username of the issuer of the authorization token - * - * @throws CouldNotPerformException thrown if the user does not have permissions or if the check fails - */ - public static AuthPair canDo( - final AuthenticationBaseData authenticationBaseData, - final UnitConfig unitConfig, - final PermissionType permissionType, - final UnitRegistry unitRegistry, - final UnitType unitType, - final ServiceType serviceType) throws CouldNotPerformException { - try { - // resolve the responsible user - UserClientPair userClientPair; - if (authenticationBaseData == null) { - // authentication data is not given so use null to check for other permissions - userClientPair = UserClientPair.getDefaultInstance(); - } else { - if (authenticationBaseData.getAuthenticationToken() != null) { - // authentication token is set so use it as the responsible user - userClientPair = UserClientPair.newBuilder().setUserId(authenticationBaseData.getAuthenticationToken().getUserId()).build(); - } else { - // use the user that is authenticated for the request - userClientPair = authenticationBaseData.getUserClientPair(); - } - } - - // check if authenticated user has needed permissions - if (AuthorizationHelper.canDo(unitConfig, userClientPair.getUserId(), unitRegistry.getAuthorizationGroupMap(), unitRegistry.getLocationMap(), permissionType)) { - return new AuthPair(userClientPair, userClientPair.getUserId()); - } - if (AuthorizationHelper.canDo(unitConfig, userClientPair.getClientId(), unitRegistry.getAuthorizationGroupMap(), unitRegistry.getLocationMap(), permissionType)) { - return new AuthPair(userClientPair, userClientPair.getClientId()); - } - - try { - // test if user is part of the admin group - final ProtocolStringList memberIdList = unitRegistry.getUnitConfigByAlias(UnitRegistry.ADMIN_GROUP_ALIAS).getAuthorizationGroupConfig().getMemberIdList(); - if (memberIdList.contains(userClientPair.getUserId())) { - return new AuthPair(userClientPair, userClientPair.getUserId()); - } - if (memberIdList.contains(userClientPair.getClientId())) { - return new AuthPair(userClientPair, userClientPair.getClientId()); - } - } catch (NotAvailableException ex) { - // continue with the checks, admin group is not available - } - - // authenticated user does not have permissions so check if the authorization token grants them - if (authenticationBaseData != null && authenticationBaseData.getAuthorizationToken() != null) { - final AuthorizationToken authorizationToken = authenticationBaseData.getAuthorizationToken(); - // verify that the authorization token is valid - verifyAuthorizationToken(authorizationToken, unitRegistry); - - // verify if the token grants the necessary permissions - return authorizedByToken(authorizationToken, userClientPair, unitConfig, permissionType, unitRegistry, unitType, serviceType); - } - String userRepresentation = userClientPair.getUserId(); - if (!userRepresentation.isEmpty()) { - userRepresentation += "@"; - } - userRepresentation += userClientPair.getClientId(); - if (userRepresentation.isEmpty()) { - userRepresentation = "Other"; - } - throw new PermissionDeniedException("User[" + userRepresentation + "] " + permissionType.name().toLowerCase() + " permission denied!"); - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not verify permissions for unit[" + UnitConfigProcessor.getDefaultAlias(unitConfig, "?") + "]", ex); - } - } - - private static AuthPair authorizedByToken( - final AuthorizationToken authorizationToken, - final UserClientPair userClientPair, - final UnitConfig unitConfig, - final PermissionType permissionType, - final UnitRegistry unitRegistry, - final UnitType unitType, - final ServiceType serviceType) throws CouldNotPerformException { - // verify if the token grants the necessary permissions - final TemplateRegistry templateRegistry = CachedTemplateRegistryRemote.getRegistry(); - final Set grantingPermissionSet = new HashSet<>(); - final Set filteringPermissionSet = new HashSet<>(); - for (final PermissionRule permissionRule : authorizationToken.getPermissionRuleList()) { - if (permissionRule.getFilter()) { - filteringPermissionSet.add(permissionRule); - } else { - grantingPermissionSet.add(permissionRule); - } - } - - boolean granted = false; - for (final PermissionRule permissionRule : grantingPermissionSet) { - if (permitted(unitConfig, permissionRule, unitRegistry, templateRegistry, serviceType, unitType, permissionType)) { - granted = true; - break; - } - } - - if (!granted) { - throw new PermissionDeniedException("Authorization token does not grant the necessary permissions"); - } - - for (final PermissionRule permissionRule : filteringPermissionSet) { - if (permitted(unitConfig, permissionRule, unitRegistry, templateRegistry, serviceType, unitType, permissionType)) { - throw new PermissionDeniedException("Authorization token does not grant the necessary permissions"); - } - } - - // build the auth pair - return new AuthPair(userClientPair, authorizationToken.getUserId()); - } - - private static boolean permitted(final UnitConfig unitConfig, - final PermissionRule permissionRule, - final UnitRegistry unitRegistry, - final TemplateRegistry templateRegistry, - final ServiceType serviceType, - final UnitType unitType, - final PermissionType permissionType) throws CouldNotPerformException { - // the permission would not grant these permissions anyway so return false - // this is done first so that rejections are handled fast - if (!AuthorizationHelper.permitted(permissionRule.getPermission(), permissionType)) { - return false; - } - // test if the unit id in the permission rule matches the unit config - if (!permissionRule.getUnitId().equals(unitConfig.getId())) { - // it does not so test if the unit id belongs to a location - final UnitConfig locationToCheck = unitRegistry.getUnitConfigById(permissionRule.getUnitId()); - // if it is not a location then the permission rule do not permit what is asked - if (locationToCheck.getUnitType() != UnitType.LOCATION) { - return false; - } - // if the location does not contain the given unit config the permission rule does not permit it - if (!containsUnit(unitConfig, locationToCheck, unitRegistry)) { - return false; - } - } - // if the given service type is defined and the rule contains a service type which does not match return false - if (serviceType != null - && serviceType != ServiceType.UNKNOWN - && permissionRule.hasServiceTemplateId() - && templateRegistry.getServiceTemplateById(permissionRule.getServiceTemplateId()).getServiceType() != serviceType) { - return false; - } - // if the given unit type is defined and the rule contains a unit type which does not match return false - return unitType == null - || unitType == UnitType.UNKNOWN - || !permissionRule.hasUnitTemplateId() - || templateRegistry.getUnitTemplateById(permissionRule.getUnitTemplateId()).getUnitType() == unitType; - } - - private static boolean containsUnit(final UnitConfig unitConfig, final UnitConfig locationToCheck, final UnitRegistry unitRegistry) throws CouldNotPerformException { - UnitConfig location = unitRegistry.getUnitConfigById(unitConfig.getPlacementConfig().getLocationId()); - if (location.getId().equals(locationToCheck.getId())) { - return true; - } - - while (!location.getLocationConfig().getRoot()) { - location = unitRegistry.getUnitConfigById(location.getPlacementConfig().getLocationId()); - if (location.getId().equals(locationToCheck.getId())) { - return true; - } - } - - return false; - } -} diff --git a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt new file mode 100644 index 0000000000..6cb5cc903e --- /dev/null +++ b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt @@ -0,0 +1,421 @@ +package org.openbase.bco.registry.unit.lib.auth + +import org.openbase.bco.authentication.lib.AuthPair +import org.openbase.bco.authentication.lib.AuthenticationBaseData +import org.openbase.bco.authentication.lib.AuthorizationHelper +import org.openbase.bco.registry.lib.util.UnitConfigProcessor +import org.openbase.bco.registry.template.lib.TemplateRegistry +import org.openbase.bco.registry.template.remote.CachedTemplateRegistryRemote +import org.openbase.bco.registry.unit.lib.UnitRegistry +import org.openbase.jul.exception.* +import org.openbase.jul.exception.MultiException.ExceptionStack +import org.openbase.type.domotic.authentication.AuthorizationTokenType +import org.openbase.type.domotic.authentication.UserClientPairType +import org.openbase.type.domotic.communication.UserMessageType +import org.openbase.type.domotic.service.ServiceTemplateType +import org.openbase.type.domotic.unit.UnitConfigType +import org.openbase.type.domotic.unit.UnitTemplateType +import org.slf4j.LoggerFactory +import java.util.* + +/*- +* #%L +* BCO Registry Unit Library +* %% +* Copyright (C) 2014 - 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% +*/ /** + * @author [Tamino Huxohl](mailto:pleminoq@openbase.org) + */ +object AuthorizationWithTokenHelper { + private val LOGGER = LoggerFactory.getLogger(AuthorizationWithTokenHelper::class.java) + + /** + * Verify an authorization token. This is done in two steps. + * By checking if all entered ids match either a UnitConfig, ServiceTemplate or UnitTemplate. + * If the id matches a unit config it is checked if the user defined in the token has at least as many permissions + * for the unit as the token would grant. + * + * @param authorizationToken the authorization token that is checked + * @param unitRegistry registry used to resolve authorization groups and locations to check permissions + * + * @throws CouldNotPerformException thrown if the token is invalid + */ + @JvmStatic + @Throws(CouldNotPerformException::class) + fun verifyAuthorizationToken( + authorizationToken: AuthorizationTokenType.AuthorizationToken, + unitRegistry: UnitRegistry, + ) { + try { + for (permissionRule in authorizationToken.permissionRuleList) { + // make sure the unit with the given id exists + val unitConfig: UnitConfigType.UnitConfig + try { + unitConfig = unitRegistry.getUnitConfigById(permissionRule.getUnitId()) + + // make sure the unit template with the given id exists + if (permissionRule.hasUnitTemplateId()) { + CachedTemplateRegistryRemote.getRegistry() + .getUnitTemplateById(permissionRule.getUnitTemplateId()) + } + + // make sure the service template with the given id exists + if (permissionRule.hasServiceTemplateId()) { + CachedTemplateRegistryRemote.getRegistry() + .getServiceTemplateById(permissionRule.getServiceTemplateId()) + } + } catch (ex: CouldNotPerformException) { + throw RejectedException("Invalid unit id, service template id or unit template id", ex) + } + + // a filter reduces permissions so it can be granted anyways + if (permissionRule.filter) { + continue + } + + // evaluate the permissions the given user has for the unit defined in the token + val permission = AuthorizationHelper.getPermission( + unitConfig, + authorizationToken.getUserId(), + unitRegistry.getAuthorizationGroupMap(), + unitRegistry.getLocationMap() + ) + + // reject the token if the user tries to give more permissions than he has + if (!AuthorizationHelper.isSubPermission(permission, permissionRule.permission)) { + throw RejectedException( + "User[" + authorizationToken.getUserId() + "] has not enough permissions to create an authorizationToken with permissions[" + permissionRule.permission + "] for unit[" + UnitConfigProcessor.getDefaultAlias( + unitConfig, + "?" + ) + "]" + ) + } + } + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException("Could verify access token", ex) + } + } + + /** + * Perform a permission check by validating that the actor is either the receiver or the sender of the message. + * + * @param authenticationBaseData the authentication data including who is authenticated and tokens used for authorization + * @param userMessage the user message for which permissions are checked + * @param permissionType the permission type which is checked + * @param unitRegistry unit registry used to resolve ids + * + * @return a string representing the authorized user, this is either just the username of the authenticated user + * or the username of the authenticated user followed by the username of the issuer of the authorization token + * + * @throws CouldNotPerformException thrown if the user does not have permissions or if the check fails + */ + @JvmStatic + @Throws(CouldNotPerformException::class) + fun canDo( + authenticationBaseData: AuthenticationBaseData?, + userMessage: UserMessageType.UserMessage, + permissionType: AuthorizationHelper.PermissionType, + unitRegistry: UnitRegistry, + ): AuthPair { + try { + // validate sender + canDo( + authenticationBaseData, + unitRegistry.getUnitConfigById(userMessage.getSenderId()), + permissionType, + unitRegistry, + null, + null + ) + } catch (ex: CouldNotPerformException) { + var exceptionStack: ExceptionStack? = null + exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, ex, exceptionStack) + + // validate receiver if sender validation failed. + try { + canDo( + authenticationBaseData, + unitRegistry.getUnitConfigById(userMessage.getRecipientId()), + permissionType, + unitRegistry, + null, + null + ) + } catch (exx: CouldNotPerformException) { + exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) + + userMessage.conditionList.forEach { condition -> + try { + return canDo( + authenticationBaseData, + unitRegistry.getUnitConfigById(condition.unitId), + permissionType, + unitRegistry, + null, + null + ) + } catch (exxx: CouldNotPerformException) { + exceptionStack = + MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) + } + } + + MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) + } + } + throw FatalImplementationErrorException( + "ExceptionStack empty in error case.", + AuthorizationWithTokenHelper::class.java + ) + } + + /** + * Perform a permission check for authentication data including tokens. + * + * @param authenticationBaseData the authentication data including who is authenticated and tokens used for authorization + * @param unitConfig the unit config for which permissions are checked + * @param permissionType the permission type which is checked + * @param unitRegistry unit registry used to resolve ids + * @param unitType the unit type for which is checked if the authorization tokens gives permissions for it, if it is null + * it will be ignored + * @param serviceType the service type for which is checked if the authorization tokens gives permissions for it, if it is null + * it will be ignored + * + * @return a string representing the authorized user, this is either just the username of the authenticated user + * or the username of the authenticated user followed by the username of the issuer of the authorization token + * + * @throws CouldNotPerformException thrown if the user does not have permissions or if the check fails + */ + @JvmStatic + @JvmOverloads + @Throws(CouldNotPerformException::class) + fun canDo( + authenticationBaseData: AuthenticationBaseData?, + unitConfig: UnitConfigType.UnitConfig, + permissionType: AuthorizationHelper.PermissionType, + unitRegistry: UnitRegistry, + unitType: UnitTemplateType.UnitTemplate.UnitType? = null, + serviceType: ServiceTemplateType.ServiceTemplate.ServiceType? = null, + ): AuthPair { + try { + // resolve the responsible user + val userClientPair: UserClientPairType.UserClientPair + userClientPair = if (authenticationBaseData == null) { + // authentication data is not given so use null to check for other permissions + UserClientPairType.UserClientPair.getDefaultInstance() + } else { + if (authenticationBaseData.authenticationToken != null) { + // authentication token is set so use it as the responsible user + UserClientPairType.UserClientPair.newBuilder() + .setUserId(authenticationBaseData.authenticationToken.getUserId()).build() + } else { + // use the user that is authenticated for the request + authenticationBaseData.userClientPair + } + } + + // check if authenticated user has needed permissions + if (AuthorizationHelper.canDo( + unitConfig, + userClientPair.getUserId(), + unitRegistry.getAuthorizationGroupMap(), + unitRegistry.getLocationMap(), + permissionType + ) + ) { + return AuthPair(userClientPair, userClientPair.getUserId()) + } + if (AuthorizationHelper.canDo( + unitConfig, + userClientPair.getClientId(), + unitRegistry.getAuthorizationGroupMap(), + unitRegistry.getLocationMap(), + permissionType + ) + ) { + return AuthPair(userClientPair, userClientPair.getClientId()) + } + try { + // test if user is part of the admin group + val memberIdList = + unitRegistry.getUnitConfigByAlias(UnitRegistry.ADMIN_GROUP_ALIAS).authorizationGroupConfig.memberIdList + if (memberIdList.contains(userClientPair.getUserId())) { + return AuthPair(userClientPair, userClientPair.getUserId()) + } + if (memberIdList.contains(userClientPair.getClientId())) { + return AuthPair(userClientPair, userClientPair.getClientId()) + } + } catch (ex: NotAvailableException) { + // continue with the checks, admin group is not available + } + + // authenticated user does not have permissions so check if the authorization token grants them + if (authenticationBaseData != null && authenticationBaseData.authorizationToken != null) { + val authorizationToken = authenticationBaseData.authorizationToken + // verify that the authorization token is valid + verifyAuthorizationToken(authorizationToken, unitRegistry) + + // verify if the token grants the necessary permissions + return authorizedByToken( + authorizationToken, + userClientPair, + unitConfig, + permissionType, + unitRegistry, + unitType, + serviceType + ) + } + var userRepresentation = userClientPair.getUserId() + if (!userRepresentation.isEmpty()) { + userRepresentation += "@" + } + userRepresentation += userClientPair.getClientId() + if (userRepresentation.isEmpty()) { + userRepresentation = "Other" + } + throw PermissionDeniedException("User[" + userRepresentation + "] " + permissionType.name.lowercase(Locale.getDefault()) + " permission denied!") + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException( + "Could not verify permissions for unit[" + UnitConfigProcessor.getDefaultAlias( + unitConfig, + "?" + ) + "]", ex + ) + } + } + + @Throws(CouldNotPerformException::class) + private fun authorizedByToken( + authorizationToken: AuthorizationTokenType.AuthorizationToken, + userClientPair: UserClientPairType.UserClientPair, + unitConfig: UnitConfigType.UnitConfig, + permissionType: AuthorizationHelper.PermissionType, + unitRegistry: UnitRegistry, + unitType: UnitTemplateType.UnitTemplate.UnitType?, + serviceType: ServiceTemplateType.ServiceTemplate.ServiceType?, + ): AuthPair { + // verify if the token grants the necessary permissions + val templateRegistry: TemplateRegistry = CachedTemplateRegistryRemote.getRegistry() + val grantingPermissionSet: MutableSet = HashSet() + val filteringPermissionSet: MutableSet = HashSet() + for (permissionRule in authorizationToken.permissionRuleList) { + if (permissionRule.filter) { + filteringPermissionSet.add(permissionRule) + } else { + grantingPermissionSet.add(permissionRule) + } + } + var granted = false + for (permissionRule in grantingPermissionSet) { + if (permitted( + unitConfig, + permissionRule, + unitRegistry, + templateRegistry, + serviceType, + unitType, + permissionType + ) + ) { + granted = true + break + } + } + if (!granted) { + throw PermissionDeniedException("Authorization token does not grant the necessary permissions") + } + for (permissionRule in filteringPermissionSet) { + if (permitted( + unitConfig, + permissionRule, + unitRegistry, + templateRegistry, + serviceType, + unitType, + permissionType + ) + ) { + throw PermissionDeniedException("Authorization token does not grant the necessary permissions") + } + } + + // build the auth pair + return AuthPair(userClientPair, authorizationToken.getUserId()) + } + + @Throws(CouldNotPerformException::class) + private fun permitted( + unitConfig: UnitConfigType.UnitConfig, + permissionRule: AuthorizationTokenType.AuthorizationToken.PermissionRule, + unitRegistry: UnitRegistry, + templateRegistry: TemplateRegistry, + serviceType: ServiceTemplateType.ServiceTemplate.ServiceType?, + unitType: UnitTemplateType.UnitTemplate.UnitType?, + permissionType: AuthorizationHelper.PermissionType, + ): Boolean { + // the permission would not grant these permissions anyway so return false + // this is done first so that rejections are handled fast + if (!AuthorizationHelper.permitted(permissionRule.permission, permissionType)) { + return false + } + // test if the unit id in the permission rule matches the unit config + if (permissionRule.getUnitId() != unitConfig.getId()) { + // it does not so test if the unit id belongs to a location + val locationToCheck = unitRegistry.getUnitConfigById(permissionRule.getUnitId()) + // if it is not a location then the permission rule do not permit what is asked + if (locationToCheck.getUnitType() != UnitTemplateType.UnitTemplate.UnitType.LOCATION) { + return false + } + // if the location does not contain the given unit config the permission rule does not permit it + if (!containsUnit(unitConfig, locationToCheck, unitRegistry)) { + return false + } + } + // if the given service type is defined and the rule contains a service type which does not match return false + return if (serviceType != null && serviceType != ServiceTemplateType.ServiceTemplate.ServiceType.UNKNOWN && permissionRule.hasServiceTemplateId() && templateRegistry.getServiceTemplateById( + permissionRule.getServiceTemplateId() + ) + .getServiceType() != serviceType + ) { + false + } else unitType == null || unitType == UnitTemplateType.UnitTemplate.UnitType.UNKNOWN || !permissionRule.hasUnitTemplateId() || templateRegistry.getUnitTemplateById( + permissionRule.getUnitTemplateId() + ).getUnitType() == unitType + // if the given unit type is defined and the rule contains a unit type which does not match return false + } + + @Throws(CouldNotPerformException::class) + private fun containsUnit( + unitConfig: UnitConfigType.UnitConfig, + locationToCheck: UnitConfigType.UnitConfig, + unitRegistry: UnitRegistry, + ): Boolean { + var location = unitRegistry.getUnitConfigById(unitConfig.placementConfig.getLocationId()) + if (location.getId() == locationToCheck.getId()) { + return true + } + while (!location.locationConfig.root) { + location = unitRegistry.getUnitConfigById(location.placementConfig.getLocationId()) + if (location.getId() == locationToCheck.getId()) { + return true + } + } + return false + } +} diff --git a/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java b/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java index 4f007b80b3..69a94d2486 100644 --- a/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java +++ b/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -31,12 +31,12 @@ import org.openbase.bco.registry.activity.core.ActivityRegistryLauncher; import org.openbase.bco.registry.clazz.core.ClassRegistryLauncher; import org.openbase.bco.registry.lib.jp.JPBCODatabaseDirectory; +import org.openbase.bco.registry.message.core.MessageRegistryLauncher; import org.openbase.bco.registry.remote.Registries; import org.openbase.bco.registry.template.core.TemplateRegistryLauncher; import org.openbase.bco.registry.unit.core.UnitRegistryLauncher; import org.openbase.jps.core.JPService; import org.openbase.jps.exception.JPServiceException; -import org.openbase.jps.preset.JPTestMode; import org.openbase.jps.preset.JPTmpDirectory; import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.FatalImplementationErrorException; @@ -45,7 +45,6 @@ import org.openbase.jul.exception.printer.ExceptionPrinter; import org.openbase.jul.exception.printer.LogLevel; import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.extension.protobuf.ProtoBufBuilderProcessor; import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap; import org.openbase.jul.extension.type.processing.LabelProcessor; import org.openbase.jul.processing.StringProcessor; @@ -174,6 +173,7 @@ public class MockRegistry { public static final String USER_NAME = "uSeRnAmE"; public static final String USER_FIRST_NAME = "Max"; public static final String USER_LAST_NAME = "Mustermann"; + public static final Map APP_CLASS_LABEL_ID_MAP = new HashMap<>(); public static final Map AGENT_CLASS_LABEL_ID_MAP = new HashMap<>(); public static final AxisAlignedBoundingBox3DFloat DEFAULT_BOUNDING_BOX = AxisAlignedBoundingBox3DFloat.newBuilder() .setHeight(10) @@ -191,6 +191,7 @@ public class MockRegistry { private static ClassRegistryLauncher classRegistryLauncher; private static TemplateRegistryLauncher templateRegistryLauncher; private static UnitRegistryLauncher unitRegistryLauncher; + private static MessageRegistryLauncher messageRegistryLauncher; protected MockRegistry() throws InstantiationException { try { @@ -263,6 +264,15 @@ protected MockRegistry() throws InstantiationException { } return null; })); + registryStartupTasks.add(GlobalCachedExecutorService.submit(() -> { + try { + messageRegistryLauncher = new MessageRegistryLauncher(); + messageRegistryLauncher.launch().get(); + } catch (CouldNotPerformException ex) { + throw ExceptionPrinter.printHistoryAndReturnThrowable(ex, LOGGER, LogLevel.ERROR); + } + return null; + })); LOGGER.debug("Starting all registries: unit, class, template, activity..."); for (Future task : registryStartupTasks) { while (true) { @@ -283,7 +293,7 @@ protected MockRegistry() throws InstantiationException { Registries.waitForData(); - if(loadTestData) { + if (loadTestData) { registryStartupTasks.add(GlobalCachedExecutorService.submit(() -> { LOGGER.debug("Update serviceTemplates..."); for (MockServiceTemplate mockServiceTemplate : MockServiceTemplate.values()) { @@ -473,6 +483,14 @@ public static UnitConfig.Builder generateAgentConfig(final String agentClassLabe return agentUnitConfig; } + public static UnitConfig.Builder generateAppConfig(final String alias, final String locationAlias) throws CouldNotPerformException { + final UnitConfig.Builder appUnitConfig = UnitConfig.newBuilder().setUnitType(UnitType.APP); + appUnitConfig.getPlacementConfigBuilder().setLocationId(Registries.getUnitRegistry().getUnitConfigByAlias(locationAlias).getId()); + LabelProcessor.addLabel(appUnitConfig.getLabelBuilder(), Locale.ENGLISH, alias); + appUnitConfig.addAlias(alias); + return appUnitConfig; + } + protected void shutdown() { if (unitRegistryLauncher != null) { unitRegistryLauncher.shutdown(); From 61a8f45be41b8885658184373b3f0c64727b80d9 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 26 Dec 2023 17:31:13 +0100 Subject: [PATCH 46/70] fix compile issue --- .../HowToObserveMotionStatesOfAllRooms.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/module/dal/example/src/main/java/org/openbase/bco/dal/example/HowToObserveMotionStatesOfAllRooms.java b/module/dal/example/src/main/java/org/openbase/bco/dal/example/HowToObserveMotionStatesOfAllRooms.java index c0c4fabb29..98b932558f 100644 --- a/module/dal/example/src/main/java/org/openbase/bco/dal/example/HowToObserveMotionStatesOfAllRooms.java +++ b/module/dal/example/src/main/java/org/openbase/bco/dal/example/HowToObserveMotionStatesOfAllRooms.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 * . @@ -48,10 +48,9 @@ /** - * * This howto demonstrates how all rooms of your smart environment can be observed regarding their current motion state. * BCO offers two different approaches that are both addressed within this howto by EXAMPLE 1 and EXAMPLE 2 - * + *

* Note: You can use the PRESENCE_STATE service as well to rely not only on motion but also on other sensor events to detect a human presence at locations. * Note: This howto requires a running bco platform provided by your network. * @@ -70,17 +69,17 @@ public static void howto() throws InterruptedException { LOGGER.info("authenticate current session..."); BCOLogin.getSession().loginUserViaUsername("admin", "admin", false); - // EXAMPLE 1: observe the movement in all rooms via a custom unit pool. + // EXAMPLE 1: observe the movement in all rooms via a custom unit pool. // create a new unit pool which is mainly a collection of unit remotes - final CustomUnitPool locationPool = new CustomUnitPool(); + final CustomUnitPool locationPool = new CustomUnitPool>(); // make sure the pool only contains tile locations (rooms are represented as tiles in bco). // so we want to filter all non locations and non tiles. locationPool.init( unitConfig -> unitConfig.getUnitType() == UnitType.LOCATION, unitConfig -> unitConfig.getLocationConfig().getLocationType() == LocationType.TILE - ); + ); // activate the pool so units get synchronized... locationPool.activate(); @@ -89,7 +88,7 @@ public static void howto() throws InterruptedException { locationPool.addServiceStateObserver((source, data) -> { // filter non motion state events - if(source.getServiceType() != ServiceType.MOTION_STATE_SERVICE) { + if (source.getServiceType() != ServiceType.MOTION_STATE_SERVICE) { return; } @@ -98,7 +97,7 @@ public static void howto() throws InterruptedException { final LocationRemote locationRemote = (LocationRemote) source.getServiceProvider(); // inform about the update. The location unit is delivered via the service provider method. - LOGGER.info("EXAMPLE 1: "+LabelProcessor.getBestMatch(locationRemote.getConfig().getLabel(), "?") + " has changed its motion state to " + motionState.getValue().name()); + LOGGER.info("EXAMPLE 1: " + LabelProcessor.getBestMatch(locationRemote.getConfig().getLabel(), "?") + " has changed its motion state to " + motionState.getValue().name()); }); // print a summary about the current movement state @@ -108,10 +107,10 @@ public static void howto() throws InterruptedException { // we need to wait for the remote synchronisation when accessing any unit state at the first time location.waitForData(); - LOGGER.info("EXAMPLE 1: "+location.getLabel("?") + " has currently "+ location.getMotionState().getValue().name()); + LOGGER.info("EXAMPLE 1: " + location.getLabel("?") + " has currently " + location.getMotionState().getValue().name()); } - // EXAMPLE 2: observe the movement in all rooms via location remotes + // EXAMPLE 2: observe the movement in all rooms via location remotes // query all tiles via the registry (rooms are represented as tiles in bco). final List locationConfigs = Registries.getUnitRegistry().getLocationUnitConfigsByLocationType(LocationType.TILE); @@ -127,7 +126,7 @@ public static void howto() throws InterruptedException { location.addServiceStateObserver(ServiceTempus.CURRENT, ServiceType.MOTION_STATE_SERVICE, (source, data) -> { // we know its a motion state final MotionState motionState = (MotionState) data; - LOGGER.info("EXAMPLE 2: "+location.getLabel("?") + " has changed its motion state to " + motionState.getValue().name()); + LOGGER.info("EXAMPLE 2: " + location.getLabel("?") + " has changed its motion state to " + motionState.getValue().name()); }); } @@ -135,7 +134,7 @@ public static void howto() throws InterruptedException { for (LocationRemote location : locations) { // we need to wait for the remote synchronisation when accessing any unit state at the first time location.waitForData(); - LOGGER.info("EXAMPLE 2: "+location.getLabel("?") + " has currently "+ location.getMotionState().getValue().name()); + LOGGER.info("EXAMPLE 2: " + location.getLabel("?") + " has currently " + location.getMotionState().getValue().name()); } LOGGER.info("Observe changes for 2 minutes"); From 18b779880f55527c6b36aaa9ae70065e4b97e1ee Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 27 Dec 2023 12:14:27 +0100 Subject: [PATCH 47/70] fix unit pool test --- .../bco/dal/remote/layer/unit/CustomUnitPoolTest.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt b/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt index 521d47975e..c9ec97278e 100644 --- a/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt +++ b/module/dal/test/src/test/java/org/openbase/bco/dal/remote/layer/unit/CustomUnitPoolTest.kt @@ -3,6 +3,7 @@ package org.openbase.bco.dal.remote.layer.unit import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout +import org.openbase.bco.dal.lib.layer.unit.UnitRemote import org.openbase.bco.dal.test.AbstractBCODeviceManagerTest import org.openbase.bco.registry.remote.Registries import org.openbase.jul.pattern.Filter @@ -41,12 +42,12 @@ class CustomUnitPoolTest : AbstractBCODeviceManagerTest() { @Timeout(10) @Throws(Exception::class) fun testUnitPool() { - val customUnitPool: CustomUnitPool<*, *> = CustomUnitPool() + val customUnitPool: CustomUnitPool<*, *> = CustomUnitPool>() Assertions.assertEquals(false, customUnitPool.isActive(), "pool is active while never activated") customUnitPool.activate() customUnitPool.init( - Filter { unitConfig: Any -> unitConfig.hasId() }, - Filter { unitConfig: Any -> unitConfig.getUnitType() === UnitTemplateType.UnitTemplate.UnitType.BUTTON }) + Filter { unitConfig: UnitConfig -> unitConfig.hasId() }, + Filter { unitConfig: UnitConfig -> unitConfig.unitType === UnitTemplateType.UnitTemplate.UnitType.BUTTON }) customUnitPool.activate() for (unitRemote in customUnitPool.internalUnitList) { Assertions.assertEquals( From 7a6dc94ba42a4168c042166c50dd57976f22b2a4 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 27 Dec 2023 14:31:24 +0100 Subject: [PATCH 48/70] fix mock registry shutdown --- .../java/org/openbase/bco/registry/mock/MockRegistry.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java b/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java index 69a94d2486..b8f7d2fded 100644 --- a/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java +++ b/module/registry/util/src/main/java/org/openbase/bco/registry/mock/MockRegistry.java @@ -492,6 +492,10 @@ public static UnitConfig.Builder generateAppConfig(final String alias, final Str } protected void shutdown() { + if (messageRegistryLauncher != null) { + messageRegistryLauncher.shutdown(); + } + if (unitRegistryLauncher != null) { unitRegistryLauncher.shutdown(); } From 511ea42157d739e9491991e642343c348aa1bdc6 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Wed, 27 Dec 2023 14:47:25 +0100 Subject: [PATCH 49/70] cleanup logs --- .../org/openbase/bco/app/preset/DeviceNotificationApp.kt | 1 - .../org/openbase/bco/dal/control/message/MessageManager.kt | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt index 7cdb269a5d..802f6098d2 100644 --- a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt +++ b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt @@ -67,7 +67,6 @@ class DeviceNotificationApp : AbstractAppController() { fun checkDevices() { try { - logger.error("#@# check device states") batteryPool.internalUnitList .onEach { remote -> remote.waitForData() } .filter { remote -> diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt index 91f90d6ac7..58e4421976 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt @@ -29,10 +29,6 @@ class MessageManager : Launchable, VoidInitializable { private var active = false - init { - logger.error("#@# start message manager") - } - private val removeOutdatedMessagesTask: RecurrenceEventFilter = object : RecurrenceEventFilter(maxUpdateInterval.toMillis()) { override fun relay() { @@ -41,7 +37,7 @@ class MessageManager : Launchable, VoidInitializable { } fun removeOutdatedMessages() { - logger.error("#@# removeOutdatedMessages") + logger.trace("removeOutdatedMessages") Registries.getMessageRegistry().userMessages .filterNot { message -> message.conditionList.any { condition -> @@ -88,7 +84,6 @@ class MessageManager : Launchable, VoidInitializable { } override fun activate() { - logger.error("#@# activate message manager") active = true Registries.getMessageRegistry().addDataObserver(messageRegistryChangeObserver) } From 0f5572288dd4a60f98010ca62b7daa48ff078d5a Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Fri, 5 Jan 2024 20:54:37 +0100 Subject: [PATCH 50/70] address some review issues by improving backend to get rid of test sleeps, cleanup unique label consistency handling. Fix rpc exception serialization to improve debugging experience. Reimplement location type dectection. Code cleanup and java -> kotlin transformations. --- .../app/preset/DeviceNotificationAppTest.kt | 57 +++- .../test/agent/AbstractBCOAppManagerTest.kt | 52 ++-- .../lib/AuthenticatedServiceProcessor.java | 105 ++++---- .../AbstractAuthorizedBaseUnitController.java | 1 + .../layer/unit/AbstractUnitController.java | 2 +- .../bco/dal/control/message/MessageManager.kt | 14 +- .../layer/service/AbstractServiceRemote.java | 21 +- .../service/ColorStateServiceRemote.java | 23 +- .../remote/layer/unit/AbstractUnitRemote.java | 15 +- .../visual/service/AbstractServicePanel.java | 20 +- .../bco/registry/lib/util/LocationUtils.java | 233 ----------------- .../bco/registry/lib/util/LocationUtils.kt | 247 ++++++++++++++++++ .../core/MessageRegistryController.java | 8 +- .../unit/core/UnitRegistryController.java | 6 +- .../BaseUnitTypeFieldConsistencyHandler.java | 8 +- .../DefaultUnitLabelConsistencyHandler.java | 34 +-- ...sUniqueVerificationConsistencyHandler.java | 85 ------ ...iasUniqueVerificationConsistencyHandler.kt | 59 +++++ ...ionGroupConfigLabelConsistencyHandler.java | 19 +- .../DalUnitLabelConsistencyHandler.java | 20 +- .../UserUnitLabelConsistencyHandler.java | 19 +- ...blishUnitTransformationRegistryPlugin.java | 16 +- .../core/plugin/UnitUserCreationPlugin.java | 4 +- .../lib/auth/AuthorizationWithTokenHelper.kt | 8 +- 24 files changed, 542 insertions(+), 534 deletions(-) delete mode 100644 module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.java create mode 100644 module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt delete mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.java create mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.kt diff --git a/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt b/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt index 700afc72ea..b9c44d23b1 100644 --- a/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt +++ b/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt @@ -3,15 +3,22 @@ package org.openbase.bco.app.preset import io.kotest.matchers.equals.shouldBeEqual import org.junit.jupiter.api.Test import org.openbase.app.test.agent.AbstractBCOAppManagerTest +import org.openbase.bco.authentication.lib.SessionManager +import org.openbase.bco.authentication.lib.future.AuthenticatedValueFuture import org.openbase.bco.dal.control.layer.unit.BatteryController import org.openbase.bco.dal.lib.layer.unit.Unit import org.openbase.bco.dal.lib.state.States import org.openbase.bco.dal.remote.layer.unit.BatteryRemote import org.openbase.bco.dal.remote.layer.unit.Units import org.openbase.bco.dal.remote.layer.unit.util.UnitStateAwaiter +import org.openbase.bco.registry.lib.util.UnitConfigProcessor import org.openbase.bco.registry.mock.MockRegistry import org.openbase.bco.registry.remote.Registries +import org.openbase.bco.registry.unit.core.plugin.UnitUserCreationPlugin +import org.openbase.jul.exception.CouldNotPerformException import org.openbase.jul.extension.type.processing.MultiLanguageTextProcessor +import org.openbase.type.domotic.authentication.AuthTokenType +import org.openbase.type.domotic.authentication.AuthenticationTokenType import org.openbase.type.domotic.communication.UserMessageType.UserMessage import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServiceType import org.openbase.type.domotic.state.ActivationStateType.ActivationState @@ -20,6 +27,7 @@ import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig import org.openbase.type.domotic.unit.UnitTemplateType import org.openbase.type.domotic.unit.app.AppDataType.AppData import java.util.* +import java.util.concurrent.ExecutionException import java.util.concurrent.TimeUnit class DeviceNotificationAppTest : AbstractBCOAppManagerTest() { @@ -38,6 +46,45 @@ class DeviceNotificationAppTest : AbstractBCOAppManagerTest condition.unitId == unit.id } } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + private fun requestAuthToken(unitConfig: UnitConfig): AuthTokenType.AuthToken { + try { + val userConfig: UnitConfig = UnitUserCreationPlugin.findUser( + unitConfig.id, Registries.getUnitRegistry(true).getUnitConfigsByUnitType( + UnitTemplateType.UnitTemplate.UnitType.USER + ) + ) + val authenticationToken = + AuthenticationTokenType.AuthenticationToken.newBuilder().setUserId(userConfig.getId()).build() + val sessionManager = SessionManager() + sessionManager.loginUser(userConfig.getId(), true) + val authenticatedValue = sessionManager.initializeRequest(authenticationToken, null) + return AuthTokenType.AuthToken.newBuilder().setAuthenticationToken( + AuthenticatedValueFuture( + Registries.getUnitRegistry().requestAuthenticationTokenAuthenticated(authenticatedValue), + String::class.java, + authenticatedValue.ticketAuthenticatorWrapper, + sessionManager + ).get() + ).build() + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException( + "Could not create authentication token for " + this + " " + UnitConfigProcessor.getDefaultAlias( + unitConfig, + unitConfig.id + ), ex + ) + } catch (ex: ExecutionException) { + throw CouldNotPerformException( + "Could not create authentication token for " + this + " " + UnitConfigProcessor.getDefaultAlias( + unitConfig, + unitConfig.id + ), ex + ) + } + } + @Test fun testLowBatteryNotification() { println("testAbsenceEnergySavingAgent") @@ -59,8 +106,6 @@ class DeviceNotificationAppTest : AbstractBCOAppManagerTest : BCOAppTest() { protected var appClass: AppClassType.AppClass? = null - protected var appConfig: UnitConfigType.UnitConfig? = null + protected var appConfig: UnitConfig? = null protected var appRemote: AppRemote? = null protected var appController: APP_CLASS? = null @@ -61,29 +63,38 @@ abstract class AbstractBCOAppManagerTest : B getAppClass().getSimpleName().replace("App", "") ) val appClass = Registries.getClassRegistry().registerAppClass(appClassBuilder.build())[5, TimeUnit.SECONDS] - val appConfigBuilder = getAppConfig() - appConfigBuilder.getAppConfigBuilder().setAppClassId(appClass.getId()) - appConfigBuilder.setUnitType(UnitTemplateType.UnitTemplate.UnitType.APP) - // register app - val appConfig = Registries.getUnitRegistry() - .registerUnitConfig(appConfigBuilder.build())[5, TimeUnit.SECONDS] + var appConfig = getAppConfig().apply { + this.appConfigBuilder.setAppClassId(appClass.getId()) + this.setUnitType(UnitTemplateType.UnitTemplate.UnitType.APP) + this.appConfigBuilder.setAutostart(true) + }.build() + + // cleanup old app instances + appConfig.aliasList + .flatMap { alias -> + Registries.getUnitRegistry().getUnitConfigs( + UnitFilterType.UnitFilter.newBuilder() + .setProperties(UnitConfig.newBuilder().addAlias(alias).build()).build() + ) + } + .distinctBy { config -> config.id } + .forEach { config -> + Registries.getUnitRegistry().removeUnitConfig(config).get( + 5, + TimeUnit.SECONDS + ) + } + // register app + appConfig = Registries.getUnitRegistry().registerUnitConfig(appConfig)[5, TimeUnit.SECONDS] Registries.waitUntilReady() // retrieve remote and activate app val appRemote = Units.getUnit(appConfig, true, Units.APP) - if (!appConfig.appConfig.autostart) { - // activate app if not in auto start - waitForExecution( - appRemote.setActivationState( - ActivationStateType.ActivationState.newBuilder().setValue( - ActivationStateType.ActivationState.State.ACTIVE - ).build() - ) - ) - } else { - // wait until active - UnitStateAwaiter(appRemote).waitForState { data: AppDataType.AppData -> data.activationState.getValue() == ActivationStateType.ActivationState.State.ACTIVE } + + // wait until active + UnitStateAwaiter(appRemote).waitForState { data: AppDataType.AppData -> + data.activationState.value == ActivationStateType.ActivationState.State.ACTIVE } this.appClass = appClass @@ -91,6 +102,9 @@ abstract class AbstractBCOAppManagerTest : B this.appRemote = appRemote this.appController = appManagerLauncher.launchable!!.appControllerRegistry.get(appConfig?.getId()) as APP_CLASS + + // final sync + Registries.requestData()[5, TimeUnit.SECONDS] } catch (ex: Exception) { throw ExceptionPrinter.printHistoryAndReturnThrowable(ex, LOGGER) } diff --git a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/AuthenticatedServiceProcessor.java b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/AuthenticatedServiceProcessor.java index ceffdd6060..591ef0fee8 100644 --- a/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/AuthenticatedServiceProcessor.java +++ b/module/authentication/lib/src/main/java/org/openbase/bco/authentication/lib/AuthenticatedServiceProcessor.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 * . @@ -111,69 +111,66 @@ public static Authen final Class internalClass, final TicketValidator ticketValidator, final InternalIdentifiedProcessable executable) throws CouldNotPerformException { - try { - // start to build the response - AuthenticatedValue.Builder response = AuthenticatedValue.newBuilder(); - if (authenticatedValue.hasTicketAuthenticatorWrapper()) { - try { - if (!JPService.getProperty(JPAuthentication.class).getValue()) { - throw new CouldNotPerformException("Cannot execute authenticated action because authentication is disabled"); - } - } catch (JPNotAvailableException ex) { - throw new CouldNotPerformException("Could not check JPEnableAuthentication property", ex); - } - // verify ticket in encrypt tokens if they were send - final AuthenticationBaseData authenticationBaseData = ticketValidator.verifyClientServerTicket(authenticatedValue); - RECEIVE decrypted = null; - // decrypt the send type from the AuthenticatedValue - if (authenticatedValue.hasValue()) { - decrypted = EncryptionHelper.decryptSymmetric(authenticatedValue.getValue(), authenticationBaseData.getSessionKey(), internalClass); + AuthenticatedValue.Builder response = AuthenticatedValue.newBuilder(); + if (authenticatedValue.hasTicketAuthenticatorWrapper()) { + try { + if (!JPService.getProperty(JPAuthentication.class).getValue()) { + throw new CouldNotPerformException("Cannot execute authenticated action because authentication is disabled"); } + } catch (JPNotAvailableException ex) { + throw new CouldNotPerformException("Could not check JPEnableAuthentication property", ex); + } + // verify ticket in encrypt tokens if they were send + final AuthenticationBaseData authenticationBaseData = ticketValidator.verifyClientServerTicket(authenticatedValue); - // execute the action of the server - RETURN result = executable.process(decrypted, authenticationBaseData); + RECEIVE decrypted = null; + // decrypt the send type from the AuthenticatedValue + if (authenticatedValue.hasValue()) { + decrypted = EncryptionHelper.decryptSymmetric(authenticatedValue.getValue(), authenticationBaseData.getSessionKey(), internalClass); + } - if (result != null) { - // encrypt the result and add it to the response - response.setValue(EncryptionHelper.encryptSymmetric(result, authenticationBaseData.getSessionKey())); - } - // add updated ticket to response - response.setTicketAuthenticatorWrapper(authenticationBaseData.getTicketAuthenticatorWrapper()); - } else { - // ticket no available so request without login - try { - RECEIVE message = null; + // execute the action of the server + RETURN result = executable.process(decrypted, authenticationBaseData); - if (authenticatedValue.hasValue() && !authenticatedValue.getValue().isEmpty()) { - if (!Message.class.isAssignableFrom(internalClass)) { - throw new CouldNotPerformException("Authenticated value has a value but the method implemented by the server did not expect one!"); - } - // when not logged in the received value is not encrypted but just send as a byte string - // so get the received message by calling parseFrom which is supported by every message - Method parseFrom = internalClass.getMethod("parseFrom", ByteString.class); - message = (RECEIVE) parseFrom.invoke(null, authenticatedValue.getValue()); - } + if (result != null) { + // encrypt the result and add it to the response + response.setValue(EncryptionHelper.encryptSymmetric(result, authenticationBaseData.getSessionKey())); + } + // add updated ticket to response + response.setTicketAuthenticatorWrapper(authenticationBaseData.getTicketAuthenticatorWrapper()); + } else { + // ticket no available so request without login + try { + RECEIVE message = null; - // execute the action of the server - RETURN result = executable.process(message, null); - if (result != null) { - if (!(result instanceof Message)) { - throw new CouldNotPerformException("Result[" + result + "] of authenticated action is not a message or not null and therefore not supported!"); - } + if (authenticatedValue.hasValue() && !authenticatedValue.getValue().isEmpty()) { + if (!Message.class.isAssignableFrom(internalClass)) { + throw new CouldNotPerformException("Authenticated value has a value but the method implemented by the server did not expect one!"); + } + // when not logged in the received value is not encrypted but just send as a byte string + // so get the received message by calling parseFrom which is supported by every message + Method parseFrom = internalClass.getMethod("parseFrom", ByteString.class); + message = (RECEIVE) parseFrom.invoke(null, authenticatedValue.getValue()); + } - // add result as a byte string to the response - response.setValue(((Message) result).toByteString()); + // execute the action of the server + RETURN result = executable.process(message, null); + if (result != null) { + if (!(result instanceof Message)) { + throw new CouldNotPerformException("Result[" + result + "] of authenticated action is not a message or not null and therefore not supported!"); } - } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - throw new CouldNotPerformException("Could not invoke parseFrom method on [" + internalClass.getSimpleName() + "]", ex); + + // add result as a byte string to the response + response.setValue(((Message) result).toByteString()); } + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | + InvocationTargetException ex) { + throw new CouldNotPerformException("Could not invoke parseFrom method on [" + internalClass.getSimpleName() + "]", ex); } - // return the response - return response.build(); - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not execute authenticated action!", ex); } + // return the response + return response.build(); } /** diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java index fec9a639fb..be8d587f77 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java @@ -106,6 +106,7 @@ private AuthToken requestAuthToken(final UnitConfig unitConfig) throws CouldNotP authenticatedValue.getTicketAuthenticatorWrapper(), sessionManager).get()).build(); } catch (CouldNotPerformException | ExecutionException ex) { + logger.error("Could not create authentication token for " + this + " " + unitConfig.getId()); throw new CouldNotPerformException("Could not create authentication token for " + this + " " + UnitConfigProcessor.getDefaultAlias(unitConfig, unitConfig.getId()), ex); } } diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java index a13c39b949..3164d573d3 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractUnitController.java @@ -186,7 +186,7 @@ public void update(DataProvider source, UnitRegistryData data) } } catch (NotAvailableException ex) { // unit config has been removed, probably because of deletion and a higher controller will do the shutdown in this case - logger.debug("Could not update unit controller", ex); + logger.trace("Skip controller config update since unit is not yet or not anymore included in the registry."); } catch (CouldNotPerformException ex) { ExceptionPrinter.printHistory("Could not update unit config of " + this, ex, logger); } diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt index 58e4421976..1b26e483d1 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/message/MessageManager.kt @@ -4,6 +4,7 @@ import com.google.protobuf.Message import org.openbase.bco.dal.lib.layer.service.Services import org.openbase.bco.dal.lib.layer.unit.UnitRemote import org.openbase.bco.dal.remote.layer.unit.Units +import org.openbase.bco.registry.message.remote.removeUserMessageAuthenticated import org.openbase.bco.registry.remote.Registries import org.openbase.jul.exception.CouldNotPerformException import org.openbase.jul.exception.InitializationException @@ -12,6 +13,7 @@ import org.openbase.jul.iface.VoidInitializable import org.openbase.jul.pattern.Observer import org.openbase.jul.pattern.provider.DataProvider import org.openbase.jul.schedule.RecurrenceEventFilter +import org.openbase.type.domotic.authentication.AuthTokenType.AuthToken import org.openbase.type.domotic.registry.MessageRegistryDataType.MessageRegistryData import org.slf4j.LoggerFactory import java.time.Duration @@ -25,7 +27,7 @@ class MessageManager : Launchable, VoidInitializable { private val unitsOfConditionsLock = ReentrantReadWriteLock() - private val maxUpdateInterval: Duration = Duration.ofSeconds(1) + private val maxUpdateInterval: Duration = Duration.ofSeconds(30) private var active = false @@ -36,7 +38,7 @@ class MessageManager : Launchable, VoidInitializable { } } - fun removeOutdatedMessages() { + fun removeOutdatedMessages(auth: AuthToken? = null) { logger.trace("removeOutdatedMessages") Registries.getMessageRegistry().userMessages .filterNot { message -> @@ -49,7 +51,13 @@ class MessageManager : Launchable, VoidInitializable { } } } - .forEach { Registries.getMessageRegistry().removeUserMessage(it).get(5, TimeUnit.SECONDS) } + .forEach { + if (auth != null) { + Registries.getMessageRegistry().removeUserMessageAuthenticated(it, auth).get(5, TimeUnit.SECONDS) + } else { + Registries.getMessageRegistry().removeUserMessage(it).get(5, TimeUnit.SECONDS) + } + } } private var unitsOfConditions: List>? = null diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/AbstractServiceRemote.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/AbstractServiceRemote.java index f6dcb0f135..e43815ed42 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/AbstractServiceRemote.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/AbstractServiceRemote.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 * . @@ -144,7 +144,7 @@ public AbstractServiceRemote(final ServiceType serviceType, final Class serv try { updateServiceState(); } catch (CouldNotPerformException ex) { - ExceptionPrinter.printHistory("Initial service state computation failed. This can be the case if any required date is not available yet.", ex, logger, LogLevel.DEBUG); + ExceptionPrinter.printHistory("Service state computation failed. This can be the case if any required date is not available yet.", ex, logger, LogLevel.DEBUG); } }; this.unitConfigObserver = (source, data) -> { @@ -198,10 +198,10 @@ private void updateServiceState() throws CouldNotPerformException { @Override public ST getData() throws NotAvailableException { // synchronized (syncObject) { - if (!serviceStateObservable.isValueAvailable()) { - throw new NotAvailableException("Data"); - } - return serviceStateObservable.getValue(); + if (!serviceStateObservable.isValueAvailable()) { + throw new NotAvailableException("Data"); + } + return serviceStateObservable.getValue(); // } } @@ -487,10 +487,13 @@ public void activate() throws CouldNotPerformException, InterruptedException { remote.addConnectionStateObserver(connectionStateObserver); } + // trigger initial state computation in case data is already available try { - updateServiceState(); + if (unitRemoteMap.values().stream().anyMatch(DataProvider::isDataAvailable)) { + updateServiceState(); + } } catch (CouldNotPerformException ex) { - ExceptionPrinter.printHistory("Initial service state computation failed. This can be the case if any required date is not available yet.", ex, logger, LogLevel.DEBUG); + logger.trace("Initial service state computation skipped because no related data available yet!"); } } diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/ColorStateServiceRemote.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/ColorStateServiceRemote.java index 33808fc0fb..c0d506901d 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/ColorStateServiceRemote.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/service/ColorStateServiceRemote.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 * . @@ -26,7 +26,6 @@ import org.openbase.bco.dal.lib.layer.service.Services; import org.openbase.bco.dal.lib.layer.service.collection.ColorStateOperationServiceCollection; import org.openbase.bco.dal.lib.layer.service.operation.ColorStateOperationService; -import org.openbase.bco.dal.lib.layer.unit.Unit; import org.openbase.bco.dal.lib.layer.unit.UnitRemote; import org.openbase.jul.exception.CouldNotPerformException; import org.openbase.jul.exception.CouldNotTransformException; @@ -53,7 +52,6 @@ import java.util.concurrent.TimeUnit; /** - * * * @author Tamino Huxohl */ public class ColorStateServiceRemote extends AbstractServiceRemote implements ColorStateOperationServiceCollection { @@ -67,6 +65,7 @@ public ColorStateServiceRemote() { * Computes the average RGB color. * * @return {@inheritDoc} + * * @throws CouldNotPerformException {@inheritDoc} */ @Override @@ -93,7 +92,7 @@ public ColorState getColorState(final UnitType unitType) throws NotAvailableExce long timestamp = 0; ActionDescription latestAction = null; final Collection colorStateOperationServiceCollection = getServices(unitType); - int amount = colorStateOperationServiceCollection.size(); + int number = colorStateOperationServiceCollection.size(); // iterate over all services and collect available states for (ColorStateOperationService service : colorStateOperationServiceCollection) { @@ -105,7 +104,7 @@ public ColorState getColorState(final UnitType unitType) throws NotAvailableExce || !state.getColor().getHsbColor().hasHue() || !state.getColor().getHsbColor().hasSaturation() || !state.getColor().getHsbColor().hasBrightness()) { - amount--; + number--; continue; } @@ -119,14 +118,14 @@ public ColorState getColorState(final UnitType unitType) throws NotAvailableExce latestAction = selectLatestAction(state, latestAction); } - if (amount == 0) { + if (number == 0) { throw new NotAvailableException("ColorState"); } // finally compute color average in rgb space - averageRed = averageRed / amount; - averageGreen = averageGreen / amount; - averageBlue = averageBlue / amount; + averageRed = averageRed / number; + averageGreen = averageGreen / number; + averageBlue = averageBlue / number; Builder serviceStateBuilder = ColorState.newBuilder(); @@ -156,7 +155,7 @@ public ColorState getColorState(final UnitType unitType) throws NotAvailableExce @Override public Future setNeutralWhite() { List> futureList = new ArrayList<>(); - for(ColorStateOperationService colorStateOperationService : getServices()) { + for (ColorStateOperationService colorStateOperationService : getServices()) { futureList.add(colorStateOperationService.setNeutralWhite()); } return FutureProcessor.allOf(ActionDescription.getDefaultInstance(), futureList); @@ -165,7 +164,7 @@ public Future setNeutralWhite() { @Override public Future setNeutralWhite(final ActionParameter actionParameter) { List> futureList = new ArrayList<>(); - for(ColorStateOperationService colorStateOperationService : getServices()) { + for (ColorStateOperationService colorStateOperationService : getServices()) { futureList.add(colorStateOperationService.setNeutralWhite(actionParameter)); } return FutureProcessor.allOf(ActionDescription.getDefaultInstance(), futureList); diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/AbstractUnitRemote.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/AbstractUnitRemote.java index f4c42b5d63..e0801800b8 100644 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/AbstractUnitRemote.java +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/layer/unit/AbstractUnitRemote.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 * . @@ -69,6 +69,7 @@ import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate; import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType; import org.slf4j.LoggerFactory; + import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -112,7 +113,7 @@ public void update(final DataProvider source, UnitRegistryData } } catch (NotAvailableException ex) { // unit config has been removed, probably because of deletion, Units will shutdown this remote - logger.debug("Could not update unit remote", ex); + logger.trace("Skip remote config update since unit is not yet or not anymore included in the registry."); } catch (CouldNotPerformException ex) { ExceptionPrinter.printHistory("Could not update unit config of " + this, ex, logger); } @@ -719,11 +720,13 @@ public Future queryRecord(final QueryType /** * {@inheritDoc} + * * @param actionId {@inheritDoc} * * @return {@inheritDoc} + * * @throws NotAvailableException {@inheritDoc} - * @throws InterruptedException {@inheritDoc} + * @throws InterruptedException {@inheritDoc} */ @Override public ActionDescription resolveRelatedActionDescription(String actionId) throws NotAvailableException, InterruptedException { @@ -746,7 +749,7 @@ public ActionDescription resolveRelatedActionDescription(String actionId) throws for (final ActionReference actionReference : actionDescription.getActionImpactList()) { // once the action is only precomputed we need to lookup it from its related unit. - if(actionReference.getActionId().equals(Action.PRECOMPUTED_ACTION_ID)) { + if (actionReference.getActionId().equals(Action.PRECOMPUTED_ACTION_ID)) { try { return Units.getUnit(actionReference.getServiceStateDescription().getUnitId(), true).resolveRelatedActionDescription(actionId); } catch (NotAvailableException ex) { @@ -763,6 +766,6 @@ public ActionDescription resolveRelatedActionDescription(String actionId) throws throw new NotAvailableException("RelatedAction", ex); } // no relation found. - throw new NotAvailableException("RelatedAction of ["+actionId+"]"); + throw new NotAvailableException("RelatedAction of [" + actionId + "]"); } } diff --git a/module/dal/visual/src/main/java/org/openbase/bco/dal/visual/service/AbstractServicePanel.java b/module/dal/visual/src/main/java/org/openbase/bco/dal/visual/service/AbstractServicePanel.java index 068bc3594f..84da99743e 100644 --- a/module/dal/visual/src/main/java/org/openbase/bco/dal/visual/service/AbstractServicePanel.java +++ b/module/dal/visual/src/main/java/org/openbase/bco/dal/visual/service/AbstractServicePanel.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -27,26 +27,28 @@ import org.openbase.bco.dal.lib.layer.service.provider.ProviderService; import org.openbase.bco.dal.lib.layer.unit.UnitRemote; import org.openbase.bco.dal.visual.util.StatusPanel; -import org.openbase.jul.exception.*; import org.openbase.jul.exception.InstantiationException; +import org.openbase.jul.exception.*; +import org.openbase.jul.extension.type.processing.ScopeProcessor; import org.openbase.jul.iface.Shutdownable; import org.openbase.jul.pattern.Observer; import org.openbase.jul.pattern.controller.Remote; -import org.openbase.type.domotic.state.ConnectionStateType.ConnectionState; import org.openbase.jul.schedule.SyncObject; +import org.openbase.type.domotic.service.ServiceDescriptionType; +import org.openbase.type.domotic.service.ServiceTemplateType; +import org.openbase.type.domotic.state.ConnectionStateType.ConnectionState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.openbase.type.domotic.service.ServiceTemplateType; import javax.swing.*; import java.awt.*; import java.util.concurrent.Future; -import org.openbase.type.domotic.service.ServiceDescriptionType; /** * @param * @param * @param + * * @author Divine Threepwood */ public abstract class AbstractServicePanel extends javax.swing.JPanel implements Shutdownable { @@ -150,7 +152,7 @@ public String getServiceName() { } return "---"; } - + protected UnitRemote getUnitRemote() { return this.unitRemote; } @@ -231,6 +233,7 @@ public void setServiceType(ServiceTemplateType.ServiceTemplate.ServiceType servi * Initializes this service panel with the given unit remote. * * @param unitRemote + * * @throws CouldNotPerformException * @throws InterruptedException */ @@ -254,6 +257,7 @@ public void initObserver() throws CouldNotPerformException, InterruptedException * Make sure the remote unit was initialized before and the service description is compatible with this unit. * * @param serviceDescription the new service description to bind to this unit remote. + * * @throws CouldNotPerformException is thrown if any error occurs during the binding process. * @throws InterruptedException */ @@ -264,7 +268,7 @@ public void bindServiceConfig(final ServiceDescriptionType.ServiceDescription se } setServiceConfig(serviceDescription); } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not bind ServiceConfig[" + serviceDescription.getServiceType() + "] on UnitRemote[" + unitRemote.getScope() + "]!", ex); + throw new CouldNotPerformException("Could not bind ServiceConfig[" + serviceDescription.getServiceType() + "] on UnitRemote[" + ScopeProcessor.generateStringRep(unitRemote.getScope()) + "]!", ex); } } diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.java b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.java deleted file mode 100644 index 0f71b830cf..0000000000 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.java +++ /dev/null @@ -1,233 +0,0 @@ -package org.openbase.bco.registry.lib.util; - -/* - * #%L - * BCO Registry Lib - * %% - * Copyright (C) 2014 - 2021 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ -import java.util.*; - -import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.exception.InvalidStateException; -import org.openbase.jul.exception.NotAvailableException; -import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap; -import org.openbase.jul.storage.registry.ConsistencyHandler; -import org.openbase.jul.storage.registry.EntryModification; -import org.openbase.jul.storage.registry.ProtoBufRegistry; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType; -import org.openbase.type.spatial.PlacementConfigType; - -/** - * - * @author Divine Threepwood - * - */ -public class LocationUtils { - - public static void validateRootLocation(final UnitConfig newRootLocation, final ProtoBufMessageMap entryMap, final ConsistencyHandler consistencyHandler) throws CouldNotPerformException, EntryModification { - try { - boolean modified = false; - // detect root location - IdentifiableMessage detectedRootLocationConfigEntry = entryMap.get(newRootLocation.getId()); - UnitConfig.Builder detectedRootLocationConfigBuilder = detectedRootLocationConfigEntry.getMessage().toBuilder(); - - // verify if root flag is set. - if (!detectedRootLocationConfigBuilder.getLocationConfig().hasRoot() || !detectedRootLocationConfigBuilder.getLocationConfig().getRoot()) { - detectedRootLocationConfigBuilder.getLocationConfigBuilder().setRoot(true); - modified = true; - } - - // verify if placement field is set. - if (!detectedRootLocationConfigBuilder.hasPlacementConfig()) { - detectedRootLocationConfigBuilder.setPlacementConfig(PlacementConfigType.PlacementConfig.newBuilder()); - modified = true; - } - - // verify if placement location id is set. - if (!detectedRootLocationConfigBuilder.getPlacementConfig().hasLocationId() || !detectedRootLocationConfigBuilder.getPlacementConfig().getLocationId().equals(detectedRootLocationConfigBuilder.getId())) { - detectedRootLocationConfigBuilder.getPlacementConfigBuilder().setLocationId(detectedRootLocationConfigBuilder.getId()); - modified = true; - } - - if (modified) { - throw new EntryModification(detectedRootLocationConfigEntry.setMessage(detectedRootLocationConfigBuilder, consistencyHandler), consistencyHandler); - } - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not validate root location!", ex); - } - } - - public static UnitConfig getRootLocation(final ProtoBufMessageMap entryMap) throws CouldNotPerformException { - return getRootLocation(entryMap.getMessages()); - } - - public static UnitConfig getRootLocation(final List locationUnitConfigList) throws CouldNotPerformException { - UnitConfig rootLocation = null; - try { - for (UnitConfig locationConfig : locationUnitConfigList) { - if (locationConfig.getLocationConfig().hasRoot() && locationConfig.getLocationConfig().getRoot()) { - if (rootLocation != null) { - throw new InvalidStateException("Found more than one [" + rootLocation.getLabel() + "] & [" + locationConfig.getLabel() + "] root locations!"); - } - rootLocation = locationConfig; - } - } - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not lookup root location!", ex); - } - - if (rootLocation == null) { - throw new NotAvailableException("root location"); - } - return rootLocation; - } - - public static UnitConfig detectRootLocation(final UnitConfig currentLocationConfig, final ProtoBufMessageMap entryMap, final ConsistencyHandler consistencyHandler) throws CouldNotPerformException, EntryModification { - try { - try { - return getRootLocation(entryMap); - } catch (NotAvailableException ex) { - UnitConfig newLocationConfig = computeNewRootLocation(currentLocationConfig, entryMap); - validateRootLocation(newLocationConfig, entryMap, consistencyHandler); - return newLocationConfig; - } - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not detect root location!", ex); - } - } - - public static UnitConfig computeNewRootLocation(final UnitConfig currentLocationConfig, ProtoBufMessageMap entryMap) throws CouldNotPerformException { - try { - HashMap rootLocationConfigList = new HashMap<>(); - for (UnitConfig locationConfig : entryMap.getMessages()) { - rootLocationConfigList.put(locationConfig.getId(), locationConfig); - } - - rootLocationConfigList.put(currentLocationConfig.getId(), currentLocationConfig); - - if (rootLocationConfigList.size() == 1) { - for (UnitConfig unitConfig : rootLocationConfigList.values()) { - return Optional.of(unitConfig).get(); - } - return Optional.empty().get(); - } - - for (UnitConfig locationConfig : new ArrayList<>(rootLocationConfigList.values())) { - if (!locationConfig.hasPlacementConfig()) { - } else if (!locationConfig.getPlacementConfig().hasLocationId()) { - } else if (locationConfig.getPlacementConfig().getLocationId().isEmpty()) { - } else if (locationConfig.getPlacementConfig().getLocationId().equals(locationConfig.getId())) { - return locationConfig; - } else { - rootLocationConfigList.remove(locationConfig.getId()); - } - } - - if (rootLocationConfigList.isEmpty()) { - throw new NotAvailableException("root candidate"); - } else if (rootLocationConfigList.size() == 1) { - for (UnitConfig unitConfig : rootLocationConfigList.values()) { - return Optional.of(unitConfig).get(); - } - return Optional.empty().get(); - } - - throw new InvalidStateException("To many potential root locations detected!"); - - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not compute root location!", ex); - } - } - - /** - * Detect the type of a location. - * Since a location, except the root location, always has a parent the only ambiguous case - * is when the parent is a zone but no children are defined. Than the location can either be - * a zone or a tile. In this case an exception is thrown. - * - * @param locationUnit the location of which the type is detected - * @param locationRegistry the location registry - * @return the type locationUnit should have - * @throws CouldNotPerformException if the type is ambiguous - */ - public static LocationType detectLocationType(UnitConfig locationUnit, ProtoBufRegistry locationRegistry) throws CouldNotPerformException { - try { - if (!locationUnit.hasPlacementConfig()) { - throw new NotAvailableException("placementConfig"); - } - if (!locationUnit.getPlacementConfig().hasLocationId() || locationUnit.getPlacementConfig().getLocationId().isEmpty()) { - throw new NotAvailableException("placementConfig.locationId"); - } - - if (locationUnit.getLocationConfig().getRoot()) { - return LocationType.ZONE; - } - - LocationType parentLocationType = locationRegistry.get(locationUnit.getPlacementConfig().getLocationId()).getMessage().getLocationConfig().getLocationType(); - Set childLocationTypes = new HashSet<>(); - for (String childId : locationUnit.getLocationConfig().getChildIdList()) { - childLocationTypes.add(locationRegistry.get(childId).getMessage().getLocationConfig().getLocationType()); - } - - return detectLocationType(parentLocationType, childLocationTypes); - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not detect Type for location [" + locationUnit.getLabel() + "]", ex); - } - } - - /** - * Detect the type of a location. - * Since a location, except the root location, always has a parent the only ambiguous case - * is when the parent is a zone but no children are defined. Than the location can either be - * a zone or a tile. In this case an exception is thrown. - * - * @param parentLocationType the type of the parent location of the location whose type is detected - * @param childLocationTypes a set of all immediate child types of the location whose type is detected - * @return the type locationUnit should have - * @throws CouldNotPerformException if the type is ambiguous - */ - private static LocationType detectLocationType(LocationType parentLocationType, Set childLocationTypes) throws CouldNotPerformException { - // if the parent is a region or tile than the location has to be a region - if (parentLocationType == LocationType.REGION || parentLocationType == LocationType.TILE) { - return LocationType.REGION; - } - - // if one child is a zone or a tile the location has to be a zone - if (childLocationTypes.contains(LocationType.ZONE) || childLocationTypes.contains(LocationType.TILE)) { - return LocationType.ZONE; - } - - // if the parent is a zone and a child is a region than the location has to be a tile - if (parentLocationType == LocationType.ZONE && childLocationTypes.contains(LocationType.REGION)) { - return LocationType.TILE; - } - - // if the parent type is a zone but no childs are defined the location could be a tile or a zone which leaves the type undefined - String childTypes = ""; - String acc = childTypes; - for (LocationType locationType : childLocationTypes) { - String s = locationType.toString() + " "; - acc = acc.concat(s); - } - childTypes = "[ " + acc + "]"; - throw new CouldNotPerformException("Could not detect locationType from parentType[" + parentLocationType.name() + "] and childTypes" + childTypes); - } -} diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt new file mode 100644 index 0000000000..cebb59fcca --- /dev/null +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt @@ -0,0 +1,247 @@ +package org.openbase.bco.registry.lib.util + +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.InvalidStateException +import org.openbase.jul.exception.NotAvailableException +import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap +import org.openbase.jul.storage.registry.ConsistencyHandler +import org.openbase.jul.storage.registry.EntryModification +import org.openbase.jul.storage.registry.ProtoBufRegistry +import org.openbase.type.domotic.unit.UnitConfigType +import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType +import org.openbase.type.spatial.PlacementConfigType +import java.util.* + +/** + * + * @author [Divine Threepwood](mailto:divine@openbase.org) + */ +object LocationUtils { + @JvmStatic + @Throws(CouldNotPerformException::class, EntryModification::class) + fun validateRootLocation( + newRootLocation: UnitConfigType.UnitConfig, + entryMap: ProtoBufMessageMap, + consistencyHandler: ConsistencyHandler<*, *, *, *>, + ) { + try { + var modified = false + // detect root location + val detectedRootLocationConfigEntry = entryMap[newRootLocation.id] + val detectedRootLocationConfigBuilder = detectedRootLocationConfigEntry.message.toBuilder() + + // verify if root flag is set. + if (!detectedRootLocationConfigBuilder.locationConfig.hasRoot() || !detectedRootLocationConfigBuilder.locationConfig.root) { + detectedRootLocationConfigBuilder.locationConfigBuilder.setRoot(true) + modified = true + } + + // verify if placement field is set. + if (!detectedRootLocationConfigBuilder.hasPlacementConfig()) { + detectedRootLocationConfigBuilder.setPlacementConfig(PlacementConfigType.PlacementConfig.newBuilder()) + modified = true + } + + // verify if placement location id is set. + if (!detectedRootLocationConfigBuilder.placementConfig.hasLocationId() || detectedRootLocationConfigBuilder.placementConfig.locationId != detectedRootLocationConfigBuilder.id) { + detectedRootLocationConfigBuilder.placementConfigBuilder.setLocationId(detectedRootLocationConfigBuilder.id) + modified = true + } + + if (modified) { + throw EntryModification( + detectedRootLocationConfigEntry.setMessage( + detectedRootLocationConfigBuilder, + consistencyHandler + ), consistencyHandler + ) + } + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException("Could not validate root location!", ex) + } + } + + @JvmStatic + @Throws(CouldNotPerformException::class) + fun getRootLocation(entryMap: ProtoBufMessageMap): UnitConfigType.UnitConfig { + return getRootLocation(entryMap.getMessages()) + } + + @JvmStatic + @Throws(CouldNotPerformException::class) + fun getRootLocation(locationUnitConfigList: List): UnitConfigType.UnitConfig { + var rootLocation: UnitConfigType.UnitConfig? = null + try { + for (locationConfig in locationUnitConfigList) { + if (locationConfig.locationConfig.hasRoot() && locationConfig.locationConfig.root) { + if (rootLocation != null) { + throw InvalidStateException("Found more than one [" + rootLocation.label + "] & [" + locationConfig.label + "] root locations!") + } + rootLocation = locationConfig + } + } + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException("Could not lookup root location!", ex) + } + + if (rootLocation == null) { + throw NotAvailableException("root location") + } + return rootLocation + } + + @JvmStatic + @Throws(CouldNotPerformException::class, EntryModification::class) + fun detectRootLocation( + currentLocationConfig: UnitConfigType.UnitConfig, + entryMap: ProtoBufMessageMap, + consistencyHandler: ConsistencyHandler<*, *, *, *>, + ): UnitConfigType.UnitConfig { + try { + try { + return getRootLocation(entryMap) + } catch (ex: NotAvailableException) { + val newLocationConfig = computeNewRootLocation(currentLocationConfig, entryMap) + validateRootLocation(newLocationConfig, entryMap, consistencyHandler) + return newLocationConfig + } + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException("Could not detect root location!", ex) + } + } + + @Throws(CouldNotPerformException::class) + fun computeNewRootLocation( + currentLocationConfig: UnitConfigType.UnitConfig, + entryMap: ProtoBufMessageMap, + ): UnitConfigType.UnitConfig { + try { + val rootLocationConfigList = HashMap() + for (locationConfig in entryMap.getMessages()) { + rootLocationConfigList[locationConfig.id] = locationConfig + } + + rootLocationConfigList[currentLocationConfig.id] = currentLocationConfig + + if (rootLocationConfigList.size == 1) { + for (unitConfig in rootLocationConfigList.values) { + return Optional.of(unitConfig).get() + } + return Optional.empty().get() + } + + for (locationConfig in ArrayList(rootLocationConfigList.values)) { + if (!locationConfig.hasPlacementConfig()) { + } else if (!locationConfig.placementConfig.hasLocationId()) { + } else if (locationConfig.placementConfig.locationId.isEmpty()) { + } else if (locationConfig.placementConfig.locationId == locationConfig.id) { + return locationConfig + } else { + rootLocationConfigList.remove(locationConfig.id) + } + } + + if (rootLocationConfigList.isEmpty()) { + throw NotAvailableException("root candidate") + } else if (rootLocationConfigList.size == 1) { + for (unitConfig in rootLocationConfigList.values) { + return Optional.of(unitConfig).get() + } + return Optional.empty().get() + } + + throw InvalidStateException("To many potential root locations detected!") + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException("Could not compute root location!", ex) + } + } + + /** + * Detect the type of a location. + * Since a location, except the root location, always has a parent the only ambiguous case + * is when the parent is a zone but no children are defined. Than the location can either be + * a zone or a tile. In this case an exception is thrown. + * + * @param locationUnit the location of which the type is detected + * @param locationRegistry the location registry + * @return the type locationUnit should have + * @throws CouldNotPerformException if the type is ambiguous + */ + @JvmStatic + @Throws(CouldNotPerformException::class) + fun detectLocationType( + locationUnit: UnitConfigType.UnitConfig, + locationRegistry: ProtoBufRegistry, + ): LocationType { + try { + if (!locationUnit.hasPlacementConfig()) { + throw NotAvailableException("placementConfig") + } + if (!locationUnit.placementConfig.hasLocationId() || locationUnit.placementConfig.locationId.isEmpty()) { + throw NotAvailableException("placementConfig.locationId") + } + + if (locationUnit.locationConfig.root) { + return LocationType.ZONE + } + + val parentLocationType = + locationRegistry[locationUnit.placementConfig.locationId].message.locationConfig.locationType + val childLocationTypes: MutableSet = HashSet() + for (childId in locationUnit.locationConfig.childIdList) { + childLocationTypes.add(locationRegistry[childId].message.locationConfig.locationType) + } + + return detectLocationType(parentLocationType, childLocationTypes) + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException("Could not detect Type for location [" + locationUnit.label + "]", ex) + } + } + + /** + * Detect the type of a location. + * Since a location, except the root location, always has a parent the only ambiguous case + * is when the parent is a zone but no children are defined. Than the location can either be + * a zone or a tile. In this case an exception is thrown. + * + * @param parentLocationType the type of the parent location of the location whose type is detected + * @param childLocationTypes a set of all immediate child types of the location whose type is detected + * @return the type locationUnit should have + * @throws CouldNotPerformException if the type is ambiguous + */ + @Throws(CouldNotPerformException::class) + private fun detectLocationType( + parentLocationType: LocationType, + childLocationTypes: Set, + ): LocationType { + + when (parentLocationType) { + // if the parent is a region or tile then the location has to be a region + LocationType.REGION, LocationType.TILE -> { + return LocationType.REGION + } + + LocationType.ZONE -> { + + // if the parent is a zone and has no children then it has to be + // a tile since each branch could contain exactly one tile + if (childLocationTypes.isEmpty()) { + return LocationType.TILE + } + + // if one child is a zone or a tile the location has to be a zone + if (childLocationTypes.contains(LocationType.ZONE) || childLocationTypes.contains(LocationType.TILE)) { + return LocationType.ZONE + } + + // if the parent is a zone and a child is a region than the location has to be a tile + if (childLocationTypes.contains(LocationType.REGION)) { + return LocationType.TILE + } + } + + LocationType.UNKNOWN -> {} // skip detection + } + throw CouldNotPerformException("Could not detect locationType from parentType[${parentLocationType.name}] and childTypes ${childLocationTypes.map { it.name }}") + } +} diff --git a/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java b/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java index d6c183e210..14e599937b 100644 --- a/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java +++ b/module/registry/message-registry/core/src/main/java/org/openbase/bco/registry/message/core/MessageRegistryController.java @@ -218,7 +218,13 @@ public Future removeUserMessageAuthenticated(final Authentic return GlobalCachedExecutorService.submit(() -> AuthenticatedServiceProcessor.authenticatedAction(authenticatedValue, UserMessage.class, this, (userMessage, authenticationBaseData) -> { // verify write permissions for the user message to remove - final UserMessage old = userMessageRegistry.getMessage(userMessage.getId()); + UserMessage old = null; + try { + old = userMessageRegistry.getMessage(userMessage.getId()); + } catch (NotAvailableException ex) { + // skip removal if message is not present. + return userMessage; + } AuthorizationWithTokenHelper.canDo(authenticationBaseData, old, PermissionType.WRITE, CachedUnitRegistryRemote.getRegistry()); return userMessageRegistry.remove(userMessage); } diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java index 202cb4cddc..a6172793eb 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java @@ -183,8 +183,8 @@ protected void postInit() throws InitializationException, InterruptedException { // post init loads registries super.postInit(); - // initially fill the alias to id map - // afterwards the {@code AliasMapUpdatePlugin} will manage changes on registering, removing or updating of units + // initially fill the alias to id map afterward + // the {@code AliasMapUpdatePlugin} will manage changes on registering, removing or updating of units synchronized (aliasIdMapLock) { try { for (ProtoBufFileSynchronizedRegistry registry : unitConfigRegistryList) { @@ -432,6 +432,7 @@ protected void registerDependencies() throws CouldNotPerformException { connectionUnitConfigRegistry.registerDependency(locationUnitConfigRegistry); + agentUnitConfigRegistry.registerDependency(userUnitConfigRegistry); agentUnitConfigRegistry.registerDependency(locationUnitConfigRegistry); agentUnitConfigRegistry.registerDependency(CachedClassRegistryRemote.getRegistry().getAgentClassRemoteRegistry(false)); @@ -439,6 +440,7 @@ protected void registerDependencies() throws CouldNotPerformException { appUnitConfigRegistry.registerDependency(CachedClassRegistryRemote.getRegistry().getAppClassRemoteRegistry(false)); appUnitConfigRegistry.registerDependency(locationUnitConfigRegistry); + appUnitConfigRegistry.registerDependency(userUnitConfigRegistry); } @Override diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/BaseUnitTypeFieldConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/BaseUnitTypeFieldConsistencyHandler.java index b21b5cd5be..74d8cc375d 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/BaseUnitTypeFieldConsistencyHandler.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/BaseUnitTypeFieldConsistencyHandler.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -43,7 +43,7 @@ public class BaseUnitTypeFieldConsistencyHandler extends AbstractProtoBufRegistr public void processData(String id, IdentifiableMessage entry, ProtoBufMessageMap entryMap, ProtoBufRegistry registry) throws CouldNotPerformException, EntryModification { // filter dal units - if(UnitConfigProcessor.isDalUnit(entry.getMessage().getUnitType())) { + if (UnitConfigProcessor.isDalUnit(entry.getMessage().getUnitType())) { return; } @@ -51,7 +51,7 @@ public void processData(String id, IdentifiableMessage. @@ -34,9 +34,7 @@ import org.openbase.jul.storage.registry.ProtoBufRegistry; import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; /** * Default label consistency handler for units. This consistency handler makes sure that a unit has at least one label @@ -46,12 +44,6 @@ */ public class DefaultUnitLabelConsistencyHandler extends AbstractProtoBufRegistryConsistencyHandler { - private final Map unitMap; - - public DefaultUnitLabelConsistencyHandler() { - this.unitMap = new HashMap<>(); - } - @Override public void processData(final String id, final IdentifiableMessage entry, @@ -90,26 +82,4 @@ protected String generateDefaultLabel(final UnitConfig unitConfig) throws CouldN } return UnitConfigProcessor.getDefaultAlias(unitConfig, "?"); } - - /** - * Generate a key for a label of a unit. - * This key can only exists once. - * Here the location id of the placement of the unit is added to the label to guarantee that this label - * exists only once per location. - * This method can be overwritten by sub classes to guarantee other things. - * - * @param label the label for which the key is generated - * @param languageKey the language key of the label. - * @param unitConfig the unit having the label - * - * @return a key for this label and unit - */ - protected String generateKey(final String label, final String languageKey, final UnitConfig unitConfig) { - return label + "_" + languageKey + "_" + unitConfig.getPlacementConfig().getLocationId(); - } - - @Override - public void reset() { - unitMap.clear(); - } } diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.java deleted file mode 100644 index 1c8701c19a..0000000000 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.openbase.bco.registry.unit.core.consistency; - -/*- - * #%L - * BCO Registry Unit Core - * %% - * Copyright (C) 2014 - 2021 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ - -import org.openbase.bco.registry.unit.lib.UnitRegistry; -import org.openbase.jul.exception.*; -import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap; -import org.openbase.jul.extension.type.processing.ScopeProcessor; -import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler; -import org.openbase.jul.storage.registry.EntryModification; -import org.openbase.jul.storage.registry.ProtoBufRegistry; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig.Builder; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -public class UnitAliasUniqueVerificationConsistencyHandler extends AbstractProtoBufRegistryConsistencyHandler { - - private final Map aliasUnitIdMap; - private final UnitRegistry unitRegistry; - - public UnitAliasUniqueVerificationConsistencyHandler(final UnitRegistry unitRegistry) { - this.unitRegistry = unitRegistry; - this.aliasUnitIdMap = new HashMap<>(); - } - - @Override - public void processData(String id, IdentifiableMessage entry, ProtoBufMessageMap entryMap, ProtoBufRegistry registry) throws CouldNotPerformException, EntryModification { - final UnitConfig.Builder unitConfig = entry.getMessage().toBuilder(); - - for (final String alias : unitConfig.getAliasList()) { - if (!aliasUnitIdMap.containsKey(alias.toLowerCase())) { - aliasUnitIdMap.put(alias.toLowerCase(), unitConfig.getId()); - } else { - // if already known check if this unit is owning the alias otherwise throw invalid state - if (!aliasUnitIdMap.get(alias.toLowerCase()).equals(unitConfig.getId())) { - throw new RejectedException("Alias[" + alias.toLowerCase() + "] of Unit[" + ScopeProcessor.generateStringRep(unitConfig.getScope()) + ", " + unitConfig.getId() + "] is already used by Unit[" + aliasUnitIdMap.get(alias.toLowerCase()) + "]"); - } - } - } - } - - @Override - public void reset() { - - // validate known aliases - for (String alias : new ArrayList<>(aliasUnitIdMap.keySet())) { - // remove alias entry if alias is globally unknown. - if (!unitRegistry.containsUnitConfigByAlias(alias)) { - logger.debug("remove alias: " + alias); - aliasUnitIdMap.remove(alias); - } - } - super.reset(); - } - - @Override - public void shutdown() { - aliasUnitIdMap.clear(); - // super call is not performed because those would only call reset() which fails because the unit registry is not responding during shutdown. - } -} diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.kt b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.kt new file mode 100644 index 0000000000..adcbf00652 --- /dev/null +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/UnitAliasUniqueVerificationConsistencyHandler.kt @@ -0,0 +1,59 @@ +package org.openbase.bco.registry.unit.core.consistency + +import org.openbase.bco.registry.unit.lib.UnitRegistry +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.RejectedException +import org.openbase.jul.extension.protobuf.IdentifiableMessage +import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap +import org.openbase.jul.extension.type.processing.ScopeProcessor +import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler +import org.openbase.jul.storage.registry.EntryModification +import org.openbase.jul.storage.registry.ProtoBufRegistry +import org.openbase.type.domotic.unit.UnitConfigType +import java.util.* + +class UnitAliasUniqueVerificationConsistencyHandler(private val unitRegistry: UnitRegistry) : + AbstractProtoBufRegistryConsistencyHandler() { + private var aliasUnitIdMap: MutableMap = HashMap() + + @Throws(CouldNotPerformException::class, EntryModification::class) + override fun processData( + id: String, + entry: IdentifiableMessage, + entryMap: ProtoBufMessageMap, + registry: ProtoBufRegistry, + ) { + val unitConfig = entry.message.toBuilder() + + for (alias in unitConfig.aliasList) { + if (!aliasUnitIdMap.containsKey(alias.lowercase(Locale.getDefault()))) { + aliasUnitIdMap[alias.lowercase(Locale.getDefault())] = unitConfig.id + } else { + // if already known check if this unit is owning the alias otherwise throw invalid state + if (aliasUnitIdMap[alias.lowercase(Locale.getDefault())] != unitConfig.id) { + throw RejectedException( + "Alias[" + alias.lowercase(Locale.getDefault()) + "] of Unit[" + ScopeProcessor.generateStringRep( + unitConfig.scope + ) + ", " + unitConfig.id + "] is already used by Unit[" + aliasUnitIdMap[alias.lowercase( + Locale.getDefault() + )] + "]" + ) + } + } + } + } + + override fun reset() { + aliasUnitIdMap = unitRegistry + .getUnitConfigs(true) + .flatMap { config -> config.aliasList.map { alias -> alias.lowercase() to config.id } } + .toMap() + .toMutableMap() + super.reset() + } + + override fun shutdown() { + aliasUnitIdMap.clear() + // super call is not performed because those would only call reset() which fails because the unit registry is not responding during shutdown. + } +} diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupConfigLabelConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupConfigLabelConsistencyHandler.java index 30e7044ab5..174bbb1610 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupConfigLabelConsistencyHandler.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/authorizationgroup/AuthorizationGroupConfigLabelConsistencyHandler.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -23,25 +23,10 @@ */ import org.openbase.bco.registry.unit.core.consistency.DefaultUnitLabelConsistencyHandler; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; /** * @author Tamino Huxohl */ public class AuthorizationGroupConfigLabelConsistencyHandler extends DefaultUnitLabelConsistencyHandler { - /** - * Label for authorization groups have to be globally unique. - * Therefore just return the label. - * - * @param label the label for which the key is generated - * @param languageKey the language key of the label. - * @param unitConfig the unit having the label - * - * @return the label - */ - @Override - protected String generateKey(final String label, final String languageKey, final UnitConfig unitConfig) { - return label + "_" + languageKey; - } } diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/dalunitconfig/DalUnitLabelConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/dalunitconfig/DalUnitLabelConsistencyHandler.java index 2d9184c432..0da1b8910e 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/dalunitconfig/DalUnitLabelConsistencyHandler.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/dalunitconfig/DalUnitLabelConsistencyHandler.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -36,10 +36,10 @@ import org.openbase.jul.storage.registry.ProtoBufFileSynchronizedRegistry; import org.openbase.jul.storage.registry.ProtoBufRegistry; import org.openbase.jul.storage.registry.Registry; -import org.openbase.type.language.LabelType.Label; import org.openbase.type.domotic.registry.UnitRegistryDataType.UnitRegistryData; import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; import org.openbase.type.domotic.unit.device.DeviceClassType.DeviceClass; +import org.openbase.type.language.LabelType.Label; import java.util.HashMap; import java.util.Map; @@ -126,18 +126,4 @@ public void processData(final String id, // make sure that label exists and are unique per location per unit type super.processData(id, entry, entryMap, registry); } - - /** - * Make sure that the label is unique per unit type and per location. - * - * @param label the label for which the key is generated - * @param languageKey the language key of the label. - * @param unitConfig the unit having the label - * - * @return a key unique per unit type per location - */ - @Override - protected String generateKey(final String label, final String languageKey, final UnitConfig unitConfig) { - return label + "_" + languageKey + "_" + unitConfig.getUnitType().name() + "_" + unitConfig.getPlacementConfig().getLocationId(); - } } diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/userconfig/UserUnitLabelConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/userconfig/UserUnitLabelConsistencyHandler.java index a13c3e1880..06858c1869 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/userconfig/UserUnitLabelConsistencyHandler.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/userconfig/UserUnitLabelConsistencyHandler.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -37,7 +37,9 @@ public class UserUnitLabelConsistencyHandler extends DefaultUnitLabelConsistency * Generate a default user name. * * @param unitConfig the unit config for which a label is generated + * * @return username (firstName lastName) + * * @throws CouldNotPerformException if values in the user config are missing */ @Override @@ -66,17 +68,4 @@ protected String generateDefaultLabel(UnitConfig unitConfig) throws CouldNotPerf } return label; } - - /** - * Return the label to make sure user label are unique. - * - * @param label the label for which the key is generated - * @param languageKey the language key of the label. - * @param unitConfig the unit having the label - * @return the label - */ - @Override - protected String generateKey(String label, final String languageKey, UnitConfig unitConfig) { - return label + "_" + languageKey; - } } diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/PublishUnitTransformationRegistryPlugin.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/PublishUnitTransformationRegistryPlugin.java index ff005d586d..4b570fe117 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/PublishUnitTransformationRegistryPlugin.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/plugin/PublishUnitTransformationRegistryPlugin.java @@ -10,12 +10,12 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . @@ -58,6 +58,16 @@ protected void publishTransformation(IdentifiableMessage. diff --git a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt index 6cb5cc903e..a5ec0590af 100644 --- a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt +++ b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt @@ -134,9 +134,9 @@ object AuthorizationWithTokenHelper { ): AuthPair { try { // validate sender - canDo( + return canDo( authenticationBaseData, - unitRegistry.getUnitConfigById(userMessage.getSenderId()), + unitRegistry.getUnitConfigById(userMessage.senderId), permissionType, unitRegistry, null, @@ -150,7 +150,7 @@ object AuthorizationWithTokenHelper { try { canDo( authenticationBaseData, - unitRegistry.getUnitConfigById(userMessage.getRecipientId()), + unitRegistry.getUnitConfigById(userMessage.recipientId), permissionType, unitRegistry, null, @@ -171,7 +171,7 @@ object AuthorizationWithTokenHelper { ) } catch (exxx: CouldNotPerformException) { exceptionStack = - MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) + MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) } } From d7db06eb5352dc3ebe1096cc6460dcd01ad3ce03 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Fri, 5 Jan 2024 21:01:46 +0100 Subject: [PATCH 51/70] link against compatible jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 7296390421..cf1d00daea 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 72963904216da0c780dc950d8a17499a46195900 +Subproject commit cf1d00daead828760b101c0e670e2e70f85b286e From 5f0a5d9f0bc40b65d36c4054b26d609bbae7e03f Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Fri, 5 Jan 2024 22:05:51 +0100 Subject: [PATCH 52/70] fix test issues --- .../openbase/bco/app/preset/DeviceNotificationApp.kt | 2 +- .../bco/app/preset/DeviceNotificationAppTest.kt | 2 +- .../test/AuthenticatorControllerTest.java | 11 +++++------ .../bco/authentication/test/StayLoggedInTest.kt | 4 ++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt index 802f6098d2..a3bc82873a 100644 --- a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt +++ b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt @@ -93,7 +93,7 @@ class DeviceNotificationApp : AbstractAppController() { MultiLanguageTextProcessor.addMultiLanguageText( textBuilder, Locale.GERMAN, - "Batteriezustand von ${ + "Batterieladung von ${ LabelProcessor.getBestMatch( Locale.GERMAN, remote.config.label diff --git a/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt b/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt index b9c44d23b1..89c0e99377 100644 --- a/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt +++ b/module/app/preset/src/test/java/org/openbase/bco/app/preset/DeviceNotificationAppTest.kt @@ -135,7 +135,7 @@ class DeviceNotificationAppTest : AbstractBCOAppManagerTest. @@ -42,12 +42,11 @@ import org.openbase.type.domotic.authentication.TicketAuthenticatorWrapperType.TicketAuthenticatorWrapper; import org.openbase.type.domotic.authentication.TicketSessionKeyWrapperType.TicketSessionKeyWrapper; import org.openbase.type.domotic.authentication.UserClientPairType.UserClientPair; -import org.slf4j.LoggerFactory; import java.util.concurrent.ExecutionException; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Tamino Huxohl @@ -334,7 +333,7 @@ public void testLoginCombinations() throws Exception { CachedAuthenticationRemote.getRemote().requestTicketGrantingTicket(clientAsymmetricUserAsymmetric).get(); fail("No exception throw even when authentication method is not supported."); } catch (ExecutionException ex) { - assertTrue(ExceptionProcessor.getInitialCauseMessage(ex).contains("NotSupportedException")); + assertTrue(ExceptionProcessor.getInitialCause(ex).getClass().getSimpleName().contains("NotSupportedException")); } finally { ExceptionPrinter.setBeQuit(false); } diff --git a/module/authentication/test/src/test/java/org/openbase/bco/authentication/test/StayLoggedInTest.kt b/module/authentication/test/src/test/java/org/openbase/bco/authentication/test/StayLoggedInTest.kt index 2c677d4b09..d0437c236c 100644 --- a/module/authentication/test/src/test/java/org/openbase/bco/authentication/test/StayLoggedInTest.kt +++ b/module/authentication/test/src/test/java/org/openbase/bco/authentication/test/StayLoggedInTest.kt @@ -13,7 +13,7 @@ import org.openbase.bco.authentication.mock.MockClientStore import org.openbase.bco.authentication.mock.MockCredentialStore import org.openbase.jps.core.JPService import org.openbase.jul.communication.mqtt.test.MqttIntegrationTest -import org.openbase.jul.exception.initialCauseMessage +import org.openbase.jul.exception.initialCause import org.openbase.jul.exception.printer.ExceptionPrinter import java.util.concurrent.ExecutionException @@ -93,7 +93,7 @@ class StayLoggedInTest : MqttIntegrationTest() { CachedAuthenticationRemote.getRemote().validateClientServerTicket(wrapper).get() Assertions.fail("No exception thrown even though the session should have timed out") } catch (ex: ExecutionException) { - ex.initialCauseMessage shouldContain "SessionExpired" + ex.initialCause.javaClass.simpleName shouldContain "SessionExpired" } finally { ExceptionPrinter.setBeQuit(false) } From a868456a3190ee06be86c335140400f29cfadc3e Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Mon, 8 Jan 2024 02:38:13 +0100 Subject: [PATCH 53/70] Stablize location and connection consistency handling --- lib/jul | 2 +- .../ConnectionLocationConsistencyHandler.java | 115 ------------------ .../ConnectionLocationConsistencyHandler.kt | 104 ++++++++++++++++ .../ConnectionTilesConsistencyHandler.java | 83 ------------- .../ConnectionTilesConsistencyHandler.kt | 42 +++++++ 5 files changed, 147 insertions(+), 199 deletions(-) delete mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.java create mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt delete mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.java create mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt diff --git a/lib/jul b/lib/jul index cf1d00daea..73c837a361 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit cf1d00daead828760b101c0e670e2e70f85b286e +Subproject commit 73c837a3617e3490a87f6acc88a72e4f7099aed6 diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.java deleted file mode 100644 index 8621070923..0000000000 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.openbase.bco.registry.unit.core.consistency.connectionconfig; - -/* - * #%L - * BCO Registry Unit Core - * %% - * Copyright (C) 2014 - 2021 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler; -import org.openbase.jul.storage.registry.EntryModification; -import org.openbase.jul.storage.registry.ProtoBufFileSynchronizedRegistry; -import org.openbase.type.spatial.PlacementConfigType.PlacementConfig; -import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap; -import org.openbase.jul.storage.registry.ProtoBufRegistry; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.registry.UnitRegistryDataType.UnitRegistryData; - -/** - * - * @author Tamino Huxohl - */ -public class ConnectionLocationConsistencyHandler extends AbstractProtoBufRegistryConsistencyHandler { - - private final ProtoBufFileSynchronizedRegistry locationRegistry; - - public ConnectionLocationConsistencyHandler(ProtoBufFileSynchronizedRegistry locationRegistry) { - this.locationRegistry = locationRegistry; - } - - @Override - public void processData(String id, IdentifiableMessage entry, ProtoBufMessageMap entryMap, ProtoBufRegistry registry) throws CouldNotPerformException, EntryModification { - UnitConfig.Builder connectionUnitConfig = entry.getMessage().toBuilder(); - - String locationId; - try { - locationId = getLowestCommonParentLocation(connectionUnitConfig.getConnectionConfig().getTileIdList(), locationRegistry).getId(); - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("Could not find parent location for connection [" + connectionUnitConfig + "]", ex); - } - if (!locationId.equals(connectionUnitConfig.getPlacementConfig().getLocationId())) { - PlacementConfig.Builder placement = connectionUnitConfig.getPlacementConfig().toBuilder().setLocationId(locationId); - throw new EntryModification(entry.setMessage(connectionUnitConfig.setPlacementConfig(placement), this), this); - } - } - - public static UnitConfig getLowestCommonParentLocation(List locationIds, ProtoBufFileSynchronizedRegistry locationUnitConfigRegistry) throws CouldNotPerformException { - // list containing the pathes from root to each location given by locationIds sorted by the lenght of the path, e.g.: - // home, apartment, hallway, entrance - // home, apartment, outdoor - final List> pathesFromRootMap = new ArrayList<>(); - - // fill the list according to the description above - for (String id : locationIds) { - UnitConfig locationUnitConfig = locationUnitConfigRegistry.getMessage(id); - final List pathFromRootList = new ArrayList<>(); - pathFromRootList.add(locationUnitConfig); - while (!locationUnitConfig.getLocationConfig().getRoot()) { - locationUnitConfig = locationUnitConfigRegistry.getMessage(locationUnitConfig.getPlacementConfig().getLocationId()); - // when adding a location at the front of the list, every entry is moved an index further - pathFromRootList.add(0, locationUnitConfig); - } - pathesFromRootMap.add(pathFromRootList); - } - - // sort the list after their sizes: - // home, apartment, outdoor - // home, apartment, hallway, entrance - pathesFromRootMap.sort(new Comparator>() { - - @Override - public int compare(List o1, List o2) { - return o2.size() - o1.size(); - } - }); - - // find the lowest common parent, e.g. for the example above apartment - // by returning the index before the first elements where the pathes differ - int shortestPath = pathesFromRootMap.get(0).size(); - for (int i = 0; i < shortestPath; ++i) { - String currentId = pathesFromRootMap.get(0).get(i).getId(); - for (int j = 1; j < pathesFromRootMap.size(); ++j) { - if (!pathesFromRootMap.get(j).get(i).getId().equals(currentId)) { - return pathesFromRootMap.get(0).get(i - 1); - } - } - } - - // checking if a lowst common parent exists should not be necessary since a tile cannot be root - return pathesFromRootMap.get(0).get(0); - } - - @Override - public void reset() { - } -} diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt new file mode 100644 index 0000000000..7606e11fdf --- /dev/null +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt @@ -0,0 +1,104 @@ +package org.openbase.bco.registry.unit.core.consistency.connectionconfig + +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.printer.ExceptionPrinter +import org.openbase.jul.extension.protobuf.IdentifiableMessage +import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap +import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler +import org.openbase.jul.storage.registry.EntryModification +import org.openbase.jul.storage.registry.ProtoBufFileSynchronizedRegistry +import org.openbase.jul.storage.registry.ProtoBufRegistry +import org.openbase.type.domotic.registry.UnitRegistryDataType.UnitRegistryData +import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig + +/** + * + * @author [Tamino Huxohl](mailto:pleminoq@openbase.org) + */ +class ConnectionLocationConsistencyHandler( + private val locationRegistry: ProtoBufFileSynchronizedRegistry, +) : + AbstractProtoBufRegistryConsistencyHandler() { + @Throws(CouldNotPerformException::class, EntryModification::class) + override fun processData( + id: String, + entry: IdentifiableMessage, + entryMap: ProtoBufMessageMap, + registry: ProtoBufRegistry, + ) { + val connectionUnitConfig = entry.message.toBuilder() + + val locationId: String? = try { + getLowestCommonParentLocation(connectionUnitConfig.connectionConfig.tileIdList, locationRegistry)?.id + } catch (ex: CouldNotPerformException) { + ExceptionPrinter.printHistory( + "Could not find parent location for connection [$connectionUnitConfig]", + ex, + logger + ) + null + } ?: locationRegistry.messages.firstOrNull { it.locationConfig.root }?.id + + locationId?.let { + if (locationId != connectionUnitConfig.placementConfig.locationId) { + val placement = connectionUnitConfig.placementConfig.toBuilder().setLocationId(locationId) + throw EntryModification( + entry.setMessage(connectionUnitConfig.setPlacementConfig(placement), this), + this + ) + } + } + } + + companion object { + fun getLowestCommonParentLocation( + locationIds: List, + locationUnitConfigRegistry: ProtoBufFileSynchronizedRegistry, + ): UnitConfig? { + // list containing the paths from root to each location given by locationIds sorted by the lenght of the path, e.g.: + // home, apartment, hallway, entrance + // home, apartment, outdoor + val pathsFromRootMap: MutableList> = ArrayList() + + // fill the list according to the description above + locationIds.forEach { id -> + var locationUnitConfig = locationUnitConfigRegistry.getMessage(id) + val pathFromRootList: MutableList = ArrayList() + pathFromRootList.add(locationUnitConfig) + while (!locationUnitConfig.locationConfig.root) { + locationUnitConfig = + locationUnitConfigRegistry.getMessage(locationUnitConfig.placementConfig.locationId) + // when adding a location at the front of the list, every entry is moved an index further + pathFromRootList.add(0, locationUnitConfig) + } + pathsFromRootMap.add(pathFromRootList) + } + + // sort the list after their sizes: + // home, apartment, outdoor + // home, apartment, hallway, entrance + pathsFromRootMap.sortWith { o1: List, o2: List -> o2.size - o1.size } + + // find the lowest common parent, e.g. for the example above apartment + // by returning the index before the first elements where the paths differ + + // return null in case connection is not linked to any locations + if (pathsFromRootMap.isEmpty()) { + return null; + } + + val shortestPath = pathsFromRootMap[0].size + (0 until shortestPath).forEach { i -> + val currentId = pathsFromRootMap[0][i].id + (1 until pathsFromRootMap.size).forEach { j -> + if (pathsFromRootMap[j][i].id != currentId) { + return pathsFromRootMap[0][i - 1] + } + } + } + + // checking if a lowest common parent exists should not be necessary since a tile cannot be root + return pathsFromRootMap[0][0] + } + } +} diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.java deleted file mode 100644 index b903a922bc..0000000000 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.openbase.bco.registry.unit.core.consistency.connectionconfig; - -/* - * #%L - * BCO Registry Unit Core - * %% - * Copyright (C) 2014 - 2021 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.exception.InvalidStateException; -import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap; -import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler; -import org.openbase.jul.storage.registry.EntryModification; -import org.openbase.jul.storage.registry.ProtoBufFileSynchronizedRegistry; -import org.openbase.jul.storage.registry.ProtoBufRegistry; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.registry.UnitRegistryDataType.UnitRegistryData; -import org.openbase.type.domotic.unit.connection.ConnectionConfigType.ConnectionConfig; -import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig; - -/** - * - * @author Tamino Huxohl - */ -public class ConnectionTilesConsistencyHandler extends AbstractProtoBufRegistryConsistencyHandler { - - private final ProtoBufFileSynchronizedRegistry locationRegistry; - - public ConnectionTilesConsistencyHandler(ProtoBufFileSynchronizedRegistry locationRegistry) { - this.locationRegistry = locationRegistry; - } - - @Override - public void processData(String id, IdentifiableMessage entry, ProtoBufMessageMap entryMap, ProtoBufRegistry registry) throws CouldNotPerformException, EntryModification { - UnitConfig.Builder connectionUnitConfig = entry.getMessage().toBuilder(); - ConnectionConfig.Builder connectionConfig = connectionUnitConfig.getConnectionConfigBuilder(); - - - if (connectionConfig.getTileIdList().size() < 2) { - throw new InvalidStateException("Connections must connect at least 2 tiles which is not true for connection [" + entry.getMessage() + "] which is connecting only "+connectionConfig.getTileIdList().size()+"!"); - } - - boolean modification = false; - connectionConfig.clearTileId(); - // remove duplicated entries and location ids that are not tiles - Map tileIds = new HashMap<>(); - for (String tileId : entry.getMessage().getConnectionConfig().getTileIdList()) { - UnitConfig location = null; - if (locationRegistry.contains(tileId)) { - location = locationRegistry.get(tileId).getMessage(); - } - if (location != null && location.getLocationConfig().hasLocationType() && location.getLocationConfig().getLocationType() == LocationConfig.LocationType.TILE) { - tileIds.put(tileId, tileId); - } else { - modification = true; - } - } - connectionConfig.addAllTileId(new ArrayList<>(tileIds.keySet())); - - if (modification) { - throw new EntryModification(entry.setMessage(connectionUnitConfig, this), this); - } - } -} diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt new file mode 100644 index 0000000000..309ea7a810 --- /dev/null +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt @@ -0,0 +1,42 @@ +package org.openbase.bco.registry.unit.core.consistency.connectionconfig + +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.extension.protobuf.IdentifiableMessage +import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap +import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler +import org.openbase.jul.storage.registry.EntryModification +import org.openbase.jul.storage.registry.ProtoBufFileSynchronizedRegistry +import org.openbase.jul.storage.registry.ProtoBufRegistry +import org.openbase.type.domotic.registry.UnitRegistryDataType +import org.openbase.type.domotic.unit.UnitConfigType +import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType + +/** + * + * @author [Tamino Huxohl](mailto:pleminoq@openbase.org) + */ +class ConnectionTilesConsistencyHandler(private val locationRegistry: ProtoBufFileSynchronizedRegistry) : + AbstractProtoBufRegistryConsistencyHandler() { + @Throws(CouldNotPerformException::class, EntryModification::class) + override fun processData( + id: String, + entry: IdentifiableMessage, + entryMap: ProtoBufMessageMap, + registry: ProtoBufRegistry, + ) { + val connectionUnitConfig = entry.message.toBuilder() + val connectionConfig = connectionUnitConfig.connectionConfigBuilder + + // remove duplicated entries and location ids that are not tiles + entry.message.connectionConfig.tileIdList + .distinct() + .filter { tileId -> locationRegistry[tileId].message?.locationConfig?.locationType == LocationType.TILE } + .let { tileIds -> + if (connectionConfig.tileIdList.toList().sorted() != tileIds.sorted()) { + connectionConfig.clearTileId() + connectionConfig.addAllTileId(tileIds) + throw EntryModification(entry.setMessage(connectionUnitConfig, this), this) + } + } + } +} From e67c3e945dd2869f916befa8e29eaab185b1e4da Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 9 Jan 2024 20:39:25 +0100 Subject: [PATCH 54/70] fix flaky location type detection test. --- .../bco/registry/lib/util/LocationUtils.kt | 17 +++-- .../ConnectionLocationConsistencyHandler.kt | 28 +++++--- .../ConnectionTilesConsistencyHandler.kt | 9 ++- .../LocationTypeConsistencyHandler.java | 67 ------------------- .../LocationTypeConsistencyHandler.kt | 59 ++++++++++++++++ .../unit/test/LocationRegistryTest.java | 24 +++---- 6 files changed, 103 insertions(+), 101 deletions(-) delete mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.java create mode 100644 module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.kt diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt index cebb59fcca..2f3d351349 100644 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt @@ -110,6 +110,7 @@ object LocationUtils { } } + @JvmStatic @Throws(CouldNotPerformException::class) fun computeNewRootLocation( currentLocationConfig: UnitConfigType.UnitConfig, @@ -171,7 +172,7 @@ object LocationUtils { @Throws(CouldNotPerformException::class) fun detectLocationType( locationUnit: UnitConfigType.UnitConfig, - locationRegistry: ProtoBufRegistry, + locationRegistry: ProtoBufRegistry, ): LocationType { try { if (!locationUnit.hasPlacementConfig()) { @@ -187,10 +188,11 @@ object LocationUtils { val parentLocationType = locationRegistry[locationUnit.placementConfig.locationId].message.locationConfig.locationType - val childLocationTypes: MutableSet = HashSet() - for (childId in locationUnit.locationConfig.childIdList) { - childLocationTypes.add(locationRegistry[childId].message.locationConfig.locationType) - } + + val childLocationTypes: List = locationUnit.locationConfig.childIdList + .map { childId -> locationRegistry[childId].message.locationConfig.locationType } + .distinct() + .filter { it != LocationType.UNKNOWN } return detectLocationType(parentLocationType, childLocationTypes) } catch (ex: CouldNotPerformException) { @@ -201,7 +203,7 @@ object LocationUtils { /** * Detect the type of a location. * Since a location, except the root location, always has a parent the only ambiguous case - * is when the parent is a zone but no children are defined. Than the location can either be + * is when the parent is a zone but no children are defined. Then the location can either be * a zone or a tile. In this case an exception is thrown. * * @param parentLocationType the type of the parent location of the location whose type is detected @@ -209,10 +211,11 @@ object LocationUtils { * @return the type locationUnit should have * @throws CouldNotPerformException if the type is ambiguous */ + @JvmStatic @Throws(CouldNotPerformException::class) private fun detectLocationType( parentLocationType: LocationType, - childLocationTypes: Set, + childLocationTypes: List, ): LocationType { when (parentLocationType) { diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt index 7606e11fdf..5a1b8f7e4d 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionLocationConsistencyHandler.kt @@ -1,6 +1,7 @@ package org.openbase.bco.registry.unit.core.consistency.connectionconfig import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.NotAvailableException import org.openbase.jul.exception.printer.ExceptionPrinter import org.openbase.jul.extension.protobuf.IdentifiableMessage import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap @@ -61,18 +62,23 @@ class ConnectionLocationConsistencyHandler( val pathsFromRootMap: MutableList> = ArrayList() // fill the list according to the description above - locationIds.forEach { id -> - var locationUnitConfig = locationUnitConfigRegistry.getMessage(id) - val pathFromRootList: MutableList = ArrayList() - pathFromRootList.add(locationUnitConfig) - while (!locationUnitConfig.locationConfig.root) { - locationUnitConfig = - locationUnitConfigRegistry.getMessage(locationUnitConfig.placementConfig.locationId) - // when adding a location at the front of the list, every entry is moved an index further - pathFromRootList.add(0, locationUnitConfig) + locationIds + .filter { locationUnitConfigRegistry.contains(it) } + .forEach { id -> + var locationUnitConfig = locationUnitConfigRegistry.getMessage(id) + val pathFromRootList: MutableList = ArrayList() + pathFromRootList.add(locationUnitConfig) + while (!locationUnitConfig.locationConfig.root) { + locationUnitConfig = try { + locationUnitConfigRegistry.getMessage(locationUnitConfig.placementConfig.locationId) + } catch (ex: NotAvailableException) { + continue + } + // when adding a location at the front of the list, every entry is moved an index further + pathFromRootList.add(0, locationUnitConfig) + } + pathsFromRootMap.add(pathFromRootList) } - pathsFromRootMap.add(pathFromRootList) - } // sort the list after their sizes: // home, apartment, outdoor diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt index 309ea7a810..1effe370a6 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/connectionconfig/ConnectionTilesConsistencyHandler.kt @@ -1,6 +1,7 @@ package org.openbase.bco.registry.unit.core.consistency.connectionconfig import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.NotAvailableException import org.openbase.jul.extension.protobuf.IdentifiableMessage import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler @@ -30,7 +31,13 @@ class ConnectionTilesConsistencyHandler(private val locationRegistry: ProtoBufFi // remove duplicated entries and location ids that are not tiles entry.message.connectionConfig.tileIdList .distinct() - .filter { tileId -> locationRegistry[tileId].message?.locationConfig?.locationType == LocationType.TILE } + .filter { tileId -> + try { + locationRegistry[tileId].message?.locationConfig?.locationType == LocationType.TILE + } catch (ex: NotAvailableException) { + false + } + } .let { tileIds -> if (connectionConfig.tileIdList.toList().sorted() != tileIds.sorted()) { connectionConfig.clearTileId() diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.java deleted file mode 100644 index aeb9a02eae..0000000000 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.openbase.bco.registry.unit.core.consistency.locationconfig; - -/*- - * #%L - * BCO Registry Unit Core - * %% - * Copyright (C) 2014 - 2021 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU 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 Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * . - * #L% - */ -import org.openbase.bco.registry.lib.util.LocationUtils; -import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.exception.printer.ExceptionPrinter; -import org.openbase.jul.exception.printer.LogLevel; -import org.openbase.jul.extension.protobuf.IdentifiableMessage; -import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap; -import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler; -import org.openbase.jul.storage.registry.EntryModification; -import org.openbase.jul.storage.registry.ProtoBufRegistry; -import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; -import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig; -import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType; - -/** - * - * @author Tamino Huxohl - */ -public class LocationTypeConsistencyHandler extends AbstractProtoBufRegistryConsistencyHandler { - - @Override - public void processData(String id, IdentifiableMessage entry, ProtoBufMessageMap entryMap, ProtoBufRegistry registry) throws CouldNotPerformException, EntryModification { - UnitConfig.Builder locationUnit = entry.getMessage().toBuilder(); - LocationConfig.Builder locationConfig = locationUnit.getLocationConfigBuilder(); - - if (!locationConfig.hasLocationType()) { - try { - locationConfig.setLocationType(LocationUtils.detectLocationType(entry.getMessage(), registry)); - throw new EntryModification(entry.setMessage(locationUnit, this), this); - } catch (CouldNotPerformException ex) { - throw new CouldNotPerformException("The locationType of location[" + locationUnit.getLabel() + "] has to be defined manually", ex); - } - } else { - try { - LocationType detectedType = LocationUtils.detectLocationType(entry.getMessage(), registry); - if (detectedType != locationConfig.getLocationType()) { - locationConfig.setLocationType(detectedType); - throw new EntryModification(entry.setMessage(locationUnit, this), this); - } - } catch (CouldNotPerformException ex) { - ExceptionPrinter.printHistory("Could not detect locationType for location[" + locationUnit.getLabel() + "] with current type [" + locationConfig.getLocationType().name() + "]", ex, logger, LogLevel.DEBUG); - } - } - } -} diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.kt b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.kt new file mode 100644 index 0000000000..8313e6ddd9 --- /dev/null +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/consistency/locationconfig/LocationTypeConsistencyHandler.kt @@ -0,0 +1,59 @@ +package org.openbase.bco.registry.unit.core.consistency.locationconfig + +import org.openbase.bco.registry.lib.util.LocationUtils +import org.openbase.jul.exception.CouldNotPerformException +import org.openbase.jul.exception.printer.ExceptionPrinter +import org.openbase.jul.exception.printer.LogLevel +import org.openbase.jul.extension.protobuf.IdentifiableMessage +import org.openbase.jul.extension.protobuf.container.ProtoBufMessageMap +import org.openbase.jul.storage.registry.AbstractProtoBufRegistryConsistencyHandler +import org.openbase.jul.storage.registry.EntryModification +import org.openbase.jul.storage.registry.ProtoBufRegistry +import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig +import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType + +/** + * + * @author [Tamino Huxohl](mailto:pleminoq@openbase.org) + */ +class LocationTypeConsistencyHandler : + AbstractProtoBufRegistryConsistencyHandler() { + @Throws(CouldNotPerformException::class, EntryModification::class) + override fun processData( + id: String, + entry: IdentifiableMessage, + entryMap: ProtoBufMessageMap, + registry: ProtoBufRegistry, + ) { + val locationUnit = entry.message.toBuilder() + val locationConfig = locationUnit.locationConfigBuilder + + val detectedType: LocationType = LocationUtils.detectLocationType(entry.message, registry) + + if (!locationConfig.hasLocationType()) { + try { + locationConfig.setLocationType(detectedType) + throw EntryModification(entry.setMessage(locationUnit, this), this) + } catch (ex: CouldNotPerformException) { + throw CouldNotPerformException( + "The locationType of location[" + locationUnit.label + "] has to be defined manually", + ex + ) + } + } else { + try { + if (detectedType != locationConfig.locationType) { + locationConfig.setLocationType(detectedType) + throw EntryModification(entry.setMessage(locationUnit, this), this) + } + } catch (ex: CouldNotPerformException) { + ExceptionPrinter.printHistory( + "Could not detect locationType for location[" + locationUnit.label + "] with current type [" + locationConfig.locationType.name + "]", + ex, + logger, + LogLevel.DEBUG + ) + } + } + } +} diff --git a/module/registry/unit-registry/test/src/test/java/org/openbase/bco/registry/unit/test/LocationRegistryTest.java b/module/registry/unit-registry/test/src/test/java/org/openbase/bco/registry/unit/test/LocationRegistryTest.java index 943f9011b1..e854c29b85 100644 --- a/module/registry/unit-registry/test/src/test/java/org/openbase/bco/registry/unit/test/LocationRegistryTest.java +++ b/module/registry/unit-registry/test/src/test/java/org/openbase/bco/registry/unit/test/LocationRegistryTest.java @@ -210,19 +210,9 @@ public void testConnectionTilesConsistency() throws Exception { // create a connection with only one tile id UnitConfig.Builder failingConnectionConfig = getConnectionUnitBuilder("Failing connection"); failingConnectionConfig.getConnectionConfigBuilder().setConnectionType(ConnectionType.DOOR).addTileId(tile1.getId()); - try { - // set exception printer to quit because an exception is expected - ExceptionPrinter.setBeQuit(Boolean.TRUE); - // try to register the connection which should fail - Registries.getUnitRegistry().registerUnitConfig(failingConnectionConfig.build()).get(); - // fail of no exception has been thrown - fail("Registered connection with less than one tile"); - } catch (ExecutionException ex) { - // if an execution exception is thrown the connection could not be registered - } finally { - // reset quit flag from exception printer - ExceptionPrinter.setBeQuit(Boolean.FALSE); - } + // register the connection which should lead to a single tile of the connection + var result = Registries.getUnitRegistry().registerUnitConfig(failingConnectionConfig.build()).get(); + assertEquals(result.getConnectionConfig().getTileIdCount(), 1); // create a new connection with duplicated and fake tile ids UnitConfig.Builder connection = getConnectionUnitBuilder("Test Connection"); @@ -264,7 +254,10 @@ public void testLocationTypeConsistency() throws Exception { assertEquals(LocationType.ZONE, root.getLocationConfig().getLocationType(), "Location type zone has not been recovered for root location"); // register a tile - UnitConfig.Builder tile = Registries.getUnitRegistry().registerUnitConfig(getLocationUnitBuilder(LocationType.TILE, "Tile", root.getId()).build()).get().toBuilder(); + UnitConfig.Builder tile = Registries.getUnitRegistry().registerUnitConfig(getLocationUnitBuilder(LocationType.UNKNOWN, "Tile", root.getId()).build()).get().toBuilder(); + + // make sure that the location has been identified as tile + assertEquals(LocationType.TILE, tile.getLocationConfig().getLocationType(), "Type has not been detected for tile"); // register a location under a tile, therefore it should be inferred to be a region UnitConfig.Builder region = getLocationUnitBuilder("Region"); @@ -274,8 +267,9 @@ public void testLocationTypeConsistency() throws Exception { // now the tile has a zone as its parent and a region as its child, therefore the consistency handler should be // able to recover its type - tile.getLocationConfigBuilder().setLocationType(LocationType.ZONE); + tile.getLocationConfigBuilder().setLocationType(LocationType.UNKNOWN); tile = Registries.getUnitRegistry().updateUnitConfig(tile.build()).get().toBuilder(); + assertFalse(tile.getLocationConfig().getRoot(), "Should not be the new root location"); assertEquals(LocationType.TILE, tile.getLocationConfig().getLocationType(), "Type of tile has not been recovered"); } From 3c687dce3fca8c8ba87845a050e5dd8c61d1cb67 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 9 Jan 2024 21:22:44 +0100 Subject: [PATCH 55/70] fix typos and fix canDo bug. --- .../AbstractAuthorizedBaseUnitController.java | 4 ++-- .../bco/registry/lib/util/LocationUtils.kt | 2 +- .../unit/core/UnitRegistryController.java | 2 +- .../unit/lib/auth/AuthorizationWithTokenHelper.kt | 15 +++++++-------- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java index be8d587f77..674639d4c5 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/AbstractAuthorizedBaseUnitController.java @@ -170,7 +170,7 @@ protected RemoteAction observe(final Future futureAction) { // register new action observedTaskList.add(remoteAction); - // validate and initiate forced stop if instance generates to many messages. + // validate and initiate forced stop if instance generates too many messages. validateUnitOverload(); return remoteAction; @@ -200,7 +200,7 @@ private synchronized void validateUnitOverload() { } if (eventsPerHour > MAX_ACTIN_SUBMITTION_PER_MINUTE * 60) { - logger.error(this + " generates to many actions and will be terminated!"); + logger.error(this + " generates too many actions and will be terminated!"); try { applyServiceState(ActivationState.newBuilder().setValue(State.INACTIVE), ServiceType.ACTIVATION_STATE_SERVICE); } catch (CouldNotPerformException ex) { diff --git a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt index 2f3d351349..37050e2f4d 100644 --- a/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt +++ b/module/registry/lib/src/main/java/org/openbase/bco/registry/lib/util/LocationUtils.kt @@ -151,7 +151,7 @@ object LocationUtils { return Optional.empty().get() } - throw InvalidStateException("To many potential root locations detected!") + throw InvalidStateException("Too many potential root locations detected!") } catch (ex: CouldNotPerformException) { throw CouldNotPerformException("Could not compute root location!", ex) } diff --git a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java index a6172793eb..af83257003 100644 --- a/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java +++ b/module/registry/unit-registry/core/src/main/java/org/openbase/bco/registry/unit/core/UnitRegistryController.java @@ -183,7 +183,7 @@ protected void postInit() throws InitializationException, InterruptedException { // post init loads registries super.postInit(); - // initially fill the alias to id map afterward + // initially fill the alias to id map afterwards // the {@code AliasMapUpdatePlugin} will manage changes on registering, removing or updating of units synchronized (aliasIdMapLock) { try { diff --git a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt index a5ec0590af..277f805fc7 100644 --- a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt +++ b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt @@ -131,10 +131,9 @@ object AuthorizationWithTokenHelper { userMessage: UserMessageType.UserMessage, permissionType: AuthorizationHelper.PermissionType, unitRegistry: UnitRegistry, - ): AuthPair { - try { + ): AuthPair = try { // validate sender - return canDo( + canDo( authenticationBaseData, unitRegistry.getUnitConfigById(userMessage.senderId), permissionType, @@ -159,9 +158,9 @@ object AuthorizationWithTokenHelper { } catch (exx: CouldNotPerformException) { exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) - userMessage.conditionList.forEach { condition -> + userMessage.conditionList.firstNotNullOfOrNull { condition -> try { - return canDo( + canDo( authenticationBaseData, unitRegistry.getUnitConfigById(condition.unitId), permissionType, @@ -172,13 +171,13 @@ object AuthorizationWithTokenHelper { } catch (exxx: CouldNotPerformException) { exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) + null } } - MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) + null } - } - throw FatalImplementationErrorException( + } ?: throw FatalImplementationErrorException( "ExceptionStack empty in error case.", AuthorizationWithTokenHelper::class.java ) From 2ea505411dd5685eb4288d7ee6d40bda6a09d91c Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 9 Jan 2024 22:18:04 +0100 Subject: [PATCH 56/70] make code compile again --- .../lib/auth/AuthorizationWithTokenHelper.kt | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt index 277f805fc7..8babea995f 100644 --- a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt +++ b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt @@ -131,57 +131,56 @@ object AuthorizationWithTokenHelper { userMessage: UserMessageType.UserMessage, permissionType: AuthorizationHelper.PermissionType, unitRegistry: UnitRegistry, - ): AuthPair = try { - // validate sender + ): AuthPair = try { + // validate sender + canDo( + authenticationBaseData, + unitRegistry.getUnitConfigById(userMessage.senderId), + permissionType, + unitRegistry, + null, + null + ) + } catch (ex: CouldNotPerformException) { + var exceptionStack: ExceptionStack? = null + exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, ex, exceptionStack) + + // validate receiver if sender validation failed. + try { canDo( authenticationBaseData, - unitRegistry.getUnitConfigById(userMessage.senderId), + unitRegistry.getUnitConfigById(userMessage.recipientId), permissionType, unitRegistry, null, null ) - } catch (ex: CouldNotPerformException) { - var exceptionStack: ExceptionStack? = null - exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, ex, exceptionStack) - - // validate receiver if sender validation failed. - try { - canDo( - authenticationBaseData, - unitRegistry.getUnitConfigById(userMessage.recipientId), - permissionType, - unitRegistry, - null, - null - ) - } catch (exx: CouldNotPerformException) { - exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) + } catch (exx: CouldNotPerformException) { + exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) - userMessage.conditionList.firstNotNullOfOrNull { condition -> - try { - canDo( - authenticationBaseData, - unitRegistry.getUnitConfigById(condition.unitId), - permissionType, - unitRegistry, - null, - null - ) - } catch (exxx: CouldNotPerformException) { - exceptionStack = - MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) + userMessage.conditionList.firstNotNullOfOrNull { condition -> + try { + canDo( + authenticationBaseData, + unitRegistry.getUnitConfigById(condition.unitId), + permissionType, + unitRegistry, + null, null - } + ) + } catch (exxx: CouldNotPerformException) { + exceptionStack = + MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) + null } - MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) - null } - } ?: throw FatalImplementationErrorException( - "ExceptionStack empty in error case.", - AuthorizationWithTokenHelper::class.java - ) - } + MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) + null + } + } ?: throw FatalImplementationErrorException( + "ExceptionStack empty in error case.", + AuthorizationWithTokenHelper::class.java + ) /** * Perform a permission check for authentication data including tokens. From 2d9daa987853da78301d2970846f499ddf6f884a Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 9 Jan 2024 22:19:38 +0100 Subject: [PATCH 57/70] link against latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 73c837a361..5ca4c67f44 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 73c837a3617e3490a87f6acc88a72e4f7099aed6 +Subproject commit 5ca4c67f442f071fd32aa576e61b3f324e603155 From a43692dbb34e072d24c90d3b2094b252b598c774 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 16 Jan 2024 22:10:12 +0100 Subject: [PATCH 58/70] upgrade jul --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 5ca4c67f44..6eedd195d5 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 5ca4c67f442f071fd32aa576e61b3f324e603155 +Subproject commit 6eedd195d56944a6a79bce8b22df24ebc96be6e1 From 3cbc1445db21c4b7611142cdf1c0e2b299468a01 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 13 Feb 2024 19:19:49 +0100 Subject: [PATCH 59/70] link against latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 6eedd195d5..6265f90b1b 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 6eedd195d56944a6a79bce8b22df24ebc96be6e1 +Subproject commit 6265f90b1b6f96ccb8ae489d06151a85712d308f From 7d65dcd0317c9600f3a9b12c8fa60a06b8b54069 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 13 Feb 2024 19:28:04 +0100 Subject: [PATCH 60/70] make docker image build tag configurable --- build-docker.sh | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/build-docker.sh b/build-docker.sh index 32828cd711..076f1c9a58 100755 --- a/build-docker.sh +++ b/build-docker.sh @@ -10,10 +10,13 @@ WHITE='\033[0;37m' APP_NAME='docker images' APP_NAME=${BLUE}${APP_NAME}${NC} -echo -e "=== ${APP_NAME} build docker image...${WHITE}${NC}" && -docker build -f docker/Dockerfile -t openbaseorg/bco:local . -docker build -f docker/device-manager/openhab/Dockerfile -t openbaseorg/bco-device-manager-openhab:local --build-arg BCO_BASE_IMAGE_VERSION=local docker/device-manager/openhab -docker build -f docker/bco-demo/Dockerfile -t openbaseorg/bco-demo:local --build-arg BCO_BASE_IMAGE_VERSION=local docker/bco-demo +IMAGE_TAG=${1:-local} + +echo -e "=== ${APP_NAME} build docker image...${WHITE}${NC}" + +docker build -f docker/Dockerfile -t openbaseorg/bco:${IMAGE_TAG} . +docker build -f docker/device-manager/openhab/Dockerfile -t openbaseorg/bco-device-manager-openhab:${IMAGE_TAG} --build-arg BCO_BASE_IMAGE_VERSION=${IMAGE_TAG} docker/device-manager/openhab +docker build -f docker/bco-demo/Dockerfile -t openbaseorg/bco-demo:${IMAGE_TAG} --build-arg BCO_BASE_IMAGE_VERSION=${IMAGE_TAG} docker/bco-demo # use this for debugging purpose: DOCKER_BUILDKIT=0 docker build -f docker/Dockerfile --progress=plain . echo -e "=== ${APP_NAME} were ${GREEN}successfully${NC} build.${NC}" From 733aea012cb818f66c77ae83764d4404a001ea95 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 13 Feb 2024 19:37:52 +0100 Subject: [PATCH 61/70] make compatible with latest jul version --- module/dal/visual/build.gradle.kts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/module/dal/visual/build.gradle.kts b/module/dal/visual/build.gradle.kts index 464c0ba09b..27d69a460b 100644 --- a/module/dal/visual/build.gradle.kts +++ b/module/dal/visual/build.gradle.kts @@ -1,5 +1,17 @@ plugins { id("org.openbase.bco") + id("org.openjfx.javafxplugin") version "0.1.0" +} + +javafx { + version = "21.0.1" + modules = listOf( + "javafx.base", + "javafx.graphics", + "javafx.media", + "javafx.controls", + "javafx.fxml" + ) } dependencies { From 3df18d86603b6018ae3a1ebd06b9a9d33a1ba0db Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 13 Feb 2024 19:39:42 +0100 Subject: [PATCH 62/70] link against latest jul version --- versions.properties | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/versions.properties b/versions.properties index c4377848b5..34b241fe1c 100644 --- a/versions.properties +++ b/versions.properties @@ -47,9 +47,8 @@ version.com.google.guava..guava=28.0-jre ## # available=31.0-jre ## # available=31.0.1-android ## # available=31.0.1-jre - -version.org.openbase..jul.communication.mqtt.test=3.3-SNAPSHOT -version.org.openbase..jul.transformation=3.3-SNAPSHOT +version.org.openbase..jul.communication.mqtt.test=3.6-SNAPSHOT +version.org.openbase..jul.transformation=3.6-SNAPSHOT version.org.testcontainers..junit-jupiter=1.18.3 version.org.testcontainers..testcontainers=1.18.3 version.org.springframework.boot..spring-boot-starter-webflux=3.1.2 @@ -60,59 +59,59 @@ version.org.openhab.core.bundles..org.openhab.core.io.rest.core=4.0.4 plugin.org.springframework.boot=3.1.2 plugin.io.spring.dependency-management=1.1.2 version.kotlin=1.9.0 -version.org.openbase..jul.communication.controller=3.3-SNAPSHOT +version.org.openbase..jul.communication.controller=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.communication.mqtt=3.3-SNAPSHOT +version.org.openbase..jul.communication.mqtt=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.exception=3.3-SNAPSHOT +version.org.openbase..jul.exception=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.extension.protobuf=3.3-SNAPSHOT +version.org.openbase..jul.extension.protobuf=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.extension.type.processing=3.3-SNAPSHOT +version.org.openbase..jul.extension.type.processing=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.extension.type.storage=3.3-SNAPSHOT +version.org.openbase..jul.extension.type.storage=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.extension.type.transform=3.3-SNAPSHOT +version.org.openbase..jul.extension.type.transform=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.extension.type.util=3.3-SNAPSHOT +version.org.openbase..jul.extension.type.util=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.pattern.launch=3.3-SNAPSHOT +version.org.openbase..jul.pattern.launch=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.pattern.trigger=3.3-SNAPSHOT +version.org.openbase..jul.pattern.trigger=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.processing=3.3-SNAPSHOT +version.org.openbase..jul.processing=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.storage=3.3-SNAPSHOT +version.org.openbase..jul.storage=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.visual.javafx=3.3-SNAPSHOT +version.org.openbase..jul.visual.javafx=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 -version.org.openbase..jul.visual.swing=3.3-SNAPSHOT +version.org.openbase..jul.visual.swing=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1 ## # available=3.0.2 From c906ea13d2126dae28ac0d2a4e60fd2756bc8df6 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 20 Feb 2024 19:48:54 +0100 Subject: [PATCH 63/70] link against latest jul version that hopefully fixes the registry startup race condition. --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 6265f90b1b..9a023a6171 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 6265f90b1b6f96ccb8ae489d06151a85712d308f +Subproject commit 9a023a617137d20596af504db9409cd92d3bf2ba From 96b514cecbb0d294ff4733cff261a37fbadb5989 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 20 Feb 2024 21:53:09 +0100 Subject: [PATCH 64/70] implement userMessage subscription. --- .../BcoGraphQlApiSpringBootApplication.kt | 5 ++ .../MessageRegistrySubscriptionObserver.kt | 50 +++++++++++++ .../subscriptions/SubscriptionModule.kt | 72 +++++++------------ .../UnitRegistrySubscriptionObserver.kt | 57 +++++++++++++++ 4 files changed, 139 insertions(+), 45 deletions(-) create mode 100644 module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/MessageRegistrySubscriptionObserver.kt create mode 100644 module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/UnitRegistrySubscriptionObserver.kt diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt index 6cb6233d6e..5178bd060e 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt @@ -122,6 +122,11 @@ open class BcoGraphQlApiSpringBootApplication { } SubscriptionModule.subscribeUnitConfigs(unitFilter, includeDisabledUnits) }) + .dataFetcher( + FieldCoordinates.coordinates("Subscription", "userMessages"), + DataFetcher { + SubscriptionModule.subscribeUserMessages() + }) .build() return GraphQLSchema.newSchema(schema) .subscription(builder.build()) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/MessageRegistrySubscriptionObserver.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/MessageRegistrySubscriptionObserver.kt new file mode 100644 index 0000000000..c4793c4358 --- /dev/null +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/MessageRegistrySubscriptionObserver.kt @@ -0,0 +1,50 @@ +package org.openbase.bco.api.graphql.subscriptions + +import com.google.common.collect.ImmutableList +import org.openbase.bco.api.graphql.error.ServerError +import org.openbase.bco.api.graphql.schema.RegistrySchemaModule +import org.openbase.bco.registry.remote.Registries +import org.openbase.jul.pattern.provider.DataProvider +import org.openbase.type.domotic.communication.UserMessageType +import org.openbase.type.domotic.registry.MessageRegistryDataType + +class MessageRegistrySubscriptionObserver() : + AbstractObserverMapper, MessageRegistryDataType.MessageRegistryData, List>() { + private val userMessages: MutableList + + init { + Registries.getMessageRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ) + userMessages = ArrayList( + RegistrySchemaModule.getUserMessages() + ) + } + + @Throws(Exception::class) + override fun update( + source: DataProvider, + target: MessageRegistryDataType.MessageRegistryData, + ) { + val newUserMessages: ImmutableList = + RegistrySchemaModule.getUserMessages() + if (newUserMessages == userMessages) { + // nothing has changed + return + } + + // store update + userMessages.clear() + userMessages.addAll(newUserMessages) + super.update(source, target) + } + + @Throws(Exception::class) + override fun mapData( + source: DataProvider, + data: MessageRegistryDataType.MessageRegistryData, + ): List { + return userMessages + } +} diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt index 26fadb2a03..3bb7b0e822 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/SubscriptionModule.kt @@ -1,12 +1,10 @@ package org.openbase.bco.api.graphql.subscriptions -import com.google.common.collect.ImmutableList import com.google.protobuf.Message import io.reactivex.BackpressureStrategy import org.openbase.bco.api.graphql.error.BCOGraphQLError import org.openbase.bco.api.graphql.error.GenericError import org.openbase.bco.api.graphql.error.ServerError -import org.openbase.bco.api.graphql.schema.RegistrySchemaModule import org.openbase.bco.dal.lib.layer.unit.Unit import org.openbase.bco.dal.lib.layer.unit.UnitRemote import org.openbase.bco.dal.remote.layer.unit.CustomUnitPool @@ -15,6 +13,8 @@ import org.openbase.jul.exception.CouldNotPerformException import org.openbase.jul.extension.protobuf.ProtoBufBuilderProcessor.merge import org.openbase.jul.pattern.Observer import org.openbase.jul.pattern.provider.DataProvider +import org.openbase.type.domotic.communication.UserMessageType.UserMessage +import org.openbase.type.domotic.registry.MessageRegistryDataType.MessageRegistryData import org.openbase.type.domotic.registry.UnitRegistryDataType.UnitRegistryData import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig import org.openbase.type.domotic.unit.UnitDataType @@ -42,7 +42,8 @@ import org.slf4j.LoggerFactory * License along with this program. If not, see * . * #L% - */ object SubscriptionModule { + */ +object SubscriptionModule { private val log = LoggerFactory.getLogger(SubscriptionModule::class.java) //TODO: what is a good strategy here @@ -94,7 +95,7 @@ import org.slf4j.LoggerFactory includeDisabledUnits: Boolean, ): Publisher> { return try { - val observer = RegistrySubscriptionObserver(unitFilter, includeDisabledUnits) + val observer = UnitRegistrySubscriptionObserver(unitFilter, includeDisabledUnits) val unitRegistry = Registries.getUnitRegistry( ServerError.BCO_TIMEOUT_SHORT, ServerError.BCO_TIMEOUT_TIME_UNIT @@ -119,50 +120,31 @@ import org.slf4j.LoggerFactory } } - class RegistrySubscriptionObserver( - private val unitFilter: UnitFilter, - private val includeDisabledUnits: Boolean, - ) : AbstractObserverMapper, UnitRegistryData, List>() { - private val unitConfigs: MutableList - - init { - Registries.getUnitRegistry( + @Throws(BCOGraphQLError::class) + fun subscribeUserMessages(): Publisher> { + return try { + val observer = MessageRegistrySubscriptionObserver() + val messageRegistry = Registries.getMessageRegistry( ServerError.BCO_TIMEOUT_SHORT, ServerError.BCO_TIMEOUT_TIME_UNIT ) - unitConfigs = ArrayList( - RegistrySchemaModule.getUnitConfigs( - unitFilter, includeDisabledUnits - ) - ) - } - - @Throws(Exception::class) - override fun update( - source: DataProvider, - target: UnitRegistryData, - ) { - val newUnitConfigs: ImmutableList = - RegistrySchemaModule.getUnitConfigs( - unitFilter, includeDisabledUnits - ) - if (newUnitConfigs == unitConfigs) { - // nothing has changed - return - } - - // store update - unitConfigs.clear() - unitConfigs.addAll(newUnitConfigs) - super.update(source, target) - } - - @Throws(Exception::class) - override fun mapData( - source: DataProvider, - data: UnitRegistryData, - ): List { - return unitConfigs + AbstractObserverMapper.createObservable( + { observer: Observer, MessageRegistryData> -> + messageRegistry.addDataObserver( + observer + ) + }, + { observer: Observer, MessageRegistryData> -> + messageRegistry.removeDataObserver(observer) + }, + observer + ).toFlowable(BACKPRESSURE_STRATEGY) + } catch (ex: RuntimeException) { + throw GenericError(ex) + } catch (ex: CouldNotPerformException) { + throw GenericError(ex) + } catch (ex: InterruptedException) { + throw GenericError(ex) } } } diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/UnitRegistrySubscriptionObserver.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/UnitRegistrySubscriptionObserver.kt new file mode 100644 index 0000000000..4eea1a30ec --- /dev/null +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/subscriptions/UnitRegistrySubscriptionObserver.kt @@ -0,0 +1,57 @@ +package org.openbase.bco.api.graphql.subscriptions + +import com.google.common.collect.ImmutableList +import org.openbase.bco.api.graphql.error.ServerError +import org.openbase.bco.api.graphql.schema.RegistrySchemaModule +import org.openbase.bco.registry.remote.Registries +import org.openbase.jul.pattern.provider.DataProvider +import org.openbase.type.domotic.registry.UnitRegistryDataType +import org.openbase.type.domotic.unit.UnitConfigType +import org.openbase.type.domotic.unit.UnitFilterType + +class UnitRegistrySubscriptionObserver( + private val unitFilter: UnitFilterType.UnitFilter, + private val includeDisabledUnits: Boolean, +) : AbstractObserverMapper, UnitRegistryDataType.UnitRegistryData, List>() { + private val unitConfigs: MutableList + + init { + Registries.getUnitRegistry( + ServerError.BCO_TIMEOUT_SHORT, + ServerError.BCO_TIMEOUT_TIME_UNIT + ) + unitConfigs = ArrayList( + RegistrySchemaModule.getUnitConfigs( + unitFilter, includeDisabledUnits + ) + ) + } + + @Throws(Exception::class) + override fun update( + source: DataProvider, + target: UnitRegistryDataType.UnitRegistryData, + ) { + val newUnitConfigs: ImmutableList = + RegistrySchemaModule.getUnitConfigs( + unitFilter, includeDisabledUnits + ) + if (newUnitConfigs == unitConfigs) { + // nothing has changed + return + } + + // store update + unitConfigs.clear() + unitConfigs.addAll(newUnitConfigs) + super.update(source, target) + } + + @Throws(Exception::class) + override fun mapData( + source: DataProvider, + data: UnitRegistryDataType.UnitRegistryData, + ): List { + return unitConfigs + } +} From 37914dda2db6b2415e92f1e06ecf7e129a074bb0 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 20 Feb 2024 21:53:45 +0100 Subject: [PATCH 65/70] fix test issues --- .../preset/agent/PresenceLightAgentTest.java | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/module/app/preset/src/test/java/org/openbase/bco/app/preset/agent/PresenceLightAgentTest.java b/module/app/preset/src/test/java/org/openbase/bco/app/preset/agent/PresenceLightAgentTest.java index c3c3be0b05..cb88183305 100644 --- a/module/app/preset/src/test/java/org/openbase/bco/app/preset/agent/PresenceLightAgentTest.java +++ b/module/app/preset/src/test/java/org/openbase/bco/app/preset/agent/PresenceLightAgentTest.java @@ -10,24 +10,21 @@ * it under the terms of the GNU 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 Public License for more details. - * + * * You should have received a copy of the GNU General Public * License along with this program. If not, see * . * #L% */ -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.openbase.app.test.agent.AbstractBCOAgentManagerTest; -import org.junit.jupiter.api.Test; import org.openbase.bco.dal.control.layer.unit.LightSensorController; import org.openbase.bco.dal.control.layer.unit.MotionDetectorController; import org.openbase.bco.dal.lib.state.States; @@ -39,29 +36,26 @@ import org.openbase.bco.dal.remote.layer.unit.Units; import org.openbase.bco.dal.remote.layer.unit.location.LocationRemote; import org.openbase.bco.dal.remote.layer.unit.util.UnitStateAwaiter; -import org.openbase.bco.dal.visual.action.BCOActionInspector; import org.openbase.bco.registry.mock.MockRegistry; -import org.openbase.jps.core.JPService; -import org.openbase.jps.preset.JPDebugMode; -import org.openbase.jps.preset.JPVerbose; import org.openbase.jul.exception.CouldNotPerformException; -import org.openbase.jul.exception.NotAvailableException; import org.openbase.jul.extension.type.processing.MultiLanguageTextProcessor; import org.openbase.type.domotic.action.ActionDescriptionType.ActionDescription; import org.openbase.type.domotic.action.ActionPriorityType.ActionPriority.Priority; -import org.openbase.type.domotic.state.IlluminanceStateType.IlluminanceState; -import org.openbase.type.domotic.state.MotionStateType.MotionState.State; -import org.openbase.type.domotic.unit.dal.LightSensorDataType.LightSensorData; -import org.slf4j.LoggerFactory; import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServiceType; +import org.openbase.type.domotic.state.IlluminanceStateType.IlluminanceState; import org.openbase.type.domotic.state.MotionStateType.MotionState; +import org.openbase.type.domotic.state.MotionStateType.MotionState.State; import org.openbase.type.domotic.state.PowerStateType.PowerState; import org.openbase.type.domotic.state.PresenceStateType.PresenceState; import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType; import org.openbase.type.domotic.unit.dal.ColorableLightDataType.ColorableLightData; +import org.openbase.type.domotic.unit.dal.LightSensorDataType.LightSensorData; import org.openbase.type.domotic.unit.dal.MotionDetectorDataType.MotionDetectorData; import org.openbase.type.domotic.unit.location.LocationDataType.LocationData; +import org.slf4j.LoggerFactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; /** * * @author Timo Michalski @@ -88,12 +82,12 @@ public class PresenceLightAgentTest extends AbstractBCOAgentManagerTest { //@BeforeAll //uncomment to enable debug mode public static void showActionInspector() throws Throwable { - JPService.registerProperty(JPDebugMode.class, true); - JPService.registerProperty(JPVerbose.class, true); + // JPService.registerProperty(JPDebugMode.class, true); + // JPService.registerProperty(JPVerbose.class, true); // uncomment to visualize action inspector during tests - String[] args = {}; - new Thread(() -> BCOActionInspector.main(args)).start(); + // String[] args = {}; + // new Thread(() -> BCOActionInspector.main(args)).start(); } @Override @@ -220,7 +214,7 @@ public void testPresenceLightAgent() throws Exception { for (ActionDescription actionDescription : colorableLightRemote.requestData().get().getActionList()) { // ignore termination action because its always on the stack - if(actionDescription.getPriority() == Priority.TERMINATION) { + if (actionDescription.getPriority() == Priority.TERMINATION) { continue; } From 993923f4f220cfe8d353fb7c9b400c0799e0f190 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 20 Feb 2024 22:14:17 +0100 Subject: [PATCH 66/70] integrate user message subscription in schema --- .../api/graphql/BcoGraphQlApiSpringBootApplication.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt index 5178bd060e..2e279d3874 100644 --- a/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt +++ b/module/api/graphql/src/main/java/org/openbase/bco/api/graphql/BcoGraphQlApiSpringBootApplication.kt @@ -57,7 +57,8 @@ import org.springframework.context.annotation.Bean * License along with this program. If not, see * . * #L% - */@SpringBootApplication + */ +@SpringBootApplication open class BcoGraphQlApiSpringBootApplication { private var injector: Injector? = null @@ -84,6 +85,8 @@ open class BcoGraphQlApiSpringBootApplication { ) val unitDataOutputType = schema.getType("openbase_type_domotic_unit_UnitData") as GraphQLOutputType? val unitConfigOutputType = schema.getType("openbase_type_domotic_unit_UnitConfig") as GraphQLOutputType? + val userMessageOutputType = + schema.getType("openbase_type_domotic_communication_UserMessage") as GraphQLOutputType? val unitFilterInputType = schema.getType("Input_openbase_type_domotic_unit_UnitFilter") as GraphQLInputType? val unitFilterInputConverter = GqlInputConverter.newBuilder().add(UnitFilterType.UnitFilter.getDescriptor().file).build() @@ -99,6 +102,11 @@ open class BcoGraphQlApiSpringBootApplication { .argument(GraphQLArgument.newArgument().name("includeDisabledUnits").type(Scalars.GraphQLBoolean)) .build() ) + builder.field( + GraphQLFieldDefinition.newFieldDefinition().name("userMessages") + .type(GraphQLList.list(userMessageOutputType)) + .build() + ) val codeRegistry = GraphQLCodeRegistry.newCodeRegistry(schema.codeRegistry) .dataFetcher(FieldCoordinates.coordinates("Subscription", "units"), DataFetcher { dataFetchingEnvironment -> val unitFilter = unitFilterInputConverter.createProtoBuf( From 8c094423312b2581dd3a5f27e3656b3c7323d3db Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 20 Feb 2024 22:15:06 +0100 Subject: [PATCH 67/70] fix exception handling in AuthorizationWithTokenHelper to handle user messages --- .../org/openbase/bco/app/preset/DeviceNotificationApp.kt | 4 ++-- .../unit/lib/auth/AuthorizationWithTokenHelper.kt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt index a3bc82873a..6efe6cd635 100644 --- a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt +++ b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt @@ -142,7 +142,7 @@ class DeviceNotificationApp : AbstractAppController() { companion object { private val LOGGER = LoggerFactory.getLogger(TemplateApp::class.java) - private val VALIDATION_PERIOD: Duration = Duration.ofHours(24) - private val INITIAL_VALIDATION_DELAY: Duration = Duration.ofHours(1) + private val VALIDATION_PERIOD: Duration = Duration.ofMinutes(1) + private val INITIAL_VALIDATION_DELAY: Duration = Duration.ofMinutes(1) } } diff --git a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt index 8babea995f..d2b4c4cab7 100644 --- a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt +++ b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt @@ -169,13 +169,13 @@ object AuthorizationWithTokenHelper { null ) } catch (exxx: CouldNotPerformException) { - exceptionStack = - MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) + exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) + null + } ?: let { + MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) null } } - MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) - null } } ?: throw FatalImplementationErrorException( "ExceptionStack empty in error case.", From 272736c54201a2812589ab046a67f7aef106f8b9 Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 20 Feb 2024 22:16:08 +0100 Subject: [PATCH 68/70] recover notification timing --- .../org/openbase/bco/app/preset/DeviceNotificationApp.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt index 6efe6cd635..7c1a8e4b0e 100644 --- a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt +++ b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt @@ -141,8 +141,8 @@ class DeviceNotificationApp : AbstractAppController() { companion object { private val LOGGER = LoggerFactory.getLogger(TemplateApp::class.java) - - private val VALIDATION_PERIOD: Duration = Duration.ofMinutes(1) - private val INITIAL_VALIDATION_DELAY: Duration = Duration.ofMinutes(1) + + private val VALIDATION_PERIOD: Duration = Duration.ofHours(24) + private val INITIAL_VALIDATION_DELAY: Duration = Duration.ofHours(1) } } From 656f2600eb0434a37025cd3f5db6a93738e763fc Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 20 Feb 2024 23:27:39 +0100 Subject: [PATCH 69/70] link against latest jul version --- lib/jul | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jul b/lib/jul index 9a023a6171..69be6a77f2 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 9a023a617137d20596af504db9409cd92d3bf2ba +Subproject commit 69be6a77f2adf642b94d070e3d6a971a3422bd49 From 9a0f52325cd3acb3c9e38ab2fcebc062097ea69e Mon Sep 17 00:00:00 2001 From: Divine Threepwood Date: Tue, 27 Feb 2024 22:03:20 +0100 Subject: [PATCH 70/70] fix permission handling by making sure the admin is always able to modify a user message. Make sure user messages are resolvable via the getById method. Make sure MessageRegistry is properly registered in the registry pool. --- lib/jul | 2 +- .../bco/registry/remote/Registries.java | 5 +- .../lib/auth/AuthorizationWithTokenHelper.kt | 161 ++++++++++-------- 3 files changed, 91 insertions(+), 77 deletions(-) diff --git a/lib/jul b/lib/jul index 69be6a77f2..57f9689153 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 69be6a77f2adf642b94d070e3d6a971a3422bd49 +Subproject commit 57f9689153d476d593e2fc9cfaad980cddbe67d5 diff --git a/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java b/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java index 00fd75f212..431ec3e50f 100644 --- a/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java +++ b/module/registry/remote/src/main/java/org/openbase/bco/registry/remote/Registries.java @@ -50,6 +50,7 @@ import org.openbase.jul.storage.registry.RegistryRemote; import org.openbase.type.domotic.activity.ActivityConfigType.ActivityConfig; import org.openbase.type.domotic.activity.ActivityTemplateType.ActivityTemplate; +import org.openbase.type.domotic.communication.UserMessageType.UserMessage; import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate; import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig; import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate; @@ -83,7 +84,8 @@ public class Registries { UnitTemplate.getDefaultInstance(), ServiceTemplate.getDefaultInstance(), ActivityTemplate.getDefaultInstance(), - ActivityConfig.getDefaultInstance() + ActivityConfig.getDefaultInstance(), + UserMessage.getDefaultInstance() }; /** @@ -102,6 +104,7 @@ public static List> getRegistries(final boolean waitForData) t registryList.add(getClassRegistry(waitForData)); registryList.add(getActivityRegistry(waitForData)); registryList.add(getUnitRegistry(waitForData)); + registryList.add(getMessageRegistry(waitForData)); return registryList; } diff --git a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt index d2b4c4cab7..4a9221328e 100644 --- a/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt +++ b/module/registry/unit-registry/lib/src/main/java/org/openbase/bco/registry/unit/lib/auth/AuthorizationWithTokenHelper.kt @@ -131,56 +131,60 @@ object AuthorizationWithTokenHelper { userMessage: UserMessageType.UserMessage, permissionType: AuthorizationHelper.PermissionType, unitRegistry: UnitRegistry, - ): AuthPair = try { - // validate sender - canDo( - authenticationBaseData, - unitRegistry.getUnitConfigById(userMessage.senderId), - permissionType, - unitRegistry, - null, - null - ) - } catch (ex: CouldNotPerformException) { + ): AuthPair { var exceptionStack: ExceptionStack? = null - exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, ex, exceptionStack) - - // validate receiver if sender validation failed. - try { + return try { + // validate sender canDo( authenticationBaseData, - unitRegistry.getUnitConfigById(userMessage.recipientId), + tryOrNull { unitRegistry.getUnitConfigById(userMessage.senderId) }, permissionType, unitRegistry, null, null ) - } catch (exx: CouldNotPerformException) { - exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) + } catch (ex: CouldNotPerformException) { + exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, ex, exceptionStack) - userMessage.conditionList.firstNotNullOfOrNull { condition -> - try { - canDo( - authenticationBaseData, - unitRegistry.getUnitConfigById(condition.unitId), - permissionType, - unitRegistry, - null, - null - ) - } catch (exxx: CouldNotPerformException) { - exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) - null - } ?: let { - MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) + // validate receiver if sender validation failed. + try { + canDo( + authenticationBaseData, + tryOrNull { unitRegistry.getUnitConfigById(userMessage.recipientId) }, + permissionType, + unitRegistry, + null, null + ) + } catch (exx: CouldNotPerformException) { + exceptionStack = MultiException.push(AuthorizationWithTokenHelper::class.java, exx, exceptionStack) + + userMessage.conditionList.firstNotNullOfOrNull { condition -> + try { + canDo( + authenticationBaseData, + unitRegistry.getUnitConfigById(condition.unitId), + permissionType, + unitRegistry, + null, + null + ) + } catch (exxx: CouldNotPerformException) { + exceptionStack = + MultiException.push(AuthorizationWithTokenHelper::class.java, exxx, exceptionStack) + null + } } } + } ?: run { + + MultiException.checkAndThrow({ "Permission denied!" }, exceptionStack) + + throw FatalImplementationErrorException( + "ExceptionStack empty in error case.", AuthorizationWithTokenHelper::class.java + ) } - } ?: throw FatalImplementationErrorException( - "ExceptionStack empty in error case.", - AuthorizationWithTokenHelper::class.java - ) + } /** * Perform a permission check for authentication data including tokens. @@ -204,7 +208,7 @@ object AuthorizationWithTokenHelper { @Throws(CouldNotPerformException::class) fun canDo( authenticationBaseData: AuthenticationBaseData?, - unitConfig: UnitConfigType.UnitConfig, + unitConfig: UnitConfigType.UnitConfig?, permissionType: AuthorizationHelper.PermissionType, unitRegistry: UnitRegistry, unitType: UnitTemplateType.UnitTemplate.UnitType? = null, @@ -227,27 +231,6 @@ object AuthorizationWithTokenHelper { } } - // check if authenticated user has needed permissions - if (AuthorizationHelper.canDo( - unitConfig, - userClientPair.getUserId(), - unitRegistry.getAuthorizationGroupMap(), - unitRegistry.getLocationMap(), - permissionType - ) - ) { - return AuthPair(userClientPair, userClientPair.getUserId()) - } - if (AuthorizationHelper.canDo( - unitConfig, - userClientPair.getClientId(), - unitRegistry.getAuthorizationGroupMap(), - unitRegistry.getLocationMap(), - permissionType - ) - ) { - return AuthPair(userClientPair, userClientPair.getClientId()) - } try { // test if user is part of the admin group val memberIdList = @@ -262,25 +245,53 @@ object AuthorizationWithTokenHelper { // continue with the checks, admin group is not available } - // authenticated user does not have permissions so check if the authorization token grants them - if (authenticationBaseData != null && authenticationBaseData.authorizationToken != null) { - val authorizationToken = authenticationBaseData.authorizationToken - // verify that the authorization token is valid - verifyAuthorizationToken(authorizationToken, unitRegistry) + if (unitConfig != null) { - // verify if the token grants the necessary permissions - return authorizedByToken( - authorizationToken, - userClientPair, - unitConfig, - permissionType, - unitRegistry, - unitType, - serviceType - ) + // check if authenticated user has needed permissions + if (AuthorizationHelper.canDo( + unitConfig, + userClientPair.getUserId(), + unitRegistry.getAuthorizationGroupMap(), + unitRegistry.getLocationMap(), + permissionType + ) + ) { + return AuthPair(userClientPair, userClientPair.getUserId()) + } + + if (AuthorizationHelper.canDo( + unitConfig, + userClientPair.getClientId(), + unitRegistry.getAuthorizationGroupMap(), + unitRegistry.getLocationMap(), + permissionType + ) + ) { + return AuthPair(userClientPair, userClientPair.getClientId()) + } + + // authenticated user does not have permissions so check if the authorization token grants them + if (authenticationBaseData != null + && authenticationBaseData.authorizationToken != null + ) { + val authorizationToken = authenticationBaseData.authorizationToken + // verify that the authorization token is valid + verifyAuthorizationToken(authorizationToken, unitRegistry) + + // verify if the token grants the necessary permissions + return authorizedByToken( + authorizationToken, + userClientPair, + unitConfig, + permissionType, + unitRegistry, + unitType, + serviceType + ) + } } var userRepresentation = userClientPair.getUserId() - if (!userRepresentation.isEmpty()) { + if (userRepresentation.isNotEmpty()) { userRepresentation += "@" } userRepresentation += userClientPair.getClientId()