[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
parent
d11a72272e
commit
b02dfdc067
|
@ -24,10 +24,10 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.WatchEvent;
|
import java.nio.file.WatchEvent;
|
||||||
import java.nio.file.WatchEvent.Kind;
|
import java.nio.file.WatchEvent.Kind;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -62,8 +62,8 @@ import org.slf4j.LoggerFactory;
|
||||||
* @author Jonathan Gilbert - added dependency tracking & per-script start levels
|
* @author Jonathan Gilbert - added dependency tracking & per-script start levels
|
||||||
*/
|
*/
|
||||||
@Component(immediate = true)
|
@Component(immediate = true)
|
||||||
public class ScriptFileWatcher extends AbstractWatchService
|
public class ScriptFileWatcher extends AbstractWatchService implements ReadyService.ReadyTracker,
|
||||||
implements ReadyService.ReadyTracker, DependencyTracker.DependencyChangeListener {
|
DependencyTracker.DependencyChangeListener, ScriptEngineManager.FactoryChangeListener {
|
||||||
|
|
||||||
private static final String FILE_DIRECTORY = "automation" + File.separator + "jsr223";
|
private static final String FILE_DIRECTORY = "automation" + File.separator + "jsr223";
|
||||||
private static final long RECHECK_INTERVAL = 20;
|
private static final long RECHECK_INTERVAL = 20;
|
||||||
|
@ -75,10 +75,10 @@ public class ScriptFileWatcher extends AbstractWatchService
|
||||||
private final ReadyService readyService;
|
private final ReadyService readyService;
|
||||||
|
|
||||||
private @Nullable ScheduledExecutorService scheduler;
|
private @Nullable ScheduledExecutorService scheduler;
|
||||||
private Supplier<ScheduledExecutorService> executerFactory;
|
private Supplier<ScheduledExecutorService> executorFactory;
|
||||||
|
|
||||||
private final Set<ScriptFileReference> pending = new HashSet<>();
|
private final Set<ScriptFileReference> pending = ConcurrentHashMap.newKeySet();
|
||||||
private final Set<ScriptFileReference> loaded = new HashSet<>();
|
private final Set<ScriptFileReference> loaded = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
private volatile int currentStartLevel = 0;
|
private volatile int currentStartLevel = 0;
|
||||||
|
|
||||||
|
@ -89,15 +89,17 @@ public class ScriptFileWatcher extends AbstractWatchService
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.dependencyTracker = dependencyTracker;
|
this.dependencyTracker = dependencyTracker;
|
||||||
this.readyService = readyService;
|
this.readyService = readyService;
|
||||||
this.executerFactory = () -> Executors
|
this.executorFactory = () -> Executors
|
||||||
.newSingleThreadScheduledExecutor(new NamedThreadFactory("scriptwatcher"));
|
.newSingleThreadScheduledExecutor(new NamedThreadFactory("scriptwatcher"));
|
||||||
|
|
||||||
|
manager.addFactoryChangeListener(this);
|
||||||
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE));
|
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deactivate
|
@Deactivate
|
||||||
@Override
|
@Override
|
||||||
public void deactivate() {
|
public void deactivate() {
|
||||||
|
manager.removeFactoryChangeListener(this);
|
||||||
readyService.unregisterTracker(this);
|
readyService.unregisterTracker(this);
|
||||||
|
|
||||||
ScheduledExecutorService localScheduler = scheduler;
|
ScheduledExecutorService localScheduler = scheduler;
|
||||||
|
@ -112,10 +114,10 @@ public class ScriptFileWatcher extends AbstractWatchService
|
||||||
/**
|
/**
|
||||||
* Override the executor service. Can be used for testing.
|
* Override the executor service. Can be used for testing.
|
||||||
*
|
*
|
||||||
* @param executerFactory supplier of ScheduledExecutorService
|
* @param executorFactory supplier of ScheduledExecutorService
|
||||||
*/
|
*/
|
||||||
void setExecuterFactory(Supplier<ScheduledExecutorService> executerFactory) {
|
void setExecutorFactory(Supplier<ScheduledExecutorService> executorFactory) {
|
||||||
this.executerFactory = executerFactory;
|
this.executorFactory = executorFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -261,7 +263,7 @@ public class ScriptFileWatcher extends AbstractWatchService
|
||||||
|
|
||||||
if (previousLevel < StartLevelService.STARTLEVEL_MODEL) { // not yet started
|
if (previousLevel < StartLevelService.STARTLEVEL_MODEL) { // not yet started
|
||||||
if (newLevel >= StartLevelService.STARTLEVEL_MODEL) { // start
|
if (newLevel >= StartLevelService.STARTLEVEL_MODEL) { // start
|
||||||
ScheduledExecutorService localScheduler = executerFactory.get();
|
ScheduledExecutorService localScheduler = executorFactory.get();
|
||||||
scheduler = localScheduler;
|
scheduler = localScheduler;
|
||||||
localScheduler.submit(() -> importResources(new File(pathToWatch)));
|
localScheduler.submit(() -> importResources(new File(pathToWatch)));
|
||||||
localScheduler.scheduleWithFixedDelay(() -> checkFiles(currentStartLevel), 0, RECHECK_INTERVAL,
|
localScheduler.scheduleWithFixedDelay(() -> checkFiles(currentStartLevel), 0, RECHECK_INTERVAL,
|
||||||
|
@ -309,4 +311,16 @@ public class ScriptFileWatcher extends AbstractWatchService
|
||||||
onStartLevelChanged(newLevel);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -212,7 +212,7 @@ class ScriptFileWatcherTest {
|
||||||
ScheduledExecutorService scheduledExecutorService = spy(
|
ScheduledExecutorService scheduledExecutorService = spy(
|
||||||
new DelegatingScheduledExecutorService(Executors.newSingleThreadScheduledExecutor()));
|
new DelegatingScheduledExecutorService(Executors.newSingleThreadScheduledExecutor()));
|
||||||
ArgumentCaptor<Runnable> scheduledTask = ArgumentCaptor.forClass(Runnable.class);
|
ArgumentCaptor<Runnable> scheduledTask = ArgumentCaptor.forClass(Runnable.class);
|
||||||
scriptFileWatcher.setExecuterFactory(() -> scheduledExecutorService);
|
scriptFileWatcher.setExecutorFactory(() -> scheduledExecutorService);
|
||||||
|
|
||||||
when(scriptEngineManager.isSupported("js")).thenReturn(false);
|
when(scriptEngineManager.isSupported("js")).thenReturn(false);
|
||||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||||
|
|
|
@ -69,4 +69,35 @@ public interface ScriptEngineManager {
|
||||||
* @return true, if supported, else false
|
* @return true, if supported, else false
|
||||||
*/
|
*/
|
||||||
boolean isSupported(String scriptType);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@ import static org.openhab.core.automation.module.script.ScriptEngineFactory.*;
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.script.Invocable;
|
import javax.script.Invocable;
|
||||||
import javax.script.ScriptContext;
|
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> customSupport = new HashMap<>();
|
||||||
private final Map<String, ScriptEngineFactory> genericSupport = new HashMap<>();
|
private final Map<String, ScriptEngineFactory> genericSupport = new HashMap<>();
|
||||||
private final ScriptExtensionManager scriptExtensionManager;
|
private final ScriptExtensionManager scriptExtensionManager;
|
||||||
|
private final Set<FactoryChangeListener> listeners = new HashSet<>();
|
||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public ScriptEngineManagerImpl(final @Reference ScriptExtensionManager scriptExtensionManager) {
|
public ScriptEngineManagerImpl(final @Reference ScriptExtensionManager scriptExtensionManager) {
|
||||||
|
@ -70,6 +73,7 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
|
||||||
} else {
|
} else {
|
||||||
this.genericSupport.put(scriptType, engineFactory);
|
this.genericSupport.put(scriptType, engineFactory);
|
||||||
}
|
}
|
||||||
|
listeners.forEach(listener -> listener.factoryAdded(scriptType));
|
||||||
}
|
}
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
if (!scriptTypes.isEmpty()) {
|
if (!scriptTypes.isEmpty()) {
|
||||||
|
@ -99,6 +103,7 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
|
||||||
} else {
|
} else {
|
||||||
this.genericSupport.remove(scriptType, engineFactory);
|
this.genericSupport.remove(scriptType, engineFactory);
|
||||||
}
|
}
|
||||||
|
listeners.forEach(listener -> listener.factoryRemoved(scriptType));
|
||||||
}
|
}
|
||||||
logger.debug("Removed {}", engineFactory.getClass().getSimpleName());
|
logger.debug("Removed {}", engineFactory.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
@ -248,4 +253,14 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
|
||||||
|
|
||||||
scriptContext.setAttribute(name, value, ScriptContext.ENGINE_SCOPE);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue