diff --git a/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcher.java b/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcher.java index f4a3e7f02e4..a71f46eb6a4 100644 --- a/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcher.java +++ b/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/main/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcher.java @@ -24,10 +24,10 @@ import java.nio.file.Path; import java.nio.file.WatchEvent; import java.nio.file.WatchEvent.Kind; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -62,8 +62,8 @@ * @author Jonathan Gilbert - added dependency tracking & per-script start levels */ @Component(immediate = true) -public class ScriptFileWatcher extends AbstractWatchService - implements ReadyService.ReadyTracker, DependencyTracker.DependencyChangeListener { +public class ScriptFileWatcher extends AbstractWatchService implements ReadyService.ReadyTracker, + DependencyTracker.DependencyChangeListener, ScriptEngineManager.FactoryChangeListener { private static final String FILE_DIRECTORY = "automation" + File.separator + "jsr223"; private static final long RECHECK_INTERVAL = 20; @@ -75,10 +75,10 @@ public class ScriptFileWatcher extends AbstractWatchService private final ReadyService readyService; private @Nullable ScheduledExecutorService scheduler; - private Supplier executerFactory; + private Supplier executorFactory; - private final Set pending = new HashSet<>(); - private final Set loaded = new HashSet<>(); + private final Set pending = ConcurrentHashMap.newKeySet(); + private final Set loaded = ConcurrentHashMap.newKeySet(); private volatile int currentStartLevel = 0; @@ -89,15 +89,17 @@ public ScriptFileWatcher(final @Reference ScriptEngineManager manager, this.manager = manager; this.dependencyTracker = dependencyTracker; this.readyService = readyService; - this.executerFactory = () -> Executors + this.executorFactory = () -> Executors .newSingleThreadScheduledExecutor(new NamedThreadFactory("scriptwatcher")); + manager.addFactoryChangeListener(this); readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE)); } @Deactivate @Override public void deactivate() { + manager.removeFactoryChangeListener(this); readyService.unregisterTracker(this); ScheduledExecutorService localScheduler = scheduler; @@ -112,10 +114,10 @@ public void deactivate() { /** * Override the executor service. Can be used for testing. * - * @param executerFactory supplier of ScheduledExecutorService + * @param executorFactory supplier of ScheduledExecutorService */ - void setExecuterFactory(Supplier executerFactory) { - this.executerFactory = executerFactory; + void setExecutorFactory(Supplier executorFactory) { + this.executorFactory = executorFactory; } /** @@ -261,7 +263,7 @@ private synchronized void onStartLevelChanged(int newLevel) { if (previousLevel < StartLevelService.STARTLEVEL_MODEL) { // not yet started if (newLevel >= StartLevelService.STARTLEVEL_MODEL) { // start - ScheduledExecutorService localScheduler = executerFactory.get(); + ScheduledExecutorService localScheduler = executorFactory.get(); scheduler = localScheduler; localScheduler.submit(() -> importResources(new File(pathToWatch))); localScheduler.scheduleWithFixedDelay(() -> checkFiles(currentStartLevel), 0, RECHECK_INTERVAL, @@ -309,4 +311,16 @@ public synchronized void onReadyMarkerRemoved(ReadyMarker readyMarker) { onStartLevelChanged(newLevel); } } + + @Override + public void factoryAdded(@Nullable String scriptType) { + } + + @Override + public void factoryRemoved(@Nullable String scriptType) { + if (scriptType == null) { + return; + } + loaded.stream().filter(ref -> scriptType.equals(ref.getScriptType().get())).forEach(this::importFileWhenReady); + } } diff --git a/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcherTest.java b/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcherTest.java index b55cb72ebe3..023d20af6dd 100644 --- a/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcherTest.java +++ b/bundles/org.opensmarthouse.core.automation.module.script.rulesupport/src/test/java/org/openhab/core/automation/module/script/rulesupport/internal/loader/ScriptFileWatcherTest.java @@ -212,7 +212,7 @@ public void testLoadOneDefaultFileDelayedSupport() { ScheduledExecutorService scheduledExecutorService = spy( new DelegatingScheduledExecutorService(Executors.newSingleThreadScheduledExecutor())); ArgumentCaptor scheduledTask = ArgumentCaptor.forClass(Runnable.class); - scriptFileWatcher.setExecuterFactory(() -> scheduledExecutorService); + scriptFileWatcher.setExecutorFactory(() -> scheduledExecutorService); when(scriptEngineManager.isSupported("js")).thenReturn(false); ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class); diff --git a/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java b/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java index 46278e50d26..9b2332e0fe9 100644 --- a/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java +++ b/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/ScriptEngineManager.java @@ -69,4 +69,35 @@ void loadScript(String engineIdentifier, InputStreamReader scriptData, * @return true, if supported, else false */ boolean isSupported(String scriptType); + + /** + * Add a listener that is notified when a ScriptEngineFactory is added or removed + * + * @param listener an object that implements {@link FactoryChangeListener} + */ + void addFactoryChangeListener(FactoryChangeListener listener); + + /** + * Remove a listener that is notified when a ScriptEngineFactory is added or removed + * + * @param listener an object that implements {@link FactoryChangeListener} + */ + void removeFactoryChangeListener(FactoryChangeListener listener); + + interface FactoryChangeListener { + + /** + * Called by the {@link ScriptEngineManager} when a ScriptEngineFactory is added + * + * @param scriptType the script type supported by the newly added factory + */ + void factoryAdded(String scriptType); + + /** + * Called by the {@link ScriptEngineManager} when a ScriptEngineFactory is removed + * + * @param scriptType the script type supported by the removed factory + */ + void factoryRemoved(String scriptType); + } } diff --git a/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java b/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java index 83dad1115bd..18cc3ac794e 100644 --- a/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java +++ b/bundles/org.opensmarthouse.core.automation.module.script/src/main/java/org/openhab/core/automation/module/script/internal/ScriptEngineManagerImpl.java @@ -16,8 +16,10 @@ import java.io.InputStreamReader; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.script.Invocable; import javax.script.ScriptContext; @@ -54,6 +56,7 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager { private final Map customSupport = new HashMap<>(); private final Map genericSupport = new HashMap<>(); private final ScriptExtensionManager scriptExtensionManager; + private final Set listeners = new HashSet<>(); @Activate public ScriptEngineManagerImpl(final @Reference ScriptExtensionManager scriptExtensionManager) { @@ -70,6 +73,7 @@ public void addScriptEngineFactory(ScriptEngineFactory engineFactory) { } else { this.genericSupport.put(scriptType, engineFactory); } + listeners.forEach(listener -> listener.factoryAdded(scriptType)); } if (logger.isDebugEnabled()) { if (!scriptTypes.isEmpty()) { @@ -99,6 +103,7 @@ public void removeScriptEngineFactory(ScriptEngineFactory engineFactory) { } else { this.genericSupport.remove(scriptType, engineFactory); } + listeners.forEach(listener -> listener.factoryRemoved(scriptType)); } logger.debug("Removed {}", engineFactory.getClass().getSimpleName()); } @@ -248,4 +253,14 @@ private void addAttributeToScriptContext(ScriptEngine engine, String name, Objec scriptContext.setAttribute(name, value, ScriptContext.ENGINE_SCOPE); } + + @Override + public void addFactoryChangeListener(FactoryChangeListener listener) { + listeners.add(listener); + } + + @Override + public void removeFactoryChangeListener(FactoryChangeListener listener) { + listeners.remove(listener); + } }