mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 21:31:53 +01:00
Refactor AbstractScriptFileWatcher (#3255)
Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
parent
a09ad3d37c
commit
ecbb854e03
@ -25,6 +25,12 @@
|
||||
<artifactId>org.openhab.core.automation.module.script</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.core.bundles</groupId>
|
||||
<artifactId>org.openhab.core.test</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.automation.module.script.rulesupport.internal.loader.collection;
|
||||
package org.openhab.core.automation.module.script.rulesupport.internal.loader;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.automation.module.script.ScriptEngineManager;
|
||||
import org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.service.StartLevelService;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
@ -37,8 +38,8 @@ public class DefaultScriptFileWatcher extends AbstractScriptFileWatcher {
|
||||
|
||||
@Activate
|
||||
public DefaultScriptFileWatcher(final @Reference ScriptEngineManager manager,
|
||||
final @Reference ReadyService readyService) {
|
||||
super(manager, readyService, FILE_DIRECTORY);
|
||||
final @Reference ReadyService readyService, final @Reference StartLevelService startLevelService) {
|
||||
super(manager, readyService, startLevelService, FILE_DIRECTORY);
|
||||
}
|
||||
|
||||
@Activate
|
||||
|
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.automation.module.script.rulesupport.internal.loader;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Script File wrapper offering various methods to inspect the script
|
||||
*
|
||||
* @author Jonathan Gilbert - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ScriptFileReference implements Comparable<ScriptFileReference> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptFileReference.class);
|
||||
|
||||
private final AtomicBoolean queued = new AtomicBoolean();
|
||||
private final AtomicBoolean loaded = new AtomicBoolean();
|
||||
|
||||
private final Path scriptFilePath;
|
||||
private final String scriptType;
|
||||
private final int startLevel;
|
||||
|
||||
/**
|
||||
* A {@link ScriptFileReference} with a given scriptType
|
||||
*
|
||||
* @param scriptFilePath the {@link Path} of the file
|
||||
* @param scriptType the script type
|
||||
* @param startLevel the system start level required for this script
|
||||
*/
|
||||
public ScriptFileReference(Path scriptFilePath, String scriptType, int startLevel) {
|
||||
this.scriptFilePath = scriptFilePath;
|
||||
this.scriptType = scriptType;
|
||||
this.startLevel = startLevel;
|
||||
}
|
||||
|
||||
public String getScriptIdentifier() {
|
||||
return getScriptIdentifier(scriptFilePath);
|
||||
}
|
||||
|
||||
public Path getScriptFilePath() {
|
||||
return scriptFilePath;
|
||||
}
|
||||
|
||||
public String getScriptType() {
|
||||
return scriptType;
|
||||
}
|
||||
|
||||
public int getStartLevel() {
|
||||
return startLevel;
|
||||
}
|
||||
|
||||
public AtomicBoolean getLoadedStatus() {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public AtomicBoolean getQueueStatus() {
|
||||
return queued;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ScriptFileReference other) {
|
||||
int startLevelCompare = Integer.compare(startLevel, other.startLevel);
|
||||
if (startLevelCompare != 0) {
|
||||
return startLevelCompare;
|
||||
}
|
||||
|
||||
String name1 = scriptFilePath.getFileName().toString();
|
||||
LOGGER.trace("o1 [{}], name1 [{}]", scriptFilePath, name1);
|
||||
|
||||
String name2 = other.scriptFilePath.getFileName().toString();
|
||||
LOGGER.trace("o2 [{}], name2 [{}]", other.scriptFilePath, name2);
|
||||
|
||||
int nameCompare = name1.compareToIgnoreCase(name2);
|
||||
if (nameCompare != 0) {
|
||||
return nameCompare;
|
||||
} else {
|
||||
return scriptFilePath.getParent().toString()
|
||||
.compareToIgnoreCase(other.scriptFilePath.getParent().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ScriptFileReference that = (ScriptFileReference) o;
|
||||
return scriptFilePath.equals(that.scriptFilePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(scriptFilePath, scriptType, startLevel);
|
||||
}
|
||||
|
||||
public static String getScriptIdentifier(Path scriptFilePath) {
|
||||
return scriptFilePath.toString();
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ import java.util.function.Consumer;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.automation.module.script.ScriptDependencyTracker;
|
||||
import org.openhab.core.automation.module.script.rulesupport.internal.loader.collection.BidiSetBag;
|
||||
import org.openhab.core.automation.module.script.rulesupport.internal.loader.BidiSetBag;
|
||||
import org.openhab.core.service.AbstractWatchService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -14,27 +14,29 @@ package org.openhab.core.automation.module.script.rulesupport.loader;
|
||||
|
||||
import static java.nio.file.StandardWatchEventKinds.*;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.nio.file.WatchEvent.Kind;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
@ -44,6 +46,7 @@ import org.openhab.core.OpenHAB;
|
||||
import org.openhab.core.automation.module.script.ScriptDependencyTracker;
|
||||
import org.openhab.core.automation.module.script.ScriptEngineContainer;
|
||||
import org.openhab.core.automation.module.script.ScriptEngineManager;
|
||||
import org.openhab.core.automation.module.script.rulesupport.internal.loader.ScriptFileReference;
|
||||
import org.openhab.core.common.NamedThreadFactory;
|
||||
import org.openhab.core.service.AbstractWatchService;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
@ -55,137 +58,145 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AbstractScriptFileWatcher} is default implementation for watching a directory for files. If a new/modified
|
||||
* file is detected, the script
|
||||
* is read and passed to the {@link ScriptEngineManager}. It needs to be sub-classed for actual use.
|
||||
* file is detected, the script is read and passed to the {@link ScriptEngineManager}. It needs to be sub-classed for
|
||||
* actual use.
|
||||
*
|
||||
* @author Simon Merschjohann - Initial contribution
|
||||
* @author Kai Kreuzer - improved logging and removed thread pool
|
||||
* @author Jonathan Gilbert - added dependency tracking & per-script start levels; made reusable
|
||||
* @author Jan N. Klug - Refactored dependy tracking to script engine factories
|
||||
* @author Jan N. Klug - Refactored dependency tracking to script engine factories
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractScriptFileWatcher extends AbstractWatchService implements ReadyService.ReadyTracker,
|
||||
ScriptDependencyTracker.Listener, ScriptEngineManager.FactoryChangeListener {
|
||||
ScriptDependencyTracker.Listener, ScriptEngineManager.FactoryChangeListener, ScriptFileWatcher {
|
||||
|
||||
private static final long RECHECK_INTERVAL = 20;
|
||||
private static final Set<String> EXCLUDED_FILE_EXTENSIONS = Set.of("txt", "old", "example", "backup", "md", "swp",
|
||||
"tmp", "bak");
|
||||
|
||||
private static final List<Pattern> START_LEVEL_PATTERNS = List.of( //
|
||||
Pattern.compile(".*/sl(\\d{2})/[^/]+"), // script in immediate slXX directory
|
||||
Pattern.compile(".*/[^/]+\\.sl(\\d{2})\\.[^/.]+") // script named <name>.slXX.<ext>
|
||||
);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractScriptFileWatcher.class);
|
||||
|
||||
private final ScriptEngineManager manager;
|
||||
private final ReadyService readyService;
|
||||
|
||||
private @Nullable ScheduledExecutorService scheduler;
|
||||
private Supplier<ScheduledExecutorService> executorFactory;
|
||||
protected ScheduledExecutorService scheduler;
|
||||
|
||||
private final Set<ScriptFileReference> pending = ConcurrentHashMap.newKeySet();
|
||||
private final Set<ScriptFileReference> loaded = ConcurrentHashMap.newKeySet();
|
||||
private final Map<String, ScriptFileReference> scriptMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, Lock> scriptLockMap = new ConcurrentHashMap<>();
|
||||
private final CompletableFuture<@Nullable Void> initialized = new CompletableFuture<>();
|
||||
|
||||
private volatile int currentStartLevel = 0;
|
||||
|
||||
public AbstractScriptFileWatcher(final ScriptEngineManager manager, final ReadyService readyService,
|
||||
final String fileDirectory) {
|
||||
final StartLevelService startLevelService, final String fileDirectory) {
|
||||
super(OpenHAB.getConfigFolder() + File.separator + fileDirectory);
|
||||
this.manager = manager;
|
||||
this.readyService = readyService;
|
||||
this.executorFactory = () -> Executors
|
||||
.newSingleThreadScheduledExecutor(new NamedThreadFactory("scriptwatcher"));
|
||||
|
||||
manager.addFactoryChangeListener(this);
|
||||
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE));
|
||||
|
||||
this.scheduler = getScheduler();
|
||||
|
||||
currentStartLevel = startLevelService.getStartLevel();
|
||||
if (currentStartLevel > StartLevelService.STARTLEVEL_MODEL) {
|
||||
initialImport();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
// TODO: needed to initialize underlying AbstractWatchService, should be removed when we switch to PR
|
||||
// openhab-core#3004
|
||||
super.activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overridden by subclasses (e.g. for testing)
|
||||
*
|
||||
* @return a {@link ScheduledExecutorService}
|
||||
*/
|
||||
protected ScheduledExecutorService getScheduler() {
|
||||
return Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("scriptwatcher"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
manager.removeFactoryChangeListener(this);
|
||||
readyService.unregisterTracker(this);
|
||||
|
||||
ScheduledExecutorService localScheduler = scheduler;
|
||||
if (localScheduler != null) {
|
||||
localScheduler.shutdownNow();
|
||||
scheduler = null;
|
||||
}
|
||||
|
||||
super.deactivate();
|
||||
|
||||
CompletableFuture.allOf(
|
||||
Set.copyOf(scriptMap.keySet()).stream().map(this::removeFile).toArray(CompletableFuture<?>[]::new))
|
||||
.thenRun(scheduler::shutdownNow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activate() {
|
||||
File directory = new File(pathToWatch);
|
||||
if (!directory.exists()) {
|
||||
if (!directory.mkdirs()) {
|
||||
logger.warn("Failed to create watched directory: {}", pathToWatch);
|
||||
}
|
||||
} else if (directory.isFile()) {
|
||||
logger.warn("Trying to watch directory {}, however it is a file", pathToWatch);
|
||||
}
|
||||
|
||||
super.activate();
|
||||
public CompletableFuture<@Nullable Void> ifInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the executor service. Can be used for testing.
|
||||
* Get the scriptType (file-extension or MIME-type) for a given file
|
||||
* <p />
|
||||
* The scriptType is determined by the file extension. The extensions in {@link #EXCLUDED_FILE_EXTENSIONS} are
|
||||
* ignored. Implementations should override this
|
||||
* method if they provide a MIME-type instead of the file extension.
|
||||
*
|
||||
* @param executorFactory supplier of ScheduledExecutorService
|
||||
* @param scriptFilePath the {@link Path} to the script
|
||||
* @return an {@link Optional<String>} containing the script type
|
||||
*/
|
||||
void setExecutorFactory(Supplier<ScheduledExecutorService> executorFactory) {
|
||||
this.executorFactory = executorFactory;
|
||||
protected Optional<String> getScriptType(Path scriptFilePath) {
|
||||
String fileName = scriptFilePath.toString();
|
||||
int index = fileName.lastIndexOf(".");
|
||||
if (index == -1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String fileExtension = fileName.substring(index + 1);
|
||||
|
||||
// ignore known file extensions for "temp" files
|
||||
if (EXCLUDED_FILE_EXTENSIONS.contains(fileExtension) || fileExtension.endsWith("~")) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(fileExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes initial import of resources.
|
||||
* Gets the individual start level for a given file
|
||||
* <p />
|
||||
* The start level is derived from the name and location of
|
||||
* the file by {@link #START_LEVEL_PATTERNS}. If no match is found, {@link StartLevelService#STARTLEVEL_RULEENGINE}
|
||||
* is used.
|
||||
*
|
||||
* @param rootDirectory the root directory to import initial resources from
|
||||
* @param scriptFilePath the {@link Path} to the script
|
||||
* @return the start level for this script
|
||||
*/
|
||||
private void importInitialResources(File rootDirectory) {
|
||||
if (rootDirectory.exists()) {
|
||||
File[] files = rootDirectory.listFiles();
|
||||
if (files != null) {
|
||||
Collection<ScriptFileReference> resources = new TreeSet<>();
|
||||
for (File f : files) {
|
||||
if (!f.isHidden()) {
|
||||
resources.addAll(collectResources(f));
|
||||
}
|
||||
}
|
||||
resources.forEach(this::importFileWhenReady);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all resources from the specified file or directory,
|
||||
* possibly including subdirectories.
|
||||
*
|
||||
* The results will be sorted.
|
||||
*
|
||||
* @param file the file or directory to import resources from
|
||||
*/
|
||||
private Collection<ScriptFileReference> collectResources(File file) {
|
||||
Collection<ScriptFileReference> resources = new TreeSet<>();
|
||||
if (!file.exists()) {
|
||||
return resources;
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
if (watchSubDirectories()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File f : files) {
|
||||
if (!f.isHidden()) {
|
||||
resources.addAll(collectResources(f));
|
||||
}
|
||||
}
|
||||
protected int getStartLevel(Path scriptFilePath) {
|
||||
for (Pattern p : START_LEVEL_PATTERNS) {
|
||||
Matcher m = p.matcher(scriptFilePath.toString());
|
||||
if (m.find() && m.groupCount() > 0) {
|
||||
try {
|
||||
return Integer.parseInt(m.group(1));
|
||||
} catch (NumberFormatException nfe) {
|
||||
logger.warn("Extracted start level {} from {}, but it's not an integer. Ignoring.", m.group(1),
|
||||
scriptFilePath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
URL url = file.toURI().toURL();
|
||||
resources.add(new ScriptFileReference(url));
|
||||
} catch (MalformedURLException e) {
|
||||
// can't happen for the 'file' protocol handler with a correctly formatted URI
|
||||
logger.debug("Can't create a URL", e);
|
||||
}
|
||||
}
|
||||
return resources;
|
||||
|
||||
return StartLevelService.STARTLEVEL_RULEENGINE;
|
||||
}
|
||||
|
||||
private List<Path> listFiles(Path path, boolean includeSubDirectory) {
|
||||
try (Stream<Path> stream = Files.walk(path, includeSubDirectory ? Integer.MAX_VALUE : 1)) {
|
||||
return stream.filter(file -> !Files.isDirectory(file)).toList();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -202,180 +213,194 @@ public abstract class AbstractScriptFileWatcher extends AbstractWatchService imp
|
||||
protected void processWatchEvent(@Nullable WatchEvent<?> event, @Nullable Kind<?> kind, @Nullable Path path) {
|
||||
File file = path.toFile();
|
||||
if (!file.isHidden()) {
|
||||
try {
|
||||
if (ENTRY_DELETE.equals(kind)) {
|
||||
if (file.isDirectory()) {
|
||||
if (watchSubDirectories()) {
|
||||
synchronized (this) {
|
||||
String prefix = file.toURI().toURL().getPath();
|
||||
Set<ScriptFileReference> toRemove = loaded.stream()
|
||||
.filter(f -> f.getScriptFileURL().getFile().startsWith(prefix))
|
||||
.collect(Collectors.toSet());
|
||||
toRemove.forEach(this::removeFile);
|
||||
}
|
||||
if (ENTRY_DELETE.equals(kind)) {
|
||||
if (file.isDirectory()) {
|
||||
if (watchSubDirectories()) {
|
||||
synchronized (this) {
|
||||
String prefix = path.getParent().toString();
|
||||
Set<String> toRemove = scriptMap.keySet().stream().filter(ref -> ref.startsWith(prefix))
|
||||
.collect(Collectors.toSet());
|
||||
toRemove.forEach(this::removeFile);
|
||||
}
|
||||
} else {
|
||||
removeFile(new ScriptFileReference(file.toURI().toURL()));
|
||||
}
|
||||
} else {
|
||||
removeFile(ScriptFileReference.getScriptIdentifier(file.toPath()));
|
||||
}
|
||||
}
|
||||
|
||||
if (file.canRead() && (ENTRY_CREATE.equals(kind) || ENTRY_MODIFY.equals(kind))) {
|
||||
collectResources(file).forEach(this::importFileWhenReady);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
logger.error("malformed", e);
|
||||
if (file.canRead() && (ENTRY_CREATE.equals(kind) || ENTRY_MODIFY.equals(kind))) {
|
||||
addFiles(listFiles(file.toPath(), watchSubDirectories()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFile(ScriptFileReference ref) {
|
||||
dequeueUrl(ref);
|
||||
private CompletableFuture<Void> addFiles(Collection<Path> files) {
|
||||
return CompletableFuture.allOf(files.stream().map(this::getScriptFileReference).filter(Optional::isPresent)
|
||||
.map(Optional::get).sorted().map(this::addScriptFileReference).toArray(CompletableFuture<?>[]::new));
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> addScriptFileReference(ScriptFileReference newRef) {
|
||||
ScriptFileReference ref = scriptMap.computeIfAbsent(newRef.getScriptIdentifier(), k -> newRef);
|
||||
// check if we are ready to load the script, otherwise we don't need to queue it
|
||||
if (currentStartLevel >= ref.getStartLevel() && !ref.getQueueStatus().getAndSet(true)) {
|
||||
return importFileWhenReady(ref.getScriptIdentifier());
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
private Optional<ScriptFileReference> getScriptFileReference(Path path) {
|
||||
return getScriptType(path).map(scriptType -> new ScriptFileReference(path, scriptType, getStartLevel(path)));
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> removeFile(String scriptIdentifier) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
Lock lock = getLockForScript(scriptIdentifier);
|
||||
try {
|
||||
ScriptFileReference ref = scriptMap.remove(scriptIdentifier);
|
||||
|
||||
if (ref == null) {
|
||||
logger.warn("Failed to unload script '{}': script reference not found.", scriptIdentifier);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ref.getLoadedStatus().get()) {
|
||||
manager.removeEngine(scriptIdentifier);
|
||||
logger.debug("Unloaded script '{}'", scriptIdentifier);
|
||||
} else {
|
||||
logger.debug("Dequeued script '{}'", scriptIdentifier);
|
||||
}
|
||||
} finally {
|
||||
scriptLockMap.remove(scriptIdentifier);
|
||||
lock.unlock();
|
||||
}
|
||||
}, scheduler);
|
||||
}
|
||||
|
||||
private synchronized Lock getLockForScript(String scriptIdentifier) {
|
||||
Lock lock = scriptLockMap.computeIfAbsent(scriptIdentifier, k -> new ReentrantLock());
|
||||
lock.lock();
|
||||
|
||||
return lock;
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> importFileWhenReady(String scriptIdentifier) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
ScriptFileReference ref = scriptMap.get(scriptIdentifier);
|
||||
if (ref == null) {
|
||||
logger.warn("Failed to import script '{}': script reference not found", scriptIdentifier);
|
||||
return;
|
||||
}
|
||||
|
||||
ref.getQueueStatus().set(false);
|
||||
|
||||
Lock lock = getLockForScript(scriptIdentifier);
|
||||
try {
|
||||
if (ref.getLoadedStatus().get()) {
|
||||
manager.removeEngine(scriptIdentifier);
|
||||
logger.debug("Unloaded script '{}'", scriptIdentifier);
|
||||
}
|
||||
|
||||
if (manager.isSupported(ref.getScriptType()) && ref.getStartLevel() <= currentStartLevel) {
|
||||
logger.info("(Re-)Loading script '{}'", scriptIdentifier);
|
||||
if (createAndLoad(ref)) {
|
||||
ref.getLoadedStatus().set(true);
|
||||
} else {
|
||||
// make sure script engine is successfully closed and the loading is re-tried
|
||||
manager.removeEngine(ref.getScriptIdentifier());
|
||||
ref.getLoadedStatus().set(false);
|
||||
}
|
||||
} else {
|
||||
ref.getLoadedStatus().set(false);
|
||||
logger.debug("Enqueued script '{}'", ref.getScriptIdentifier());
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}, scheduler);
|
||||
}
|
||||
|
||||
private boolean createAndLoad(ScriptFileReference ref) {
|
||||
String scriptIdentifier = ref.getScriptIdentifier();
|
||||
|
||||
manager.removeEngine(scriptIdentifier);
|
||||
loaded.remove(ref);
|
||||
}
|
||||
|
||||
private synchronized void importFileWhenReady(ScriptFileReference ref) {
|
||||
if (loaded.contains(ref)) {
|
||||
this.removeFile(ref); // if already loaded, remove first
|
||||
}
|
||||
|
||||
Optional<String> scriptType = ref.getScriptType();
|
||||
|
||||
scriptType.ifPresent(type -> {
|
||||
if (ref.getStartLevel() <= currentStartLevel && manager.isSupported(type)) {
|
||||
importFile(ref);
|
||||
} else {
|
||||
enqueue(ref);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void importFile(ScriptFileReference ref) {
|
||||
if (createAndLoad(ref)) {
|
||||
loaded.add(ref);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean createAndLoad(ScriptFileReference ref) {
|
||||
String fileName = ref.getScriptFileURL().getFile();
|
||||
Optional<String> scriptType = ref.getScriptType();
|
||||
assert scriptType.isPresent();
|
||||
|
||||
try (InputStreamReader reader = new InputStreamReader(
|
||||
new BufferedInputStream(ref.getScriptFileURL().openStream()), StandardCharsets.UTF_8)) {
|
||||
logger.info("Loading script '{}'", fileName);
|
||||
|
||||
String scriptIdentifier = ref.getScriptIdentifier();
|
||||
|
||||
ScriptEngineContainer container = manager.createScriptEngine(scriptType.get(), scriptIdentifier);
|
||||
|
||||
try (InputStreamReader reader = new InputStreamReader(Files.newInputStream(ref.getScriptFilePath()),
|
||||
StandardCharsets.UTF_8)) {
|
||||
ScriptEngineContainer container = manager.createScriptEngine(ref.getScriptType(),
|
||||
ref.getScriptIdentifier());
|
||||
if (container != null) {
|
||||
container.getScriptEngine().put(ScriptEngine.FILENAME, fileName);
|
||||
manager.loadScript(container.getIdentifier(), reader);
|
||||
|
||||
logger.debug("Script loaded: {}", fileName);
|
||||
return true;
|
||||
} else {
|
||||
logger.error("Script loading error, ignoring file: {}", fileName);
|
||||
container.getScriptEngine().put(ScriptEngine.FILENAME, scriptIdentifier);
|
||||
if (manager.loadScript(container.getIdentifier(), reader)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
logger.warn("Script loading error, ignoring file '{}'", scriptIdentifier);
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to load file '{}': {}", ref.getScriptFileURL().getFile(), e.getMessage());
|
||||
logger.warn("Failed to load file '{}': {}", ref.getScriptFilePath(), e.getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void enqueue(ScriptFileReference ref) {
|
||||
synchronized (pending) {
|
||||
pending.add(ref);
|
||||
}
|
||||
private void initialImport() {
|
||||
File directory = new File(pathToWatch);
|
||||
|
||||
logger.debug("Enqueued {}", ref.getScriptIdentifier());
|
||||
}
|
||||
|
||||
private void dequeueUrl(ScriptFileReference ref) {
|
||||
synchronized (pending) {
|
||||
pending.remove(ref);
|
||||
}
|
||||
|
||||
logger.debug("Dequeued {}", ref.getScriptIdentifier());
|
||||
}
|
||||
|
||||
private void checkFiles(int forLevel) {
|
||||
List<ScriptFileReference> newlySupported;
|
||||
|
||||
synchronized (pending) {
|
||||
newlySupported = pending.stream()
|
||||
.filter(ref -> manager.isSupported(ref.getScriptType().get()) && forLevel >= ref.getStartLevel())
|
||||
.sorted().collect(Collectors.toList());
|
||||
pending.removeAll(newlySupported);
|
||||
}
|
||||
|
||||
newlySupported.forEach(this::importFileWhenReady);
|
||||
}
|
||||
|
||||
private synchronized void onStartLevelChanged(int newLevel) {
|
||||
int previousLevel = currentStartLevel;
|
||||
currentStartLevel = newLevel;
|
||||
|
||||
if (previousLevel < StartLevelService.STARTLEVEL_MODEL) { // not yet started
|
||||
if (newLevel >= StartLevelService.STARTLEVEL_MODEL) { // start
|
||||
ScheduledExecutorService localScheduler = executorFactory.get();
|
||||
scheduler = localScheduler;
|
||||
localScheduler.submit(() -> importInitialResources(new File(pathToWatch)));
|
||||
localScheduler.scheduleWithFixedDelay(() -> checkFiles(currentStartLevel), 0, RECHECK_INTERVAL,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
} else { // already started
|
||||
assert scheduler != null;
|
||||
if (newLevel < StartLevelService.STARTLEVEL_MODEL) { // stop
|
||||
scheduler.shutdown();
|
||||
scheduler = null;
|
||||
} else if (newLevel > previousLevel) {
|
||||
scheduler.submit(() -> checkFiles(newLevel));
|
||||
if (!directory.exists()) {
|
||||
if (!directory.mkdirs()) {
|
||||
logger.warn("Failed to create watched directory: {}", pathToWatch);
|
||||
}
|
||||
} else if (directory.isFile()) {
|
||||
logger.warn("Trying to watch directory {}, however it is a file", pathToWatch);
|
||||
}
|
||||
|
||||
addFiles(listFiles(directory.toPath(), watchSubDirectories())).thenRun(() -> initialized.complete(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDependencyChange(@Nullable String scriptId) {
|
||||
logger.debug("Reimporting {}...", scriptId);
|
||||
try {
|
||||
ScriptFileReference scriptFileReference = new ScriptFileReference(new URL(scriptId));
|
||||
if (loaded.contains(scriptFileReference)) {
|
||||
importFileWhenReady(scriptFileReference);
|
||||
}
|
||||
} catch (MalformedURLException ignored) {
|
||||
public void onDependencyChange(String scriptIdentifier) {
|
||||
logger.debug("Reimporting {}...", scriptIdentifier);
|
||||
ScriptFileReference ref = scriptMap.get(scriptIdentifier);
|
||||
if (ref != null && !ref.getQueueStatus().getAndSet(true)) {
|
||||
importFileWhenReady(scriptIdentifier);
|
||||
}
|
||||
logger.debug("Ignoring dependency change for {} as it is no file or not loaded by this file watcher", scriptId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onReadyMarkerAdded(ReadyMarker readyMarker) {
|
||||
int newLevel = Integer.parseInt(readyMarker.getIdentifier());
|
||||
int previousLevel = currentStartLevel;
|
||||
currentStartLevel = Integer.parseInt(readyMarker.getIdentifier());
|
||||
|
||||
if (newLevel > currentStartLevel) {
|
||||
onStartLevelChanged(newLevel);
|
||||
if (currentStartLevel < StartLevelService.STARTLEVEL_STATES) {
|
||||
// ignore start level less than 30
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentStartLevel == StartLevelService.STARTLEVEL_STATES) {
|
||||
initialImport();
|
||||
} else {
|
||||
scriptMap.values().stream().sorted()
|
||||
.filter(ref -> needsStartLevelProcessing(ref, previousLevel, currentStartLevel))
|
||||
.forEach(ref -> importFileWhenReady(ref.getScriptIdentifier()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PMD.EmptyWhileStmt")
|
||||
public synchronized void onReadyMarkerRemoved(ReadyMarker readyMarker) {
|
||||
int newLevel = Integer.parseInt(readyMarker.getIdentifier());
|
||||
private boolean needsStartLevelProcessing(ScriptFileReference ref, int previousLevel, int newLevel) {
|
||||
int refStartLevel = ref.getStartLevel();
|
||||
return !ref.getLoadedStatus().get() && newLevel >= refStartLevel && previousLevel < refStartLevel
|
||||
&& !ref.getQueueStatus().getAndSet(true);
|
||||
}
|
||||
|
||||
if (currentStartLevel > newLevel) {
|
||||
while (newLevel-- > 0 && !readyService
|
||||
.isReady(new ReadyMarker(StartLevelService.STARTLEVEL_MARKER_TYPE, Integer.toString(newLevel)))) {
|
||||
}
|
||||
onStartLevelChanged(newLevel);
|
||||
}
|
||||
@Override
|
||||
public void onReadyMarkerRemoved(ReadyMarker readyMarker) {
|
||||
// we don't need to process this, as openHAB only reduces its start level when the system is shutdown
|
||||
// in this case the service is de-activated anyway and al scripts are removed by {@link #deactivate}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void factoryAdded(@Nullable String scriptType) {
|
||||
scriptMap.forEach((scriptIdentifier, ref) -> {
|
||||
if (ref.getScriptType().equals(scriptType) && !ref.getQueueStatus().getAndSet(true)) {
|
||||
importFileWhenReady(scriptIdentifier);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -383,6 +408,9 @@ public abstract class AbstractScriptFileWatcher extends AbstractWatchService imp
|
||||
if (scriptType == null) {
|
||||
return;
|
||||
}
|
||||
loaded.stream().filter(ref -> scriptType.equals(ref.getScriptType().get())).forEach(this::importFileWhenReady);
|
||||
Set<String> toRemove = scriptMap.values().stream()
|
||||
.filter(ref -> ref.getLoadedStatus().get() && scriptType.equals(ref.getScriptType()))
|
||||
.map(ScriptFileReference::getScriptIdentifier).collect(Collectors.toSet());
|
||||
toRemove.forEach(this::removeFile);
|
||||
}
|
||||
}
|
||||
|
@ -1,140 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.automation.module.script.rulesupport.loader;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.service.StartLevelService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Script File wrapper offering various methods to inspect the script
|
||||
*
|
||||
* @author Jonathan Gilbert - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ScriptFileReference implements Comparable<ScriptFileReference> {
|
||||
|
||||
private static final Set<String> EXCLUDED_FILE_EXTENSIONS = Set.of("txt", "old", "example", "backup", "md", "swp",
|
||||
"tmp", "bak");
|
||||
|
||||
private static final Pattern[] START_LEVEL_PATTERNS = new Pattern[] { Pattern.compile(".*/sl(\\d{2})/[^/]+"), // script
|
||||
// in
|
||||
// immediate
|
||||
// slXX
|
||||
// directory
|
||||
Pattern.compile(".*/[^/]+\\.sl(\\d{2})\\.[^/.]+") // script named <name>.slXX.<ext>
|
||||
};
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptFileReference.class);
|
||||
|
||||
private final URL scriptFileURL;
|
||||
|
||||
public ScriptFileReference(URL scriptFileURL) {
|
||||
this.scriptFileURL = scriptFileURL;
|
||||
}
|
||||
|
||||
public URL getScriptFileURL() {
|
||||
return scriptFileURL;
|
||||
}
|
||||
|
||||
public int getStartLevel() {
|
||||
for (Pattern p : START_LEVEL_PATTERNS) {
|
||||
Matcher m = p.matcher(scriptFileURL.getPath());
|
||||
if (m.find() && m.groupCount() > 0) {
|
||||
try {
|
||||
return Integer.parseInt(m.group(1));
|
||||
} catch (NumberFormatException nfe) {
|
||||
LOGGER.warn("Extracted start level {} from {}, but it's not an integer. Ignoring.", m.group(1),
|
||||
scriptFileURL.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return StartLevelService.STARTLEVEL_RULEENGINE;
|
||||
}
|
||||
|
||||
public Optional<String> getScriptType() {
|
||||
String fileName = scriptFileURL.getPath();
|
||||
int index = fileName.lastIndexOf(".");
|
||||
if (index == -1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String fileExtension = fileName.substring(index + 1);
|
||||
|
||||
// ignore known file extensions for "temp" files
|
||||
if (EXCLUDED_FILE_EXTENSIONS.contains(fileExtension) || fileExtension.endsWith("~")) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(fileExtension);
|
||||
}
|
||||
|
||||
public String getScriptIdentifier() {
|
||||
return scriptFileURL.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ScriptFileReference other) {
|
||||
try {
|
||||
Path path1 = Paths.get(scriptFileURL.toURI());
|
||||
String name1 = path1.getFileName().toString();
|
||||
LOGGER.trace("o1 [{}], path1 [{}], name1 [{}]", scriptFileURL, path1, name1);
|
||||
|
||||
Path path2 = Paths.get(other.scriptFileURL.toURI());
|
||||
String name2 = path2.getFileName().toString();
|
||||
LOGGER.trace("o2 [{}], path2 [{}], name2 [{}]", other.scriptFileURL, path2, name2);
|
||||
|
||||
int startLevelCompare = Integer.compare(getStartLevel(), other.getStartLevel());
|
||||
if (startLevelCompare != 0) {
|
||||
return startLevelCompare;
|
||||
}
|
||||
int nameCompare = name1.compareToIgnoreCase(name2);
|
||||
if (nameCompare != 0) {
|
||||
return nameCompare;
|
||||
} else {
|
||||
return path1.getParent().toString().compareToIgnoreCase(path2.getParent().toString());
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
LOGGER.error("URI syntax exception", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ScriptFileReference that = (ScriptFileReference) o;
|
||||
return scriptFileURL.equals(that.scriptFileURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(scriptFileURL);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.automation.module.script.rulesupport.loader;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
|
||||
/**
|
||||
* The {@link ScriptFileWatcher} interface needs to be implemented by script file watchers. Services that implement this
|
||||
* interface can be tracked to set a {@link ReadyMarker} once all services have completed their initial loading.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ScriptFileWatcher {
|
||||
|
||||
/**
|
||||
* Returns a {@link CompletableFuture<Void>} that completes when the {@link ScriptFileWatcher} has completed it's
|
||||
* initial loading of files.
|
||||
*
|
||||
* @return the {@link CompletableFuture}
|
||||
*/
|
||||
CompletableFuture<@Nullable Void> ifInitialized();
|
||||
}
|
@ -13,15 +13,21 @@
|
||||
package org.openhab.core.automation.module.script.rulesupport.loader;
|
||||
|
||||
import static java.nio.file.StandardWatchEventKinds.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.core.OpenHAB.CONFIG_DIR_PROG_ARGUMENT;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.script.ScriptEngine;
|
||||
|
||||
@ -31,7 +37,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@ -41,36 +46,49 @@ import org.openhab.core.automation.module.script.ScriptDependencyTracker;
|
||||
import org.openhab.core.automation.module.script.ScriptEngineContainer;
|
||||
import org.openhab.core.automation.module.script.ScriptEngineFactory;
|
||||
import org.openhab.core.automation.module.script.ScriptEngineManager;
|
||||
import org.openhab.core.automation.module.script.rulesupport.internal.loader.DelegatingScheduledExecutorService;
|
||||
import org.openhab.core.automation.module.script.rulesupport.internal.loader.ScriptFileReference;
|
||||
import org.openhab.core.service.ReadyMarker;
|
||||
import org.openhab.core.service.ReadyService;
|
||||
import org.openhab.core.service.StartLevelService;
|
||||
import org.openhab.core.test.java.JavaTest;
|
||||
import org.opentest4j.AssertionFailedError;
|
||||
|
||||
/**
|
||||
* Test for Script File Watcher, covering differing start levels and dependency tracking
|
||||
* Test for {@link AbstractScriptFileWatcher}, covering differing start levels and dependency tracking
|
||||
*
|
||||
* @author Jonathan Gilbert - initial contribution
|
||||
* @author Jonathan Gilbert - Initial contribution
|
||||
* @author Jan N. Klug - Refactoring and improvements
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class AbstractScriptFileWatcherTest {
|
||||
class AbstractScriptFileWatcherTest extends JavaTest {
|
||||
|
||||
private boolean watchSubDirectories = true;
|
||||
private @NonNullByDefault({}) AbstractScriptFileWatcher scriptFileWatcher;
|
||||
|
||||
private @Mock @NonNullByDefault({}) ScriptEngineManager scriptEngineManagerMock;
|
||||
private @Mock @NonNullByDefault({}) ScriptDependencyTracker scriptDependencyTrackerMock;
|
||||
private @Mock @NonNullByDefault({}) ReadyService readyService;
|
||||
private @Mock @NonNullByDefault({}) StartLevelService startLevelServiceMock;
|
||||
private @Mock @NonNullByDefault({}) ReadyService readyServiceMock;
|
||||
|
||||
protected @NonNullByDefault({}) @TempDir Path tempScriptDir;
|
||||
|
||||
private final AtomicInteger atomicInteger = new AtomicInteger();
|
||||
|
||||
private int currentStartLevel = 0;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
scriptFileWatcher = new AbstractScriptFileWatcher(scriptEngineManagerMock, readyService,
|
||||
"automation" + File.separator + "jsr223") {
|
||||
};
|
||||
scriptFileWatcher.activate();
|
||||
System.setProperty(CONFIG_DIR_PROG_ARGUMENT, tempScriptDir.toString());
|
||||
|
||||
atomicInteger.set(0);
|
||||
currentStartLevel = 0;
|
||||
|
||||
// ensure initialize is not called on initialization
|
||||
when(startLevelServiceMock.getStartLevel()).thenAnswer(invocation -> currentStartLevel);
|
||||
|
||||
scriptFileWatcher = createScriptFileWatcher();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@ -78,7 +96,502 @@ class AbstractScriptFileWatcherTest {
|
||||
scriptFileWatcher.deactivate();
|
||||
}
|
||||
|
||||
protected Path getFile(String name) {
|
||||
@Test
|
||||
public void testLoadOneDefaultFileAlreadyStarted() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubDirectoryIncludedInInitialImport() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
Path p0 = getFile("script.js");
|
||||
Path p1 = getFile("dir/script.js");
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js",
|
||||
ScriptFileReference.getScriptIdentifier(p0));
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js",
|
||||
ScriptFileReference.getScriptIdentifier(p1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubDirectoryIgnoredInInitialImport() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
watchSubDirectories = false;
|
||||
Path p0 = getFile("script.js");
|
||||
Path p1 = getFile("dir/script.js");
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js",
|
||||
ScriptFileReference.getScriptIdentifier(p0));
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine("js", ScriptFileReference.getScriptIdentifier(p1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadOneDefaultFileWaitUntilStarted() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(20);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
// verify is called when the start level increases
|
||||
updateStartLevel(100);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadOneCustomFileWaitUntilStarted() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
|
||||
updateStartLevel(50);
|
||||
|
||||
Path p = getFile("script.sl60.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
// verify is called when the start level increases
|
||||
updateStartLevel(100);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadTwoCustomFilesDifferentStartLevels() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(20);
|
||||
|
||||
Path p1 = getFile("script.sl70.js");
|
||||
Path p2 = getFile("script.sl50.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p1);
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p2);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(40);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(60);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p2.toString());
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), eq(p1.toString()));
|
||||
|
||||
updateStartLevel(80);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p1.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadTwoCustomFilesAlternativePatternDifferentStartLevels() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
|
||||
Path p1 = getFile("sl70/script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p1);
|
||||
Path p2 = getFile("sl50/script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p2);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(40);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(60);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p2.toString());
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), eq(p1.toString()));
|
||||
|
||||
updateStartLevel(80);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p1.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadOneDefaultFileDelayedSupport() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(false);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
// verify not yet called but checked
|
||||
waitForAssert(() -> verify(scriptEngineManagerMock).isSupported(anyString()));
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
// add support is added for .js files
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
scriptFileWatcher.factoryAdded("js");
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
// verify script has now been processed
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderingWithinSingleStartLevel() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(50);
|
||||
|
||||
Path p64 = getFile("script.sl64.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p64);
|
||||
Path p66 = getFile("script.sl66.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p66);
|
||||
Path p65 = getFile("script.sl65.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p65);
|
||||
|
||||
updateStartLevel(70);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p64.toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p65.toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p66.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderingStartlevelFolders() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
|
||||
Path p50 = getFile("a_script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p50);
|
||||
Path p40 = getFile("sl40/b_script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p40);
|
||||
Path p30 = getFile("sl30/script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p30);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
updateStartLevel(70);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p30.toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p40.toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p50.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReloadActiveWhenDependencyChanged() {
|
||||
ScriptEngineFactory scriptEngineFactoryMock = mock(ScriptEngineFactory.class);
|
||||
when(scriptEngineFactoryMock.getDependencyTracker()).thenReturn(scriptDependencyTrackerMock);
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineContainer.getFactory()).thenReturn(scriptEngineFactoryMock);
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js", p.toString());
|
||||
|
||||
scriptFileWatcher.onDependencyChange(p.toString());
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(2)).createScriptEngine("js", p.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotReloadInactiveWhenDependencyChanged() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
scriptFileWatcher.onDependencyChange(p.toString());
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine("js", p.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveBeforeReAdd() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
String scriptIdentifier = ScriptFileReference.getScriptIdentifier(p);
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
awaitEmptyQueue();
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", scriptIdentifier);
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_MODIFY, p);
|
||||
|
||||
awaitEmptyQueue();
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).removeEngine(scriptIdentifier);
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", scriptIdentifier);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryAdded() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p1 = getFile("dir/script.js");
|
||||
Path p2 = getFile("dir/script2.js");
|
||||
Path d = p1.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, d);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p1.toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p2.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryAddedSubDirIncluded() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p1 = getFile("dir/script.js");
|
||||
Path p2 = getFile("dir/sub/script.js");
|
||||
Path d = p1.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, d);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js",
|
||||
ScriptFileReference.getScriptIdentifier(p1));
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js",
|
||||
ScriptFileReference.getScriptIdentifier(p2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryAddedSubDirIgnored() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
watchSubDirectories = false;
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p1 = getFile("dir/script.js");
|
||||
Path p2 = getFile("dir/sub/script.js");
|
||||
Path d = p1.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, d);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js",
|
||||
ScriptFileReference.getScriptIdentifier(p1));
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine("js", ScriptFileReference.getScriptIdentifier(p2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortsAllFilesInNewDirectory() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p20 = getFile("dir/script.sl20.js");
|
||||
Path p10 = getFile("dir/script2.sl10.js");
|
||||
Path d = p10.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, d);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p10.toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p20.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryRemoved() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(true);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p1 = getFile("dir/script.js");
|
||||
Path p2 = getFile("dir/script2.js");
|
||||
Path d = p1.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p1);
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p2);
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_DELETE, d);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p1.toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p2.toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).removeEngine(p1.toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).removeEngine(p2.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScriptEngineRemovedOnFailedLoad() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
when(scriptEngineManagerMock.loadScript(any(), any())).thenReturn(false);
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
when(scriptEngineContainer.getIdentifier()).thenReturn(ScriptFileReference.getScriptIdentifier(p));
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
awaitEmptyQueue();
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(1000)).createScriptEngine("js",
|
||||
ScriptFileReference.getScriptIdentifier(p));
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(1000))
|
||||
.loadScript(eq(ScriptFileReference.getScriptIdentifier(p)), any());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(1000)).removeEngine(ScriptFileReference.getScriptIdentifier(p));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIfInitializedForEarlyInitialization() {
|
||||
CompletableFuture<?> initialized = scriptFileWatcher.ifInitialized();
|
||||
|
||||
assertThat(initialized.isDone(), is(false));
|
||||
|
||||
updateStartLevel(StartLevelService.STARTLEVEL_STATES);
|
||||
waitForAssert(() -> assertThat(initialized.isDone(), is(true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIfInitializedForLateInitialization() {
|
||||
when(startLevelServiceMock.getStartLevel()).thenReturn(StartLevelService.STARTLEVEL_RULEENGINE);
|
||||
AbstractScriptFileWatcher watcher = createScriptFileWatcher();
|
||||
|
||||
waitForAssert(() -> assertThat(watcher.ifInitialized().isDone(), is(true)));
|
||||
|
||||
watcher.deactivate();
|
||||
}
|
||||
|
||||
private Path getFile(String name) {
|
||||
Path tempFile = tempScriptDir.resolve(name);
|
||||
try {
|
||||
File parent = tempFile.getParent().toFile();
|
||||
@ -96,336 +609,56 @@ class AbstractScriptFileWatcherTest {
|
||||
return Path.of(tempFile.toUri());
|
||||
}
|
||||
|
||||
void updateStartLevel(int level) {
|
||||
scriptFileWatcher
|
||||
.onReadyMarkerAdded(new ReadyMarker(StartLevelService.STARTLEVEL_MARKER_TYPE, Integer.toString(level)));
|
||||
/**
|
||||
* Increase the start level in steps of 10
|
||||
*
|
||||
* @param level the target start-level
|
||||
*/
|
||||
private void updateStartLevel(int level) {
|
||||
while (currentStartLevel < level) {
|
||||
currentStartLevel += 10;
|
||||
scriptFileWatcher.onReadyMarkerAdded(
|
||||
new ReadyMarker(StartLevelService.STARTLEVEL_MARKER_TYPE, Integer.toString(currentStartLevel)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadOneDefaultFileAlreadyStarted() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
private AbstractScriptFileWatcher createScriptFileWatcher() {
|
||||
return new AbstractScriptFileWatcher(scriptEngineManagerMock, readyServiceMock, startLevelServiceMock, "") {
|
||||
|
||||
updateStartLevel(100);
|
||||
@Override
|
||||
protected ScheduledExecutorService getScheduler() {
|
||||
return new CountingScheduledExecutor(atomicInteger);
|
||||
}
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p.toFile().toURI().toString());
|
||||
@Override
|
||||
protected boolean watchSubDirectories() {
|
||||
return watchSubDirectories;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadOneDefaultFileWaitUntilStarted() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
// verify is called when the start level increases
|
||||
updateStartLevel(100);
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p.toFile().toURI().toString());
|
||||
private void awaitEmptyQueue() {
|
||||
waitForAssert(() -> assertThat(atomicInteger.get(), is(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadOneCustomFileWaitUntilStarted() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
private static class CountingScheduledExecutor extends ScheduledThreadPoolExecutor {
|
||||
|
||||
updateStartLevel(50);
|
||||
private final AtomicInteger counter;
|
||||
|
||||
Path p = getFile("script.sl60.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
public CountingScheduledExecutor(AtomicInteger counter) {
|
||||
super(1);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
this.counter = counter;
|
||||
}
|
||||
|
||||
// verify is called when the start level increases
|
||||
updateStartLevel(100);
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadTwoCustomFilesDifferentStartLevels() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
Path p1 = getFile("script.sl70.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p1);
|
||||
Path p2 = getFile("script.sl50.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p2);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(40);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(60);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p2.toFile().toURI().toString());
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), eq(p1.toFile().toURI().toString()));
|
||||
|
||||
updateStartLevel(80);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p1.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadTwoCustomFilesAlternativePatternDifferentStartLevels() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
Path p1 = getFile("sl70/script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p1);
|
||||
Path p2 = getFile("sl50/script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p2);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(40);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
updateStartLevel(60);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p2.toFile().toURI().toString());
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), eq(p1.toFile().toURI().toString()));
|
||||
|
||||
updateStartLevel(80);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p1.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadOneDefaultFileDelayedSupport() {
|
||||
// set an executor which captures the scheduled task
|
||||
ScheduledExecutorService scheduledExecutorService = spy(
|
||||
new DelegatingScheduledExecutorService(Executors.newSingleThreadScheduledExecutor()));
|
||||
ArgumentCaptor<Runnable> scheduledTask = ArgumentCaptor.forClass(Runnable.class);
|
||||
scriptFileWatcher.setExecutorFactory(() -> scheduledExecutorService);
|
||||
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(false);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
updateStartLevel(100);
|
||||
|
||||
verify(scheduledExecutorService).scheduleWithFixedDelay(scheduledTask.capture(), anyLong(), anyLong(), any());
|
||||
|
||||
Path p = getFile("script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
// verify not yet called
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine(anyString(), anyString());
|
||||
|
||||
// add support is added for .js files
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
// update (in current thread)
|
||||
scheduledTask.getValue().run();
|
||||
|
||||
// verify script has now been processed
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderingWithinSingleStartLevel() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
Path p64 = getFile("script.sl64.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p64);
|
||||
Path p66 = getFile("script.sl66.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p66);
|
||||
Path p65 = getFile("script.sl65.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p65);
|
||||
|
||||
updateStartLevel(70);
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p64.toFile().toURI().toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p65.toFile().toURI().toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p66.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderingStartlevelFolders() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
Path p50 = getFile("a_script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p50);
|
||||
Path p40 = getFile("sl40/b_script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p40);
|
||||
Path p30 = getFile("sl30/script.js");
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p30);
|
||||
|
||||
updateStartLevel(70);
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p30.toFile().toURI().toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p40.toFile().toURI().toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p50.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReloadActiveWhenDependencyChanged() {
|
||||
ScriptEngineFactory scriptEngineFactoryMock = mock(ScriptEngineFactory.class);
|
||||
when(scriptEngineFactoryMock.getDependencyTracker()).thenReturn(scriptDependencyTrackerMock);
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineContainer.getFactory()).thenReturn(scriptEngineFactoryMock);
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
scriptFileWatcher.onDependencyChange(p.toFile().toURI().toString());
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(2)).createScriptEngine("js",
|
||||
p.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotReloadInactiveWhenDependencyChanged() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
|
||||
scriptFileWatcher.onDependencyChange(p.toFile().toURI().toString());
|
||||
|
||||
verify(scriptEngineManagerMock, never()).createScriptEngine("js", p.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveBeforeReAdd() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p = getFile("script.js");
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p);
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_MODIFY, p);
|
||||
|
||||
verify(scriptEngineManagerMock).removeEngine(p.toFile().toURI().toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000).times(2)).createScriptEngine("js",
|
||||
p.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryAdded() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p1 = getFile("dir/script.js");
|
||||
Path p2 = getFile("dir/script2.js");
|
||||
Path d = p1.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, d);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p1.toFile().toURI().toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p2.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortsAllFilesInNewDirectory() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p20 = getFile("dir/script.sl20.js");
|
||||
Path p10 = getFile("dir/script2.sl10.js");
|
||||
Path d = p10.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, d);
|
||||
|
||||
InOrder inOrder = inOrder(scriptEngineManagerMock);
|
||||
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p10.toFile().toURI().toString());
|
||||
inOrder.verify(scriptEngineManagerMock, timeout(10000).times(1)).createScriptEngine("js",
|
||||
p20.toFile().toURI().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDirectoryRemoved() {
|
||||
when(scriptEngineManagerMock.isSupported("js")).thenReturn(true);
|
||||
ScriptEngineContainer scriptEngineContainer = mock(ScriptEngineContainer.class);
|
||||
when(scriptEngineContainer.getScriptEngine()).thenReturn(mock(ScriptEngine.class));
|
||||
when(scriptEngineManagerMock.createScriptEngine(anyString(), anyString())).thenReturn(scriptEngineContainer);
|
||||
|
||||
updateStartLevel(100);
|
||||
|
||||
Path p1 = getFile("dir/script.js");
|
||||
Path p2 = getFile("dir/script2.js");
|
||||
Path d = p1.getParent();
|
||||
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p1);
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_CREATE, p2);
|
||||
scriptFileWatcher.processWatchEvent(null, ENTRY_DELETE, d);
|
||||
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p1.toFile().toURI().toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).createScriptEngine("js", p2.toFile().toURI().toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).removeEngine(p1.toFile().toURI().toString());
|
||||
verify(scriptEngineManagerMock, timeout(10000)).removeEngine(p2.toFile().toURI().toString());
|
||||
@Override
|
||||
public Future<?> submit(@NonNullByDefault({}) Runnable runnable) {
|
||||
counter.getAndIncrement();
|
||||
Runnable wrappedRunnable = () -> {
|
||||
runnable.run();
|
||||
counter.getAndDecrement();
|
||||
};
|
||||
return super.submit(wrappedRunnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,8 +42,9 @@ public interface ScriptEngineManager {
|
||||
*
|
||||
* @param engineIdentifier the unique identifier for the ScriptEngine (script file path or UUID)
|
||||
* @param scriptData the content of the script
|
||||
* @return <code>true</code> if the script was successfully loaded, <code>false</code> otherwise
|
||||
*/
|
||||
void loadScript(String engineIdentifier, InputStreamReader scriptData);
|
||||
boolean loadScript(String engineIdentifier, InputStreamReader scriptData);
|
||||
|
||||
/**
|
||||
* Unloads the ScriptEngine loaded with the engineIdentifier
|
||||
|
@ -174,13 +174,12 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadScript(String engineIdentifier, InputStreamReader scriptData) {
|
||||
public boolean loadScript(String engineIdentifier, InputStreamReader scriptData) {
|
||||
ScriptEngineContainer container = loadedScriptEngineInstances.get(engineIdentifier);
|
||||
if (container == null) {
|
||||
logger.error("Could not load script, as no ScriptEngine has been created");
|
||||
} else {
|
||||
ScriptEngine engine = container.getScriptEngine();
|
||||
|
||||
try {
|
||||
engine.eval(scriptData);
|
||||
if (engine instanceof Invocable) {
|
||||
@ -193,11 +192,13 @@ public class ScriptEngineManagerImpl implements ScriptEngineManager {
|
||||
} else {
|
||||
logger.trace("ScriptEngine does not support Invocable interface");
|
||||
}
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error during evaluation of script '{}': {}", engineIdentifier, ex.getMessage());
|
||||
logger.debug("", ex);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
Loading…
Reference in New Issue
Block a user