diff --git a/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/ConfigXmlConfigDescriptionProvider.java b/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/ConfigXmlConfigDescriptionProvider.java index 2aa5ae30fc3..7ba7b4160dc 100644 --- a/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/ConfigXmlConfigDescriptionProvider.java +++ b/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/ConfigXmlConfigDescriptionProvider.java @@ -8,6 +8,8 @@ package org.eclipse.smarthome.config.xml; import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import org.eclipse.smarthome.config.core.ConfigDescription; import org.eclipse.smarthome.config.core.ConfigDescriptionProvider; @@ -18,6 +20,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; @@ -41,19 +45,31 @@ public class ConfigXmlConfigDescriptionProvider extends AbstractXmlConfigDescrip private XmlDocumentBundleTracker> configDescriptionTracker; private ConfigI18nLocalizationService configI18nLocalizerService; + private ReadyService readyService; + + private ScheduledExecutorService scheduler = ThreadPoolManager + .getScheduledPool(XmlDocumentBundleTracker.THREAD_POOL_NAME); + private Future trackerJob; @Activate public void activate(ComponentContext componentContext) { XmlDocumentReader> configDescriptionReader = new ConfigDescriptionReader(); configDescriptionTracker = new XmlDocumentBundleTracker<>(componentContext.getBundleContext(), XML_DIRECTORY, - configDescriptionReader, this, READY_MARKER); - configDescriptionTracker.open(); + configDescriptionReader, this, READY_MARKER, readyService); + trackerJob = scheduler.submit(() -> { + configDescriptionTracker.open(); + }); } @Deactivate public void deactivate() { + if (trackerJob != null && !trackerJob.isDone()) { + trackerJob.cancel(true); + trackerJob = null; + } configDescriptionTracker.close(); + configDescriptionTracker = null; } @Reference @@ -65,6 +81,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; diff --git a/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/osgi/XmlDocumentBundleTracker.java b/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/osgi/XmlDocumentBundleTracker.java index efc3b42df5c..f21460c7d62 100644 --- a/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/osgi/XmlDocumentBundleTracker.java +++ b/bundles/config/org.eclipse.smarthome.config.xml/src/main/java/org/eclipse/smarthome/config/xml/osgi/XmlDocumentBundleTracker.java @@ -19,20 +19,18 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; 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; @@ -55,20 +53,22 @@ */ public class XmlDocumentBundleTracker extends BundleTracker { + public static final String THREAD_POOL_NAME = "xml-processing"; + private final Logger logger = LoggerFactory.getLogger(XmlDocumentBundleTracker.class); - private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("xml-processing"); + private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(THREAD_POOL_NAME); private final String xmlDirectory; private final XmlDocumentReader xmlDocumentTypeReader; private final XmlDocumentProviderFactory xmlDocumentProviderFactory; private final Map> bundleDocumentProviderMap = new ConcurrentHashMap<>(); - private final Map> queue = new ConcurrentHashMap<>(); + private final Map> queue = new ConcurrentHashMap<>(); private final Set finishedBundles = new CopyOnWriteArraySet<>(); - @SuppressWarnings("rawtypes") - private final Map bundleReadyMarkerRegistrations = new ConcurrentHashMap<>(); + private final Map 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. @@ -91,7 +91,7 @@ public class XmlDocumentBundleTracker extends BundleTracker { */ public XmlDocumentBundleTracker(BundleContext bundleContext, String xmlDirectory, XmlDocumentReader xmlDocumentTypeReader, XmlDocumentProviderFactory xmlDocumentProviderFactory, - String readyMarkerKey) throws IllegalArgumentException { + String readyMarkerKey, ReadyService readyService) throws IllegalArgumentException { super(bundleContext, Bundle.ACTIVE, null); if (bundleContext == null) { @@ -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 getRelevantBundles() { @@ -144,13 +152,15 @@ public final synchronized void close() { super.close(); unregisterReadyMarkers(); bundleDocumentProviderMap.clear(); - relevantBundlesTracker.close(); + if (relevantBundlesTracker != null) { + relevantBundlesTracker.close(); + } clearQueue(); finishedBundles.clear(); } private void clearQueue() { - for (ScheduledFuture future : queue.values()) { + for (Future future : queue.values()) { future.cancel(true); } queue.clear(); @@ -214,9 +224,9 @@ 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); + Future future = queue.remove(bundle); if (future != null) { future.cancel(true); } @@ -284,13 +294,14 @@ 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); + Future future = scheduler.submit(new Runnable() { + // this should remain an anonymous class and not be converted to a lambda because of + // http://bugs.java.com/view_bug.do?bug_id=8073755 + @Override + public void run() { + processBundle(bundle); + } + }); queue.put(bundle, future); } @@ -313,10 +324,12 @@ private Set getRemainingBundles() { } private void processBundle(Bundle bundle) { - Enumeration xmlDocumentPaths = bundle.findEntries(xmlDirectory, "*.xml", true); - if (xmlDocumentPaths != null) { - Collection filteredPaths = filterPatches(xmlDocumentPaths, bundle); - parseDocuments(bundle, filteredPaths); + if (isNotFragment(bundle)) { + Enumeration xmlDocumentPaths = bundle.findEntries(xmlDirectory, "*.xml", true); + if (xmlDocumentPaths != null) { + Collection filteredPaths = filterPatches(xmlDocumentPaths, bundle); + parseDocuments(bundle, filteredPaths); + } } finishBundle(bundle); } @@ -344,25 +357,23 @@ private void parseDocuments(Bundle bundle, Collection 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(); } diff --git a/bundles/core/org.eclipse.smarthome.core.binding.xml/META-INF/MANIFEST.MF b/bundles/core/org.eclipse.smarthome.core.binding.xml/META-INF/MANIFEST.MF index 12af743d3f6..96518c3f114 100644 --- a/bundles/core/org.eclipse.smarthome.core.binding.xml/META-INF/MANIFEST.MF +++ b/bundles/core/org.eclipse.smarthome.core.binding.xml/META-INF/MANIFEST.MF @@ -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, diff --git a/bundles/core/org.eclipse.smarthome.core.binding.xml/src/main/java/org/eclipse/smarthome/core/binding/xml/internal/XmlBindingInfoProvider.java b/bundles/core/org.eclipse.smarthome.core.binding.xml/src/main/java/org/eclipse/smarthome/core/binding/xml/internal/XmlBindingInfoProvider.java index ed30bf3cdd9..fc850acd82f 100644 --- a/bundles/core/org.eclipse.smarthome.core.binding.xml/src/main/java/org/eclipse/smarthome/core/binding/xml/internal/XmlBindingInfoProvider.java +++ b/bundles/core/org.eclipse.smarthome.core.binding.xml/src/main/java/org/eclipse/smarthome/core/binding/xml/internal/XmlBindingInfoProvider.java @@ -10,6 +10,8 @@ import java.util.HashSet; import java.util.Locale; import java.util.Set; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import org.eclipse.smarthome.config.core.ConfigDescriptionProvider; import org.eclipse.smarthome.config.xml.AbstractXmlBasedProvider; @@ -20,8 +22,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; @@ -49,18 +53,29 @@ public class XmlBindingInfoProvider extends AbstractXmlBasedProvider bindingInfoTracker; + private ReadyService readyService; + + private ScheduledExecutorService scheduler = ThreadPoolManager + .getScheduledPool(XmlDocumentBundleTracker.THREAD_POOL_NAME); + private Future trackerJob; @Activate public void activate(ComponentContext componentContext) { XmlDocumentReader bindingInfoReader = new BindingInfoReader(); bindingInfoTracker = new XmlDocumentBundleTracker<>(componentContext.getBundleContext(), XML_DIRECTORY, - bindingInfoReader, this, READY_MARKER); - bindingInfoTracker.open(); + bindingInfoReader, this, READY_MARKER, readyService); + trackerJob = scheduler.submit(() -> { + bindingInfoTracker.open(); + }); } @Deactivate public void deactivate(ComponentContext componentContext) { + if (trackerJob != null && !trackerJob.isDone()) { + trackerJob.cancel(true); + trackerJob = null; + } bindingInfoTracker.close(); bindingInfoTracker = null; } @@ -93,6 +108,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) { diff --git a/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/internal/service/ReadyServiceImplTest.java b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/internal/service/ReadyServiceImplTest.java new file mode 100644 index 00000000000..b2990c8381f --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core.test/src/test/java/org/eclipse/smarthome/core/internal/service/ReadyServiceImplTest.java @@ -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); + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/internal/ThingManagerOSGiTest.groovy b/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/internal/ThingManagerOSGiTest.groovy index 801c90609f0..c72ae71a804 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/internal/ThingManagerOSGiTest.groovy +++ b/bundles/core/org.eclipse.smarthome.core.thing.test/src/test/groovy/org/eclipse/smarthome/core/thing/internal/ThingManagerOSGiTest.groovy @@ -31,7 +31,7 @@ import org.eclipse.smarthome.core.library.items.StringItem import org.eclipse.smarthome.core.library.types.DecimalType import org.eclipse.smarthome.core.library.types.StringType import org.eclipse.smarthome.core.service.ReadyMarker -import org.eclipse.smarthome.core.service.ReadyUtil +import org.eclipse.smarthome.core.service.ReadyService import org.eclipse.smarthome.core.thing.Bridge import org.eclipse.smarthome.core.thing.Channel import org.eclipse.smarthome.core.thing.ChannelUID @@ -79,6 +79,7 @@ class ThingManagerOSGiTest extends OSGiTest { ManagedThingProvider managedThingProvider ThingLinkManager thingLinkManager ItemRegistry itemRegistry + ReadyService readyService ManagedItemChannelLinkProvider managedItemChannelLinkProvider @@ -111,6 +112,9 @@ class ThingManagerOSGiTest extends OSGiTest { itemChannelLinkRegistry = getService(ItemChannelLinkRegistry) assertNotNull(itemChannelLinkRegistry) + readyService = getService(ReadyService) + assertNotNull(readyService) + waitForAssert { assertThat getBundleContext().getServiceReferences(ReadyMarker, "(" + ThingManager.XML_THING_TYPE + "=" + getBundleContext().getBundle().getSymbolicName() + ")"), is(notNullValue()) } @@ -1292,11 +1296,9 @@ class ThingManagerOSGiTest extends OSGiTest { waitForAssert { // wait for the XML processing to be finished, then remove the ready marker again - def ref = bundleContext.getServiceReferences(ReadyMarker.class.getName(), "(" + ThingManager.XML_THING_TYPE + "=" + FrameworkUtil.getBundle(this.getClass()).getSymbolicName() + ")") - assertThat ref, is(notNullValue()) - def registration = ref.registration.getAt(0) - assertThat registration, is(notNullValue()) - registration.unregister() + ReadyMarker marker = new ReadyMarker(ThingManager.XML_THING_TYPE, FrameworkUtil.getBundle(this.getClass()).getSymbolicName()) + assertThat readyService.isReady(marker), is(true) + readyService.unmarkReady(marker); } def statusInfo = ThingStatusInfoBuilder.create(ThingStatus.UNINITIALIZED, ThingStatusDetail.NONE).build() @@ -1309,7 +1311,7 @@ class ThingManagerOSGiTest extends OSGiTest { assertThat initializedCalled, is(false) assertThat thing.getStatusInfo(), is(statusInfo) - ReadyUtil.markAsReady(bundleContext, ThingManager.XML_THING_TYPE, FrameworkUtil.getBundle(this.getClass()).getSymbolicName()) + readyService.markReady(new ReadyMarker(ThingManager.XML_THING_TYPE, FrameworkUtil.getBundle(this.getClass()).getSymbolicName())) // ThingHandler.initialize() called, thing status is INITIALIZING.NONE statusInfo = ThingStatusInfoBuilder.create(ThingStatus.INITIALIZING, ThingStatusDetail.NONE).build() diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/META-INF/MANIFEST.MF b/bundles/core/org.eclipse.smarthome.core.thing.xml/META-INF/MANIFEST.MF index 684567c14e0..85ec3e91ded 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.xml/META-INF/MANIFEST.MF +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/META-INF/MANIFEST.MF @@ -13,6 +13,7 @@ Import-Package: com.thoughtworks.xstream, org.eclipse.smarthome.config.xml, org.eclipse.smarthome.config.xml.osgi, org.eclipse.smarthome.config.xml.util, + org.eclipse.smarthome.core.common, org.eclipse.smarthome.core.common.osgi, org.eclipse.smarthome.core.common.registry, org.eclipse.smarthome.core.i18n, diff --git a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlThingTypeProvider.java b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlThingTypeProvider.java index b2a5ceac4c5..f4cc73c7d88 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlThingTypeProvider.java +++ b/bundles/core/org.eclipse.smarthome.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/XmlThingTypeProvider.java @@ -10,6 +10,8 @@ import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import org.eclipse.smarthome.config.core.ConfigDescriptionProvider; import org.eclipse.smarthome.config.xml.AbstractXmlBasedProvider; @@ -18,6 +20,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.eclipse.smarthome.core.thing.ThingTypeUID; import org.eclipse.smarthome.core.thing.UID; import org.eclipse.smarthome.core.thing.binding.ThingTypeProvider; @@ -56,18 +60,30 @@ public class XmlThingTypeProvider extends AbstractXmlBasedProvider> thingTypeTracker; + private ReadyService readyService; + + private ScheduledExecutorService scheduler = ThreadPoolManager + .getScheduledPool(XmlDocumentBundleTracker.THREAD_POOL_NAME); + private Future trackerJob; @Activate protected void activate(BundleContext bundleContext) { XmlDocumentReader> thingTypeReader = new ThingDescriptionReader(); - thingTypeTracker = new XmlDocumentBundleTracker>(bundleContext, XML_DIRECTORY, thingTypeReader, this, - READY_MARKER); - thingTypeTracker.open(); + READY_MARKER, readyService); + + trackerJob = scheduler.submit(() -> { + thingTypeTracker.open(); + }); + } @Deactivate protected void deactivate() { + if (trackerJob != null && !trackerJob.isDone()) { + trackerJob.cancel(true); + trackerJob = null; + } thingTypeTracker.close(); thingTypeTracker = null; } @@ -120,6 +136,15 @@ public void unsetChannelGroupTypeProvider(ChannelTypeProvider channelGroupTypePr this.channelGroupTypeProvider = null; } + @Reference + public void setReadyService(ReadyService readyService) { + this.readyService = readyService; + } + + public void unsetReadyService(ReadyService readyService) { + this.readyService = null; + } + @Override protected ThingType localize(Bundle bundle, ThingType thingType, Locale locale) { if (thingTypeI18nLocalizationService == null) { diff --git a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java index 46a63c1a8c8..3ee21a666e9 100644 --- a/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java +++ b/bundles/core/org.eclipse.smarthome.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ThingManager.java @@ -38,6 +38,8 @@ import org.eclipse.smarthome.core.common.registry.Provider; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.service.ReadyMarker; +import org.eclipse.smarthome.core.service.ReadyMarkerFilter; +import org.eclipse.smarthome.core.service.ReadyService; import org.eclipse.smarthome.core.thing.Bridge; import org.eclipse.smarthome.core.thing.Channel; import org.eclipse.smarthome.core.thing.ChannelUID; @@ -97,7 +99,7 @@ * @author Thomas Höfer - Added localization of thing status info */ @Component(immediate = true, service = { ThingTypeMigrationService.class }) -public class ThingManager implements ThingTracker, ThingTypeMigrationService { +public class ThingManager implements ThingTracker, ThingTypeMigrationService, ReadyService.ReadyTracker { private static final String FORCEREMOVE_THREADPOOL_NAME = "forceRemove"; private static final String THING_MANAGER_THREADPOOL_NAME = "thingManager"; @@ -903,20 +905,26 @@ protected void addThingHandlerFactory(ThingHandlerFactory thingHandlerFactory) { private Set loadedXmlThingTypes = new CopyOnWriteArraySet<>(); - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - protected void addReadyMarker(ReadyMarker readyMarker, Map properties) { - if (properties.containsKey(XML_THING_TYPE)) { - String bsn = (String) properties.get(XML_THING_TYPE); - loadedXmlThingTypes.add(bsn); - handleThingHandlerFactoryAddition(bsn); - } + @Reference + public void setReadyService(ReadyService readyService) { + readyService.registerTracker(this, new ReadyMarkerFilter().withType(XML_THING_TYPE)); } - protected void removeReadyMarker(ReadyMarker readyMarker, Map properties) { - if (properties.containsKey(XML_THING_TYPE)) { - String bsn = (String) properties.get(XML_THING_TYPE); - loadedXmlThingTypes.remove(bsn); - } + public void unsetReadyService(ReadyService readyService) { + readyService.unregisterTracker(this); + } + + @Override + public void onReadyMarkerAdded(ReadyMarker readyMarker) { + String bsn = readyMarker.getIdentifier(); + loadedXmlThingTypes.add(bsn); + handleThingHandlerFactoryAddition(bsn); + } + + @Override + public void onReadyMarkerRemoved(ReadyMarker readyMarker) { + String bsn = readyMarker.getIdentifier(); + loadedXmlThingTypes.remove(bsn); } private void handleThingHandlerFactoryAddition(String bsn) { diff --git a/bundles/core/org.eclipse.smarthome.core/OSGI-INF/.gitignore b/bundles/core/org.eclipse.smarthome.core/OSGI-INF/.gitignore index 18f8c64599f..74283c26a68 100644 --- a/bundles/core/org.eclipse.smarthome.core/OSGI-INF/.gitignore +++ b/bundles/core/org.eclipse.smarthome.core/OSGI-INF/.gitignore @@ -1 +1,2 @@ /org.eclipse.smarthome.core.net.NetUtil.xml +/org.eclipse.smarthome.core.internal.service.ReadyServiceImpl.xml diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/service/ReadyServiceImpl.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/service/ReadyServiceImpl.java new file mode 100644 index 00000000000..913e86e450b --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/internal/service/ReadyServiceImpl.java @@ -0,0 +1,119 @@ +/** + * 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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; + +import org.eclipse.smarthome.core.service.ReadyMarker; +import org.eclipse.smarthome.core.service.ReadyMarkerFilter; +import org.eclipse.smarthome.core.service.ReadyService; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of the {@link ReadyService} interface. + * + * @author Simon Kaufmann - initial contribution and API. + * + */ +@Component +public class ReadyServiceImpl implements ReadyService { + + private final Logger logger = LoggerFactory.getLogger(ReadyServiceImpl.class); + private static final ReadyMarkerFilter ANY = new ReadyMarkerFilter(); + + private final Set markers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + private final Map trackers = new HashMap<>(); + private final ReentrantReadWriteLock rwlTrackers = new ReentrantReadWriteLock(true); + + @Override + public void markReady(ReadyMarker readyMarker) { + rwlTrackers.readLock().lock(); + try { + boolean isNew = markers.add(readyMarker); + if (isNew) { + notifyTrackers(readyMarker, tracker -> tracker.onReadyMarkerAdded(readyMarker)); + logger.debug("Added ready marker {}", readyMarker); + } + } finally { + rwlTrackers.readLock().unlock(); + } + } + + @Override + public void unmarkReady(ReadyMarker readyMarker) { + rwlTrackers.readLock().lock(); + try { + boolean isRemoved = markers.remove(readyMarker); + if (isRemoved) { + notifyTrackers(readyMarker, tracker -> tracker.onReadyMarkerRemoved(readyMarker)); + logger.debug("Removed ready marker {}", readyMarker); + } + } finally { + rwlTrackers.readLock().unlock(); + } + } + + private void notifyTrackers(ReadyMarker readyMarker, Consumer action) { + trackers.entrySet().stream().filter(entry -> { + return entry.getValue().apply(readyMarker); + }).map(entry -> { + return entry.getKey(); + }).forEach(action); + } + + @Override + public boolean isReady(ReadyMarker readyMarker) { + return markers.contains(readyMarker); + } + + @Override + public void registerTracker(ReadyTracker readyTracker) { + registerTracker(readyTracker, ANY); + } + + @Override + public void registerTracker(ReadyTracker readyTracker, ReadyMarkerFilter filter) { + rwlTrackers.writeLock().lock(); + try { + if (!trackers.containsKey(readyTracker)) { + trackers.put(readyTracker, filter); + notifyTracker(readyTracker, marker -> readyTracker.onReadyMarkerAdded(marker)); + } + } finally { + rwlTrackers.writeLock().unlock(); + } + } + + @Override + public void unregisterTracker(ReadyTracker readyTracker) { + rwlTrackers.writeLock().lock(); + try { + if (trackers.containsKey(readyTracker)) { + notifyTracker(readyTracker, marker -> readyTracker.onReadyMarkerRemoved(marker)); + } + trackers.remove(readyTracker); + } finally { + rwlTrackers.writeLock().unlock(); + } + } + + private void notifyTracker(ReadyTracker readyTracker, Consumer action) { + ReadyMarkerFilter f = trackers.get(readyTracker); + markers.stream().filter(marker -> f.apply(marker)).forEach(action); + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyMarker.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyMarker.java index f99abe0175f..674c48b53f4 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyMarker.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyMarker.java @@ -7,12 +7,89 @@ */ package org.eclipse.smarthome.core.service; +import org.eclipse.jdt.annotation.NonNull; + /** - * Marker service in order to denote that loading of a specific component has completed and it is ready to be used. + * This is a token, identifying something to be completed. + * + * A caller may use it to identify which action or process is completed or to which completion it wants to listen to. + * Thereby the {@code type} denotes the category of readyness, the {@code identifier}, e.g. + * {@code new ReadyMarker("xmlProcessing", "o.e.sh.binding.sample")} would denote that the "sample binding" has finished + * processing some xmls. + *

+ * When the action or process is being marked as "ready" or removed from the "ready" state, a registered tracker will be + * notified by the ReadyService. + *

+ * This class overrides {@link #hashCode()} and {@link #equals(Object)} so that any using class does not have to keep + * original references, but new instances can be used for calls to the {@link ReadyService} every time. * - * @author Simon Kaufmann - initial contribution and API + * @author Simon Kaufmann - initial contribution and API. * */ -public interface ReadyMarker { +public final class ReadyMarker { + + @NonNull + private final String type; + + @NonNull + private final String identifier; + + public ReadyMarker(@NonNull String type, @NonNull String identifier) { + this.type = type; + this.identifier = identifier; + } + + @NonNull + public String getType() { + return type; + } + + @NonNull + public String getIdentifier() { + return identifier; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((identifier == null) ? 0 : identifier.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ReadyMarker other = (ReadyMarker) obj; + if (identifier == null) { + if (other.identifier != null) { + return false; + } + } else if (!identifier.equals(other.identifier)) { + return false; + } + if (type == null) { + if (other.type != null) { + return false; + } + } else if (!type.equals(other.type)) { + return false; + } + return true; + } + + @Override + public String toString() { + return getType() + "=" + getIdentifier(); + } } diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyMarkerFilter.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyMarkerFilter.java new file mode 100644 index 00000000000..36424b64241 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyMarkerFilter.java @@ -0,0 +1,64 @@ +/** + * 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.service; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * Filter for {@link ReadyMarker}s which a ReadyTracker is interested in. + * + * By default, this filter will match any {@link ReadyMarker}. + * + * @author Simon Kaufmann - initial contribution and API. + * + */ +public final class ReadyMarkerFilter { + + private final String identifier; + private final String type; + + public ReadyMarkerFilter() { + this(null, null); + } + + private ReadyMarkerFilter(String type, String identifier) { + this.type = type; + this.identifier = identifier; + } + + public boolean apply(@NonNull ReadyMarker readyMarker) { + return isTracked(type, readyMarker.getType()) && isTracked(identifier, readyMarker.getIdentifier()); + } + + private boolean isTracked(String trackingSpec, String realValue) { + return trackingSpec == null || trackingSpec.equals(realValue); + } + + /** + * Returns a {@link ReadyMarkerFilter} restricted to the given type. + * + * @param type + * @return + */ + @NonNull + public ReadyMarkerFilter withType(String type) { + return new ReadyMarkerFilter(type, identifier); + } + + /** + * Returns a {@link ReadyMarkerFilter} restricted to the given identifier. + * + * @param type + * @return + */ + @NonNull + public ReadyMarkerFilter withIdentifier(String identifier) { + return new ReadyMarkerFilter(type, identifier); + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyService.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyService.java new file mode 100644 index 00000000000..629cb4ee559 --- /dev/null +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyService.java @@ -0,0 +1,102 @@ +/** + * 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.service; + +import org.eclipse.jdt.annotation.NonNull; + +/** + * Registry for {@link ReadyMarker}s. + * + * Services may use the {@link ReadyService} in order to denote they have completed loading/processing something. + *

+ * Interested parties may register as a tracker for {@link ReadyMarker}s. Optionally they can provide a + * {@link ReadyMarkerFilter} in order to restrict the {@link ReadyMarker}s they get notified for. + *

+ * Alternatively, {@link #isReady(ReadyMarker)} can be used to check for any given {@link ReadyMarker}. + * + * @author Simon Kaufmann - initial contribution and API. + * + */ +public interface ReadyService { + + /** + * Register the given marker as being "ready". + * + * @param readyMarker + */ + void markReady(@NonNull ReadyMarker readyMarker); + + /** + * Removes the given marker. + * + * @param readyMarker + */ + void unmarkReady(@NonNull ReadyMarker readyMarker); + + /** + * + * @param readyMarker + * @return {@code true} if the given {@link ReadyMarker} is registered as being "ready". + */ + boolean isReady(@NonNull ReadyMarker readyMarker); + + /** + * Adds the given tracker. + * + * It will be notified for all {@link ReadyMarker}s. + * + * @param readyTracker + */ + void registerTracker(@NonNull ReadyTracker readyTracker); + + /** + * Adds the given tracker. + * + * It will be notified for a ReadyMarker changes related to those which match the given filter criteria. + *

+ * The provided tracker will get notified about the addition of all existing readyMarkers right away. + * + * @param readyTracker + * @param readyMarker + */ + void registerTracker(@NonNull ReadyTracker readyTracker, @NonNull ReadyMarkerFilter filter); + + /** + * Removes the given tracker. + * + * The provided tracker will get notified about the removal of all existing readyMarkers right away. + * + * @param readyTracker + */ + void unregisterTracker(@NonNull ReadyTracker readyTracker); + + /** + * Tracker for changes related to {@link ReadyMarker} registrations. + * + * @author Simon Kaufmann - initial contribution and API. + * + */ + interface ReadyTracker { + + /** + * Gets called when a new {@link ReadyMarker} was registered as being "ready". + * + * @param readyMarker + */ + void onReadyMarkerAdded(@NonNull ReadyMarker readyMarker); + + /** + * Gets called when a {@link ReadyMarker} was unregistered. + * + * @param readyMarker + */ + void onReadyMarkerRemoved(@NonNull ReadyMarker readyMarker); + + } + +} diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyUtil.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyUtil.java deleted file mode 100644 index afdfc938253..00000000000 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/ReadyUtil.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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.service; - -import java.util.Dictionary; -import java.util.Properties; - -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.ServiceRegistration; - -/** - * Utility class to easily register {@link ReadyMarker}s programmatically. - * - * @author Simon Kaufmann - initial contribution and API - * - */ -public class ReadyUtil { - - /** - * Registers a {@link ReadyMarker} with the given identification details. - * - * The caller needs to remember the returned {@link ServiceReference} and must make sure to unregister it at the - * appropriate point in time. - * - * @param context the {@link BundleContext} - * @param key the marker type (see {@link ReadyMarker} for constants) - * @param identifier an identifier. What that is depends on the key. - * @return a {@link ServiceRegistration} object - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static ServiceRegistration markAsReady(BundleContext context, String key, String identifier) { - Dictionary props = new Properties(); - props.put(key, identifier); - return context.registerService(ReadyMarker.class.getName(), new ReadyMarker() { - }, props); - } - -} diff --git a/bundles/model/org.eclipse.smarthome.model.thing.tests/src/org/eclipse/smarthome/model/thing/tests/GenericThingProviderTest4.groovy b/bundles/model/org.eclipse.smarthome.model.thing.tests/src/org/eclipse/smarthome/model/thing/tests/GenericThingProviderTest4.groovy index 90e9181e4e9..eaabac3b77d 100644 --- a/bundles/model/org.eclipse.smarthome.model.thing.tests/src/org/eclipse/smarthome/model/thing/tests/GenericThingProviderTest4.groovy +++ b/bundles/model/org.eclipse.smarthome.model.thing.tests/src/org/eclipse/smarthome/model/thing/tests/GenericThingProviderTest4.groovy @@ -12,7 +12,7 @@ import static org.junit.Assert.* import static org.junit.matchers.JUnitMatchers.* import org.eclipse.smarthome.core.service.ReadyMarker -import org.eclipse.smarthome.core.service.ReadyUtil +import org.eclipse.smarthome.core.service.ReadyService import org.eclipse.smarthome.core.thing.Bridge import org.eclipse.smarthome.core.thing.ChannelUID import org.eclipse.smarthome.core.thing.Thing @@ -50,6 +50,7 @@ import org.osgi.service.component.ComponentContext @RunWith(Parameterized.class) class GenericThingProviderTest4 extends OSGiTest{ private TestHueThingTypeProvider thingTypeProvider + private ReadyService readyService private Bundle bundle private ThingHandlerFactory hueThingHandlerFactory private boolean finished @@ -101,6 +102,8 @@ class GenericThingProviderTest4 extends OSGiTest{ @Before public void setUp() { + readyService = getService ReadyService + assertThat readyService, is(notNullValue()) thingRegistry = getService ThingRegistry assertThat thingRegistry, is(notNullValue()) modelRepository = getService ModelRepository @@ -136,11 +139,9 @@ class GenericThingProviderTest4 extends OSGiTest{ private void removeReadyMarker() { waitForAssert { // wait for the XML processing to be finished, then remove the ready marker again - def ref = bundleContext.getServiceReferences(ReadyMarker.class.getName(), "(" + ReadyMarker.XML_THING_TYPE + "=" + bundle.getSymbolicName() + ")") - assertThat ref, is(notNullValue()) - def registration = ref.registration.getAt(0) - assertThat registration, is(notNullValue()) - registration.unregister() + ReadyMarker marker = new ReadyMarker("esh.xmlThingTypes", bundle.getSymbolicName()) + assertThat readyService.isReady(marker), is(true) + readyService.unmarkReady(marker); } } @@ -188,7 +189,7 @@ class GenericThingProviderTest4 extends OSGiTest{ private def finishLoading() { finished = true; assertThat bridgeInitializeCounter, is(0) - ReadyUtil.markAsReady(bundleContext, ReadyMarker.XML_THING_TYPE, bundle.getSymbolicName()) + readyService.markReady(new ReadyMarker("esh.xmlThingTypes", bundle.getSymbolicName())) } private def unload() { diff --git a/bundles/model/org.eclipse.smarthome.model.thing/OSGI-INF/genericthingprovider.xml b/bundles/model/org.eclipse.smarthome.model.thing/OSGI-INF/genericthingprovider.xml index f178ac91f8a..f9cd0fb2087 100644 --- a/bundles/model/org.eclipse.smarthome.model.thing/OSGI-INF/genericthingprovider.xml +++ b/bundles/model/org.eclipse.smarthome.model.thing/OSGI-INF/genericthingprovider.xml @@ -18,5 +18,5 @@ - + diff --git a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend index f1e626d0b10..b8de5b0df31 100644 --- a/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend +++ b/bundles/model/org.eclipse.smarthome.model.thing/src/org/eclipse/smarthome/model/thing/internal/GenericThingProvider.xtend @@ -23,6 +23,8 @@ import org.eclipse.smarthome.config.core.Configuration import org.eclipse.smarthome.core.common.registry.AbstractProvider import org.eclipse.smarthome.core.i18n.LocaleProvider import org.eclipse.smarthome.core.service.ReadyMarker +import org.eclipse.smarthome.core.service.ReadyMarkerFilter +import org.eclipse.smarthome.core.service.ReadyService import org.eclipse.smarthome.core.thing.Bridge import org.eclipse.smarthome.core.thing.Channel import org.eclipse.smarthome.core.thing.ChannelUID @@ -49,9 +51,9 @@ import org.eclipse.smarthome.model.thing.thing.ModelPropertyContainer import org.eclipse.smarthome.model.thing.thing.ModelThing import org.eclipse.smarthome.model.thing.thing.ThingModel import org.eclipse.xtend.lib.annotations.Data +import org.osgi.framework.FrameworkUtil import org.slf4j.Logger import org.slf4j.LoggerFactory -import org.osgi.framework.FrameworkUtil /** * {@link ThingProvider} implementation which computes *.things files. @@ -66,7 +68,7 @@ import org.osgi.framework.FrameworkUtil * @author Markus Rathgeb - Add locale provider support * */ -class GenericThingProvider extends AbstractProvider implements ThingProvider, ModelRepositoryChangeListener { +class GenericThingProvider extends AbstractProvider implements ThingProvider, ModelRepositoryChangeListener, ReadyService.ReadyTracker { private static final String XML_THING_TYPE = "esh.xmlThingTypes"; @@ -522,12 +524,19 @@ class GenericThingProvider extends AbstractProvider implements ThingProvi ] } - def protected void addReadyMarker(ReadyMarker readyMarker, Map properties) { - if (properties.containsKey(XML_THING_TYPE)) { - val bsn = properties.get(XML_THING_TYPE) as String - loadedXmlThingTypes.add(bsn) - bsn.handleXmlThingTypesLoaded - } + + def void setReadyService(ReadyService readyService) { + readyService.registerTracker(this, new ReadyMarkerFilter().withType(XML_THING_TYPE)); + } + + def void unsetReadyService(ReadyService readyService) { + readyService.unregisterTracker(this); + } + + override onReadyMarkerAdded(ReadyMarker readyMarker) { + val bsn = readyMarker.identifier + loadedXmlThingTypes.add(bsn) + bsn.handleXmlThingTypesLoaded } def private getBundleName(ThingHandlerFactory thingHandlerFactory) { @@ -542,11 +551,9 @@ class GenericThingProvider extends AbstractProvider implements ThingProvi ] } - def protected void removeReadyMarker(ReadyMarker readyMarker, Map properties) { - if (properties.containsKey(XML_THING_TYPE)) { - val bsn = properties.get(XML_THING_TYPE) as String - loadedXmlThingTypes.remove(bsn); - } + override onReadyMarkerRemoved(ReadyMarker readyMarker) { + val bsn = readyMarker.identifier + loadedXmlThingTypes.remove(bsn); } def private createThingsFromModelForThingHandlerFactory(String modelName, ThingHandlerFactory factory) { diff --git a/bundles/test/org.eclipse.smarthome.test/src/main/groovy/org/eclipse/smarthome/test/SyntheticBundleInstaller.java b/bundles/test/org.eclipse.smarthome.test/src/main/groovy/org/eclipse/smarthome/test/SyntheticBundleInstaller.java index 3abccef5289..15bd6d86a19 100644 --- a/bundles/test/org.eclipse.smarthome.test/src/main/groovy/org/eclipse/smarthome/test/SyntheticBundleInstaller.java +++ b/bundles/test/org.eclipse.smarthome.test/src/main/groovy/org/eclipse/smarthome/test/SyntheticBundleInstaller.java @@ -28,13 +28,13 @@ import org.apache.commons.io.IOUtils; import org.eclipse.smarthome.core.service.ReadyMarker; +import org.eclipse.smarthome.core.service.ReadyService; import org.junit.Assert; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; -import org.osgi.framework.InvalidSyntaxException; -import org.slf4j.LoggerFactory; +import org.osgi.framework.ServiceReference; import com.google.common.collect.ImmutableSet; @@ -275,26 +275,25 @@ public static void waitUntilLoadingFinished(BundleContext context, Bundle bundle } private static void waitForReadyMarker(BundleContext context, String marker, Bundle bundle) { - try { - if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) { - return; + if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) { + return; + } + long startTime = System.nanoTime(); + ServiceReference readyServiceRef = context.getServiceReference(ReadyService.class.getName()); + ReadyService readyService = (ReadyService) context.getService(readyServiceRef); + ReadyMarker expected = new ReadyMarker(marker, bundle.getSymbolicName()); + while (!readyService.isReady(expected)) { + if (System.nanoTime() - startTime > TimeUnit.SECONDS.toNanos(WAIT_TIMOUT)) { + Assert.fail(MessageFormat.format("Timout waiting for marker {0} at bundle {1}", marker, + bundle.getSymbolicName())); } - String filter = "(" + marker + "=" + bundle.getSymbolicName() + ")"; - long startTime = System.nanoTime(); - while (context.getServiceReferences(ReadyMarker.class.getName(), filter) == null) { - if (System.nanoTime() - startTime > TimeUnit.SECONDS.toNanos(WAIT_TIMOUT)) { - Assert.fail(MessageFormat.format("Timout waiting for marker {0} at bundle {1}", marker, - bundle.getSymbolicName())); - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } - } catch (InvalidSyntaxException e) { - LoggerFactory.getLogger(SyntheticBundleInstaller.class).error("Error looking up the ready marker", e); } + context.ungetService(readyServiceRef); } /**