From 53889cdcdbf04318d6980a9b8c956f3992bd8337 Mon Sep 17 00:00:00 2001 From: J-N-K Date: Mon, 9 Sep 2024 20:06:15 +0200 Subject: [PATCH] Add a PersistenceService bundle tracker (#4324) Signed-off-by: Jan N. Klug --- .../ScriptEngineFactoryBundleTracker.java | 119 +------------ .../PersistenceServiceBundleTracker.java | 50 ++++++ .../core/thing/internal/ThingManagerImpl.java | 2 +- .../service/AbstractServiceBundleTracker.java | 157 ++++++++++++++++++ 4 files changed, 212 insertions(+), 116 deletions(-) create mode 100644 bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceBundleTracker.java create mode 100644 bundles/org.openhab.core/src/main/java/org/openhab/core/service/AbstractServiceBundleTracker.java diff --git a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java index e60e1a831f8..9696201f0ca 100644 --- a/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java +++ b/bundles/org.openhab.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineFactoryBundleTracker.java @@ -13,26 +13,17 @@ package org.openhab.core.automation.module.script.internal; import java.util.Dictionary; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.automation.module.script.ScriptEngineFactory; +import org.openhab.core.service.AbstractServiceBundleTracker; import org.openhab.core.service.ReadyMarker; -import org.openhab.core.service.ReadyMarkerFilter; import org.openhab.core.service.ReadyService; -import org.openhab.core.service.StartLevelService; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; -import org.osgi.util.tracker.BundleTracker; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link ScriptEngineFactoryBundleTracker} tracks bundles that provide {@link ScriptEngineFactory} and sets the @@ -42,118 +33,16 @@ */ @NonNullByDefault @Component(immediate = true) -public class ScriptEngineFactoryBundleTracker extends BundleTracker implements ReadyService.ReadyTracker { - private static final int STATE_MASK = Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE | Bundle.STARTING - | Bundle.STOPPING | Bundle.UNINSTALLED; +public class ScriptEngineFactoryBundleTracker extends AbstractServiceBundleTracker { public static final ReadyMarker READY_MARKER = new ReadyMarker("automation", "scriptEngineFactories"); - private final Logger logger = LoggerFactory.getLogger(ScriptEngineFactoryBundleTracker.class); - - private final ReadyService readyService; - - private final Map bundles = new ConcurrentHashMap<>(); - private boolean startLevel = false; - private boolean ready = false; - @Activate public ScriptEngineFactoryBundleTracker(final @Reference ReadyService readyService, BundleContext bc) { - super(bc, STATE_MASK, null); - this.readyService = readyService; - this.open(); - - readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE) - .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL))); - } - - @Deactivate - public void deactivate() throws Exception { - this.close(); - ready = false; - } - - private boolean allBundlesActive() { - return bundles.values().stream().allMatch(i -> i == Bundle.ACTIVE); - } - - @Override - public Bundle addingBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event) { - String bsn = bundle.getSymbolicName(); - int state = bundle.getState(); - if (isScriptingBundle(bundle)) { - logger.debug("Added {}: {} ", bsn, stateToString(state)); - bundles.put(bsn, state); - checkReady(); - } - - return bundle; - } - - @Override - public void modifiedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event, - @NonNullByDefault({}) Bundle object) { - String bsn = bundle.getSymbolicName(); - int state = bundle.getState(); - if (isScriptingBundle(bundle)) { - logger.debug("Modified {}: {}", bsn, stateToString(state)); - bundles.put(bsn, state); - checkReady(); - } - } - - @Override - public void removedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event, - @NonNullByDefault({}) Bundle object) { - String bsn = bundle.getSymbolicName(); - if (isScriptingBundle(bundle)) { - logger.debug("Removed {}", bsn); - bundles.remove(bsn); - checkReady(); - } - } - - @Override - public void onReadyMarkerAdded(ReadyMarker readyMarker) { - logger.debug("Readymarker {} added", readyMarker); - startLevel = true; - checkReady(); + super(readyService, bc, READY_MARKER); } @Override - public void onReadyMarkerRemoved(ReadyMarker readyMarker) { - logger.debug("Readymarker {} removed", readyMarker); - startLevel = false; - ready = false; - readyService.unmarkReady(READY_MARKER); - } - - private synchronized void checkReady() { - boolean allBundlesActive = allBundlesActive(); - logger.trace("ready: {}, startlevel: {}, allActive: {}", ready, startLevel, allBundlesActive); - - if (!ready && startLevel && allBundlesActive) { - logger.debug("Adding ready marker: All automation bundles ready ({})", bundles); - readyService.markReady(READY_MARKER); - ready = true; - } else if (ready && !allBundlesActive) { - logger.debug("Removing ready marker: Not all automation bundles ready ({})", bundles); - readyService.unmarkReady(READY_MARKER); - ready = false; - } - } - - private String stateToString(int state) { - return switch (state) { - case Bundle.UNINSTALLED -> "UNINSTALLED"; - case Bundle.INSTALLED -> "INSTALLED"; - case Bundle.RESOLVED -> "RESOLVED"; - case Bundle.STARTING -> "STARTING"; - case Bundle.STOPPING -> "STOPPING"; - case Bundle.ACTIVE -> "ACTIVE"; - default -> "UNKNOWN"; - }; - } - - private boolean isScriptingBundle(Bundle bundle) { + protected boolean isRelevantBundle(Bundle bundle) { Dictionary headers = bundle.getHeaders(); String provideCapability = headers.get("Provide-Capability"); return provideCapability != null && provideCapability.contains(ScriptEngineFactory.class.getName()); diff --git a/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceBundleTracker.java b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceBundleTracker.java new file mode 100644 index 00000000000..46b284c50d2 --- /dev/null +++ b/bundles/org.openhab.core.persistence/src/main/java/org/openhab/core/persistence/internal/PersistenceServiceBundleTracker.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.persistence.internal; + +import java.util.Dictionary; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.persistence.PersistenceService; +import org.openhab.core.service.AbstractServiceBundleTracker; +import org.openhab.core.service.ReadyMarker; +import org.openhab.core.service.ReadyService; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link PersistenceServiceBundleTracker} tracks bundles that provide {@link PersistenceService} and sets the + * {@link #READY_MARKER} when all registered bundles are active + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +@Component(immediate = true) +public class PersistenceServiceBundleTracker extends AbstractServiceBundleTracker { + public static final ReadyMarker READY_MARKER = new ReadyMarker("persistence", "services"); + + @Activate + public PersistenceServiceBundleTracker(final @Reference ReadyService readyService, BundleContext bc) { + super(readyService, bc, READY_MARKER); + } + + @Override + protected boolean isRelevantBundle(Bundle bundle) { + Dictionary headers = bundle.getHeaders(); + String provideCapability = headers.get("Provide-Capability"); + return provideCapability != null && provideCapability.contains(PersistenceService.class.getName()); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java index d36568af45f..22f8dd42acf 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/openhab/core/thing/internal/ThingManagerImpl.java @@ -210,7 +210,7 @@ public ThingManagerImpl( // this.thingRegistry.addThingTracker(this); readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE) - .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL))); + .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_STATES))); } @Deactivate diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/service/AbstractServiceBundleTracker.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/AbstractServiceBundleTracker.java new file mode 100644 index 00000000000..c0b58aeb34d --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/service/AbstractServiceBundleTracker.java @@ -0,0 +1,157 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.util.tracker.BundleTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AbstractServiceBundleTracker} tracks a set of bundles (selected {@link #isRelevantBundle(Bundle)} + * and sets the + * {@link #readyMarker} when all registered bundles are active + * + * @author Jan N. Klug - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractServiceBundleTracker extends BundleTracker implements ReadyService.ReadyTracker { + private static final int STATE_MASK = Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE | Bundle.STARTING + | Bundle.STOPPING | Bundle.UNINSTALLED; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final ReadyService readyService; + private final ReadyMarker readyMarker; + + private final Map bundles = new ConcurrentHashMap<>(); + private boolean startLevel = false; + private boolean ready = false; + + @Activate + public AbstractServiceBundleTracker(final @Reference ReadyService readyService, BundleContext bc, + ReadyMarker readyMarker) { + super(bc, STATE_MASK, null); + this.readyService = readyService; + this.readyMarker = readyMarker; + this.open(); + + readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE) + .withIdentifier(Integer.toString(StartLevelService.STARTLEVEL_MODEL))); + } + + @Deactivate + public void deactivate() throws Exception { + this.close(); + ready = false; + } + + private boolean allBundlesActive() { + return bundles.values().stream().allMatch(i -> i == Bundle.ACTIVE); + } + + @Override + public Bundle addingBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event) { + String bsn = bundle.getSymbolicName(); + int state = bundle.getState(); + if (isRelevantBundle(bundle)) { + logger.debug("Added {}: {} ", bsn, stateToString(state)); + bundles.put(bsn, state); + checkReady(); + } + + return bundle; + } + + @Override + public void modifiedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event, + @NonNullByDefault({}) Bundle object) { + String bsn = bundle.getSymbolicName(); + int state = bundle.getState(); + if (isRelevantBundle(bundle)) { + logger.debug("Modified {}: {}", bsn, stateToString(state)); + bundles.put(bsn, state); + checkReady(); + } + } + + @Override + public void removedBundle(@NonNullByDefault({}) Bundle bundle, @Nullable BundleEvent event, + @NonNullByDefault({}) Bundle object) { + String bsn = bundle.getSymbolicName(); + if (isRelevantBundle(bundle)) { + logger.debug("Removed {}", bsn); + bundles.remove(bsn); + checkReady(); + } + } + + @Override + public void onReadyMarkerAdded(ReadyMarker readyMarker) { + logger.debug("Readymarker '{}' added", readyMarker); + startLevel = true; + checkReady(); + } + + @Override + public void onReadyMarkerRemoved(ReadyMarker readyMarker) { + logger.debug("Readymarker '{}' removed", readyMarker); + startLevel = false; + ready = false; + readyService.unmarkReady(readyMarker); + } + + private synchronized void checkReady() { + boolean allBundlesActive = allBundlesActive(); + logger.trace("ready: {}, startlevel: {}, allActive: {}", ready, startLevel, allBundlesActive); + + if (!ready && startLevel && allBundlesActive) { + logger.debug("Adding ready marker '{}': All bundles ready ({})", readyMarker, bundles); + readyService.markReady(readyMarker); + ready = true; + } else if (ready && !allBundlesActive) { + logger.debug("Removing ready marker '{}' : Not all bundles ready ({})", readyMarker, bundles); + readyService.unmarkReady(readyMarker); + ready = false; + } + } + + private String stateToString(int state) { + return switch (state) { + case Bundle.UNINSTALLED -> "UNINSTALLED"; + case Bundle.INSTALLED -> "INSTALLED"; + case Bundle.RESOLVED -> "RESOLVED"; + case Bundle.STARTING -> "STARTING"; + case Bundle.STOPPING -> "STOPPING"; + case Bundle.ACTIVE -> "ACTIVE"; + default -> "UNKNOWN"; + }; + } + + /** + * Decide if a bundle should be tracked by this bundle tracker + * + * @param bundle the bundle + * @return {@code true} if the bundle should be considered, {@code false} otherwise + */ + protected abstract boolean isRelevantBundle(Bundle bundle); +}