[automation] implement a listener for ScriptEngineFactory changes (#2459)

* implement a listener for ScriptEngineFactory changes

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
pull/2462/head
J-N-K 2021-08-22 12:15:23 +02:00 committed by GitHub
parent d11a72272e
commit b02dfdc067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 12 deletions

View File

@ -24,10 +24,10 @@ import java.nio.charset.StandardCharsets;
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 @@ import org.slf4j.LoggerFactory;
* @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<ScheduledExecutorService> executerFactory;
private Supplier<ScheduledExecutorService> executorFactory;
private final Set<ScriptFileReference> pending = new HashSet<>();
private final Set<ScriptFileReference> loaded = new HashSet<>();
private final Set<ScriptFileReference> pending = ConcurrentHashMap.newKeySet();
private final Set<ScriptFileReference> loaded = ConcurrentHashMap.newKeySet();
private volatile int currentStartLevel = 0;
@ -89,15 +89,17 @@ public class ScriptFileWatcher extends AbstractWatchService
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 class ScriptFileWatcher extends AbstractWatchService
/**
* Override the executor service. Can be used for testing.
*
* @param executerFactory supplier of ScheduledExecutorService
* @param executorFactory supplier of ScheduledExecutorService
*/
void setExecuterFactory(Supplier<ScheduledExecutorService> executerFactory) {
this.executerFactory = executerFactory;
void setExecutorFactory(Supplier<ScheduledExecutorService> executorFactory) {
this.executorFactory = executorFactory;
}
/**
@ -261,7 +263,7 @@ public class ScriptFileWatcher extends AbstractWatchService
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 class ScriptFileWatcher extends AbstractWatchService
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);
}
}

View File

@ -212,7 +212,7 @@ class ScriptFileWatcherTest {
ScheduledExecutorService scheduledExecutorService = spy(
new DelegatingScheduledExecutorService(Executors.newSingleThreadScheduledExecutor()));
ArgumentCaptor<Runnable> scheduledTask = ArgumentCaptor.forClass(Runnable.class);
scriptFileWatcher.setExecuterFactory(() -> scheduledExecutorService);
scriptFileWatcher.setExecutorFactory(() -> scheduledExecutorService);
when(scriptEngineManager.isSupported("js")).thenReturn(false);
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);

View File

@ -69,4 +69,35 @@ public interface ScriptEngineManager {
* @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);
}
}

View File

@ -16,8 +16,10 @@ import static org.openhab.core.automation.module.script.ScriptEngineFactory.*;
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<String, ScriptEngineFactory> customSupport = new HashMap<>();
private final Map<String, ScriptEngineFactory> genericSupport = new HashMap<>();
private final ScriptExtensionManager scriptExtensionManager;
private final Set<FactoryChangeListener> listeners = new HashSet<>();
@Activate
public ScriptEngineManagerImpl(final @Reference ScriptExtensionManager scriptExtensionManager) {
@ -70,6 +73,7 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
} else {
this.genericSupport.put(scriptType, engineFactory);
}
listeners.forEach(listener -> listener.factoryAdded(scriptType));
}
if (logger.isDebugEnabled()) {
if (!scriptTypes.isEmpty()) {
@ -99,6 +103,7 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
} else {
this.genericSupport.remove(scriptType, engineFactory);
}
listeners.forEach(listener -> listener.factoryRemoved(scriptType));
}
logger.debug("Removed {}", engineFactory.getClass().getSimpleName());
}
@ -248,4 +253,14 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
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);
}
}