Skip to content

Commit

Permalink
Add a PersistenceService bundle tracker (#4324)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan N. Klug <[email protected]>
  • Loading branch information
J-N-K authored Sep 9, 2024
1 parent 6e6f000 commit 53889cd
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -42,118 +33,16 @@
*/
@NonNullByDefault
@Component(immediate = true)
public class ScriptEngineFactoryBundleTracker extends BundleTracker<Bundle> 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<String, Integer> 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<String, String> headers = bundle.getHeaders();
String provideCapability = headers.get("Provide-Capability");
return provideCapability != null && provideCapability.contains(ScriptEngineFactory.class.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, String> headers = bundle.getHeaders();
String provideCapability = headers.get("Provide-Capability");
return provideCapability != null && provideCapability.contains(PersistenceService.class.getName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Bundle> 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<String, Integer> 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);
}

0 comments on commit 53889cd

Please sign in to comment.