Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
Maintaining ReadyMarkers in a dedicated service
Browse files Browse the repository at this point in the history
...instead of using the SCR for it.

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
  • Loading branch information
Simon Kaufmann committed Sep 8, 2017
1 parent 74880d0 commit 8ea28ba
Show file tree
Hide file tree
Showing 19 changed files with 636 additions and 139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
package org.eclipse.smarthome.config.xml;

import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.smarthome.config.core.ConfigDescription;
import org.eclipse.smarthome.config.core.ConfigDescriptionProvider;
Expand All @@ -18,6 +21,8 @@
import org.eclipse.smarthome.config.xml.osgi.XmlDocumentProvider;
import org.eclipse.smarthome.config.xml.osgi.XmlDocumentProviderFactory;
import org.eclipse.smarthome.config.xml.util.XmlDocumentReader;
import org.eclipse.smarthome.core.common.ThreadPoolManager;
import org.eclipse.smarthome.core.service.ReadyService;
import org.osgi.framework.Bundle;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
Expand All @@ -41,18 +46,27 @@ public class ConfigXmlConfigDescriptionProvider extends AbstractXmlConfigDescrip
private XmlDocumentBundleTracker<List<ConfigDescription>> configDescriptionTracker;

private ConfigI18nLocalizationService configI18nLocalizerService;
private ReadyService readyService;

private ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("xml-processing");
private ScheduledFuture<?> trackerJob;

@Activate
public void activate(ComponentContext componentContext) {
XmlDocumentReader<List<ConfigDescription>> configDescriptionReader = new ConfigDescriptionReader();

configDescriptionTracker = new XmlDocumentBundleTracker<>(componentContext.getBundleContext(), XML_DIRECTORY,
configDescriptionReader, this, READY_MARKER);
configDescriptionTracker.open();
configDescriptionReader, this, READY_MARKER, readyService);
trackerJob = scheduler.schedule(() -> {
configDescriptionTracker.open();
}, 0, TimeUnit.SECONDS);
}

@Deactivate
public void deactivate() {
if (trackerJob != null && !trackerJob.isDone()) {
trackerJob.cancel(true);
}
configDescriptionTracker.close();
}

Expand All @@ -65,6 +79,15 @@ public void unsetConfigI18nLocalizerService(ConfigI18nLocalizationService config
this.configI18nLocalizerService = null;
}

@Reference
public void setReadyService(ReadyService readyService) {
this.readyService = readyService;
}

public void unsetReadyService(ReadyService readyService) {
this.readyService = null;
}

@Override
protected ConfigI18nLocalizationService getConfigI18nLocalizerService() {
return configI18nLocalizerService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@
import org.eclipse.smarthome.config.xml.util.XmlDocumentReader;
import org.eclipse.smarthome.core.common.ThreadPoolManager;
import org.eclipse.smarthome.core.service.ReadyMarker;
import org.eclipse.smarthome.core.service.ReadyUtil;
import org.eclipse.smarthome.core.service.ReadyService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.BundleTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -64,11 +63,12 @@ public class XmlDocumentBundleTracker<T> extends BundleTracker<Bundle> {
private final Map<Bundle, ScheduledFuture<?>> queue = new ConcurrentHashMap<>();
private final Set<Bundle> finishedBundles = new CopyOnWriteArraySet<>();
@SuppressWarnings("rawtypes")
private final Map<String, ServiceRegistration> bundleReadyMarkerRegistrations = new ConcurrentHashMap<>();
private final Map<String, ReadyMarker> bundleReadyMarkerRegistrations = new ConcurrentHashMap<>();
private final String readyMarkerKey;

@SuppressWarnings("rawtypes")
private BundleTracker relevantBundlesTracker;
private ReadyService readyService;

/**
* Creates a new instance of this class with the specified parameters.
Expand All @@ -91,7 +91,7 @@ public class XmlDocumentBundleTracker<T> extends BundleTracker<Bundle> {
*/
public XmlDocumentBundleTracker(BundleContext bundleContext, String xmlDirectory,
XmlDocumentReader<T> xmlDocumentTypeReader, XmlDocumentProviderFactory<T> xmlDocumentProviderFactory,
String readyMarkerKey) throws IllegalArgumentException {
String readyMarkerKey, ReadyService readyService) throws IllegalArgumentException {
super(bundleContext, Bundle.ACTIVE, null);

if (bundleContext == null) {
Expand All @@ -106,15 +106,23 @@ public XmlDocumentBundleTracker(BundleContext bundleContext, String xmlDirectory
if (xmlDocumentProviderFactory == null) {
throw new IllegalArgumentException("The XmlDocumentProviderFactory must not be null!");
}
if (readyService == null) {
throw new IllegalArgumentException("The ReadyService must not be null!");
}

this.readyMarkerKey = readyMarkerKey;
this.xmlDirectory = xmlDirectory;
this.xmlDocumentTypeReader = xmlDocumentTypeReader;
this.xmlDocumentProviderFactory = xmlDocumentProviderFactory;
this.readyService = readyService;
}

private boolean isBundleRelevant(Bundle bundle) {
return bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null && isResourcePresent(bundle, xmlDirectory);
return isNotFragment(bundle) && isResourcePresent(bundle, xmlDirectory);
}

private boolean isNotFragment(Bundle bundle) {
return bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null;
}

private Set<Bundle> getRelevantBundles() {
Expand Down Expand Up @@ -144,7 +152,9 @@ public final synchronized void close() {
super.close();
unregisterReadyMarkers();
bundleDocumentProviderMap.clear();
relevantBundlesTracker.close();
if (relevantBundlesTracker != null) {
relevantBundlesTracker.close();
}
clearQueue();
finishedBundles.clear();
}
Expand Down Expand Up @@ -214,7 +224,7 @@ public final synchronized Bundle addingBundle(Bundle bundle, BundleEvent event)

@Override
public final synchronized void removedBundle(Bundle bundle, BundleEvent event, Bundle object) {
logger.debug("Removing the XML related objects from module '{}'...", bundle.getSymbolicName());
logger.trace("Removing the XML related objects from module '{}'...", bundle.getSymbolicName());
finishedBundles.remove(bundle);
ScheduledFuture<?> future = queue.remove(bundle);
if (future != null) {
Expand Down Expand Up @@ -284,10 +294,6 @@ private final boolean isResourcePresent(Bundle bundle, String path) {
* @param bundle
*/
private void addingBundle(Bundle bundle) {
if (!isBundleRelevant(bundle)) {
finishBundle(bundle);
return;
}
ScheduledFuture<?> future = scheduler.schedule(() -> {
processBundle(bundle);
}, 0, TimeUnit.SECONDS);
Expand All @@ -313,10 +319,12 @@ private Set<Bundle> getRemainingBundles() {
}

private void processBundle(Bundle bundle) {
Enumeration<URL> xmlDocumentPaths = bundle.findEntries(xmlDirectory, "*.xml", true);
if (xmlDocumentPaths != null) {
Collection<URL> filteredPaths = filterPatches(xmlDocumentPaths, bundle);
parseDocuments(bundle, filteredPaths);
if (isNotFragment(bundle)) {
Enumeration<URL> xmlDocumentPaths = bundle.findEntries(xmlDirectory, "*.xml", true);
if (xmlDocumentPaths != null) {
Collection<URL> filteredPaths = filterPatches(xmlDocumentPaths, bundle);
parseDocuments(bundle, filteredPaths);
}
}
finishBundle(bundle);
}
Expand Down Expand Up @@ -344,25 +352,23 @@ private void parseDocuments(Bundle bundle, Collection<URL> filteredPaths) {
private void registerReadyMarker(Bundle bundle) {
String bsn = bundle.getSymbolicName();
if (!bundleReadyMarkerRegistrations.containsKey(bsn)) {
@SuppressWarnings("rawtypes")
ServiceRegistration reg = ReadyUtil.markAsReady(context, readyMarkerKey, bsn);
bundleReadyMarkerRegistrations.put(bsn, reg);
ReadyMarker readyMarker = new ReadyMarker(readyMarkerKey, bsn);
readyService.markReady(readyMarker);
bundleReadyMarkerRegistrations.put(bsn, readyMarker);
}
}

private void unregisterReadyMarker(Bundle bundle) {
String bsn = bundle.getSymbolicName();
@SuppressWarnings("rawtypes")
ServiceRegistration reg = bundleReadyMarkerRegistrations.remove(bsn);
if (reg != null) {
reg.unregister();
ReadyMarker readyMarker = bundleReadyMarkerRegistrations.remove(bsn);
if (readyMarker != null) {
readyService.unmarkReady(readyMarker);
}
}

private void unregisterReadyMarkers() {
for (@SuppressWarnings("rawtypes")
ServiceRegistration reg : bundleReadyMarkerRegistrations.values()) {
reg.unregister();
for (ReadyMarker readyMarker : bundleReadyMarkerRegistrations.values()) {
readyService.unmarkReady(readyMarker);
}
bundleReadyMarkerRegistrations.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Import-Package: com.thoughtworks.xstream,
org.eclipse.smarthome.config.xml.osgi,
org.eclipse.smarthome.config.xml.util,
org.eclipse.smarthome.core.binding,
org.eclipse.smarthome.core.common,
org.eclipse.smarthome.core.common.osgi,
org.eclipse.smarthome.core.common.registry,
org.eclipse.smarthome.core.i18n,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.smarthome.config.core.ConfigDescriptionProvider;
import org.eclipse.smarthome.config.xml.AbstractXmlBasedProvider;
Expand All @@ -20,8 +23,10 @@
import org.eclipse.smarthome.config.xml.util.XmlDocumentReader;
import org.eclipse.smarthome.core.binding.BindingInfo;
import org.eclipse.smarthome.core.binding.BindingInfoProvider;
import org.eclipse.smarthome.core.common.ThreadPoolManager;
import org.eclipse.smarthome.core.i18n.BindingI18nUtil;
import org.eclipse.smarthome.core.i18n.TranslationProvider;
import org.eclipse.smarthome.core.service.ReadyService;
import org.osgi.framework.Bundle;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
Expand Down Expand Up @@ -49,18 +54,27 @@ public class XmlBindingInfoProvider extends AbstractXmlBasedProvider<String, Bin
private BindingI18nUtil bindingI18nUtil;
private AbstractXmlConfigDescriptionProvider configDescriptionProvider;
private XmlDocumentBundleTracker<BindingInfoXmlResult> bindingInfoTracker;
private ReadyService readyService;

private ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("xml-processing");
private ScheduledFuture<?> trackerJob;

@Activate
public void activate(ComponentContext componentContext) {
XmlDocumentReader<BindingInfoXmlResult> bindingInfoReader = new BindingInfoReader();

bindingInfoTracker = new XmlDocumentBundleTracker<>(componentContext.getBundleContext(), XML_DIRECTORY,
bindingInfoReader, this, READY_MARKER);
bindingInfoTracker.open();
bindingInfoReader, this, READY_MARKER, readyService);
trackerJob = scheduler.schedule(() -> {
bindingInfoTracker.open();
}, 0, TimeUnit.SECONDS);
}

@Deactivate
public void deactivate(ComponentContext componentContext) {
if (trackerJob != null && !trackerJob.isDone()) {
trackerJob.cancel(true);
}
bindingInfoTracker.close();
bindingInfoTracker = null;
}
Expand Down Expand Up @@ -93,6 +107,15 @@ public void unsetConfigDescriptionProvider(ConfigDescriptionProvider configDescr
this.configDescriptionProvider = null;
}

@Reference
public void setReadyService(ReadyService readyService) {
this.readyService = readyService;
}

public void unsetReadyService(ReadyService readyService) {
this.readyService = null;
}

@Override
protected BindingInfo localize(Bundle bundle, BindingInfo bindingInfo, Locale locale) {
if (this.bindingI18nUtil == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.core.internal.service;

import static org.junit.Assert.*;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;

import org.eclipse.smarthome.core.service.ReadyMarker;
import org.eclipse.smarthome.core.service.ReadyMarkerFilter;
import org.eclipse.smarthome.core.service.ReadyService.ReadyTracker;
import org.junit.Test;

/**
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
public class ReadyServiceImplTest {

@Test
public void testDifferentReadyMarkerInstances() {
ReadyServiceImpl rs = new ReadyServiceImpl();
assertFalse(rs.isReady(new ReadyMarker("test", "id")));
rs.markReady(new ReadyMarker("test", "id"));
assertTrue(rs.isReady(new ReadyMarker("test", "id")));
rs.unmarkReady(new ReadyMarker("test", "id"));
assertFalse(rs.isReady(new ReadyMarker("test", "id")));
}

@Test
public void testTrackersAreInformedInitially() {
ReadyTracker tracker = mock(ReadyTracker.class);
ReadyServiceImpl rs = new ReadyServiceImpl();
rs.markReady(new ReadyMarker("test", "id"));
rs.registerTracker(tracker);
verify(tracker).onReadyMarkerAdded(isA(ReadyMarker.class));
verifyNoMoreInteractions(tracker);
}

@Test
public void testTrackersAreInformedOnChange() {
ReadyTracker tracker = mock(ReadyTracker.class);
ReadyServiceImpl rs = new ReadyServiceImpl();
rs.registerTracker(tracker);
rs.markReady(new ReadyMarker("test", "id"));
verify(tracker).onReadyMarkerAdded(isA(ReadyMarker.class));
rs.unmarkReady(new ReadyMarker("test", "id"));
verify(tracker).onReadyMarkerRemoved(isA(ReadyMarker.class));
verifyNoMoreInteractions(tracker);
}

@Test
public void testTrackersAreInformedOnlyOnMatch() {
ReadyTracker tracker = mock(ReadyTracker.class);
ReadyServiceImpl rs = new ReadyServiceImpl();
rs.registerTracker(tracker, new ReadyMarkerFilter().withType("test"));
rs.markReady(new ReadyMarker("foo", "id"));
verifyNoMoreInteractions(tracker);
rs.markReady(new ReadyMarker("test", "id"));
verify(tracker).onReadyMarkerAdded(isA(ReadyMarker.class));
verifyNoMoreInteractions(tracker);
}

@Test
public void testUnregisterTracker() {
ReadyTracker tracker = mock(ReadyTracker.class);
ReadyServiceImpl rs = new ReadyServiceImpl();
rs.markReady(new ReadyMarker("foo", "id"));
rs.registerTracker(tracker, new ReadyMarkerFilter());
verify(tracker).onReadyMarkerAdded(isA(ReadyMarker.class));
rs.unregisterTracker(tracker);
verify(tracker).onReadyMarkerRemoved(isA(ReadyMarker.class));
verifyNoMoreInteractions(tracker);
}

}
Loading

0 comments on commit 8ea28ba

Please sign in to comment.