Skip to content

Commit

Permalink
[automation] implement a listener for ScriptEngineFactory changes (op…
Browse files Browse the repository at this point in the history
…enhab#2459)

* implement a listener for ScriptEngineFactory changes

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
  • Loading branch information
J-N-K authored and ghys committed Sep 9, 2021
1 parent ff7d7b6 commit 4e5eb77
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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;
Expand All @@ -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<ScheduledExecutorService> executerFactory) {
this.executerFactory = executerFactory;
void setExecutorFactory(Supplier<ScheduledExecutorService> executorFactory) {
this.executorFactory = executorFactory;
}

/**
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public void testLoadOneDefaultFileDelayedSupport() {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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()) {
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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);
}
}

0 comments on commit 4e5eb77

Please sign in to comment.