WatchService: use absolute path in processWatchEvent (#4796)

* WatchService: use absolute path in processWatchEvent

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>

* update interface and use a consistent name `fullPath`

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>

---------

Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
pull/4798/head
jimtng 2025-05-11 00:13:03 +10:00 committed by GitHub
parent 8ff69a3f50
commit c40c258e04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 137 additions and 111 deletions

View File

@ -78,8 +78,8 @@ public abstract class AbstractScriptDependencyTracker
}
@Override
public void processWatchEvent(WatchService.Kind kind, Path path) {
File file = libraryPath.resolve(path).toFile();
public void processWatchEvent(WatchService.Kind kind, Path fullPath) {
File file = fullPath.toFile();
if (kind == DELETE || (!file.isHidden() && file.canRead() && (kind == CREATE || kind == MODIFY))) {
dependencyChanged(file.toString());
}

View File

@ -218,13 +218,12 @@ public abstract class AbstractScriptFileWatcher implements WatchService.WatchEve
}
@Override
public void processWatchEvent(WatchService.Kind kind, Path path) {
public void processWatchEvent(WatchService.Kind kind, Path fullPath) {
if (!initialized.isDone()) {
// discard events if the initial import has not finished
return;
}
Path fullPath = watchPath.resolve(path);
File file = fullPath.toFile();
// Subdirectory events are filtered out by WatchService, so we only need to deal with files

View File

@ -92,7 +92,7 @@ public class AbstractScriptDependencyTrackerTest {
scriptDependencyTracker.addChangeTracker(listener2);
scriptDependencyTracker.startTracking("scriptId", depPath.toString());
scriptDependencyTracker.processWatchEvent(WatchService.Kind.CREATE, DEPENDENCY);
scriptDependencyTracker.processWatchEvent(WatchService.Kind.CREATE, depPath);
verify(listener1).onDependencyChange(eq("scriptId"));
verify(listener2).onDependencyChange(eq("scriptId"));
@ -108,7 +108,7 @@ public class AbstractScriptDependencyTrackerTest {
scriptDependencyTracker.startTracking("scriptId1", depPath.toString());
scriptDependencyTracker.startTracking("scriptId2", depPath.toString());
scriptDependencyTracker.processWatchEvent(WatchService.Kind.MODIFY, DEPENDENCY);
scriptDependencyTracker.processWatchEvent(WatchService.Kind.MODIFY, depPath);
verify(listener).onDependencyChange(eq("scriptId1"));
verify(listener).onDependencyChange(eq("scriptId2"));
@ -123,8 +123,8 @@ public class AbstractScriptDependencyTrackerTest {
scriptDependencyTracker.startTracking("scriptId", depPath.toString());
scriptDependencyTracker.startTracking("scriptId", depPath2.toString());
scriptDependencyTracker.processWatchEvent(WatchService.Kind.MODIFY, DEPENDENCY);
scriptDependencyTracker.processWatchEvent(WatchService.Kind.DELETE, DEPENDENCY2);
scriptDependencyTracker.processWatchEvent(WatchService.Kind.MODIFY, depPath);
scriptDependencyTracker.processWatchEvent(WatchService.Kind.DELETE, depPath2);
verify(listener, times(2)).onDependencyChange(eq("scriptId"));
verifyNoMoreInteractions(listener);
@ -139,7 +139,7 @@ public class AbstractScriptDependencyTrackerTest {
scriptDependencyTracker.startTracking("scriptId1", depPath.toString());
scriptDependencyTracker.startTracking("scriptId2", depPath2.toString());
scriptDependencyTracker.processWatchEvent(WatchService.Kind.CREATE, DEPENDENCY);
scriptDependencyTracker.processWatchEvent(WatchService.Kind.CREATE, depPath);
verify(listener).onDependencyChange(eq("scriptId1"));
verifyNoMoreInteractions(listener);

View File

@ -58,8 +58,7 @@ public class AutomationWatchService implements WatchService.WatchEventListener {
}
@Override
public void processWatchEvent(WatchService.Kind kind, Path path) {
Path fullPath = watchingDir.resolve(path);
public void processWatchEvent(WatchService.Kind kind, Path fullPath) {
try {
if (kind == WatchService.Kind.DELETE) {
provider.removeResources(fullPath.toFile());

View File

@ -63,8 +63,7 @@ public class ConfigDispatcherFileWatcher implements WatchService.WatchEventListe
}
@Override
public void processWatchEvent(WatchService.Kind kind, Path path) {
Path fullPath = watchService.getWatchPath().resolve(SERVICES_FOLDER).resolve(path);
public void processWatchEvent(WatchService.Kind kind, Path fullPath) {
try {
if (kind == WatchService.Kind.CREATE || kind == WatchService.Kind.MODIFY) {
if (!Files.isHidden(fullPath) && fullPath.toString().endsWith(".cfg")) {
@ -79,7 +78,7 @@ public class ConfigDispatcherFileWatcher implements WatchService.WatchEventListe
configDispatcher.fileRemoved(fullPath.toString());
}
} catch (IOException e) {
logger.error("Failed to process watch event {} for {}", kind, path, e);
logger.error("Failed to process watch event {} for {}", kind, fullPath, e);
}
}
}

View File

@ -49,8 +49,6 @@ public class ConfigDispatcherFileWatcherTest {
configDispatcherFileWatcher = new ConfigDispatcherFileWatcher(configDispatcherMock, watchService);
verify(configDispatcherMock).processConfigFile(any());
when(watchService.getWatchPath()).thenReturn(tempDir.toAbsolutePath());
cfgPath = tempDir.resolve("myPath.cfg");
nonCfgPath = tempDir.resolve("myPath");

View File

@ -59,6 +59,7 @@ import org.slf4j.LoggerFactory;
@Component(name = "org.openhab.core.folder", immediate = true, configurationPid = "org.openhab.folder", configurationPolicy = ConfigurationPolicy.REQUIRE)
public class FolderObserver implements WatchService.WatchEventListener {
private final WatchService watchService;
private final Path watchPath;
private final Logger logger = LoggerFactory.getLogger(FolderObserver.class);
/* the model repository is provided as a service */
@ -88,6 +89,7 @@ public class FolderObserver implements WatchService.WatchEventListener {
this.modelRepository = modelRepo;
this.readyService = readyService;
this.watchService = watchService;
this.watchPath = watchService.getWatchPath();
}
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE, policy = ReferencePolicy.DYNAMIC)
@ -127,7 +129,7 @@ public class FolderObserver implements WatchService.WatchEventListener {
continue;
}
Path folderPath = watchService.getWatchPath().resolve(folderName);
Path folderPath = watchPath.resolve(folderName);
if (Files.exists(folderPath) && Files.isDirectory(folderPath)) {
String[] validExtensions = ((String) config.get(folderName)).split(",");
folderFileExtMap.put(folderName, Set.of(validExtensions));
@ -191,7 +193,7 @@ public class FolderObserver implements WatchService.WatchEventListener {
continue;
}
Path folderPath = watchService.getWatchPath().resolve(folderName);
Path folderPath = watchPath.resolve(folderName);
logger.debug("Adding files in '{}' to the model", folderPath);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(folderPath,
new FileExtensionsFilter(validExtensions))) {
@ -287,7 +289,8 @@ public class FolderObserver implements WatchService.WatchEventListener {
}
@Override
public void processWatchEvent(WatchService.Kind kind, Path path) {
public void processWatchEvent(WatchService.Kind kind, Path fullPath) {
Path path = watchPath.relativize(fullPath);
if (path.getNameCount() != 2) {
logger.trace("{} event for {} ignored (only depth 1 allowed)", kind, path);
return;
@ -310,7 +313,6 @@ public class FolderObserver implements WatchService.WatchEventListener {
return;
}
Path resolvedPath = watchService.getWatchPath().resolve(path);
checkPath(resolvedPath, kind);
checkPath(fullPath, kind);
}
}

View File

@ -90,7 +90,7 @@ public class FolderObserverTest extends JavaTest {
when(modelParserMock.getExtension()).thenReturn("java");
when(contextMock.getProperties()).thenReturn(configProps);
when(watchServiceMock.getWatchPath()).thenReturn(WATCHED_DIRECTORY.toPath());
when(watchServiceMock.getWatchPath()).thenReturn(WATCHED_DIRECTORY.toPath().toAbsolutePath());
folderObserver = new FolderObserver(modelRepoMock, readyServiceMock, watchServiceMock);
folderObserver.addModelParser(modelParserMock);
@ -129,7 +129,7 @@ public class FolderObserverTest extends JavaTest {
Files.writeString(file.toPath(), INITIAL_FILE_CONTENT, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
waitForAssert(() -> assertThat(file.exists(), is(true)));
folderObserver.processWatchEvent(CREATE, WATCHED_DIRECTORY.toPath().relativize(file.toPath()));
folderObserver.processWatchEvent(CREATE, file.toPath().toAbsolutePath());
verify(modelRepoMock).addOrRefreshModel(eq(file.getName()), any());
verifyNoMoreInteractions(modelRepoMock);
@ -154,12 +154,12 @@ public class FolderObserverTest extends JavaTest {
Files.writeString(file.toPath(), INITIAL_FILE_CONTENT, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
waitForAssert(() -> assertThat(file.exists(), is(true)));
folderObserver.processWatchEvent(CREATE, WATCHED_DIRECTORY.toPath().relativize(file.toPath()));
folderObserver.processWatchEvent(CREATE, file.toPath().toAbsolutePath());
String text = "Additional content";
Files.writeString(file.toPath(), text, StandardCharsets.UTF_8, StandardOpenOption.APPEND);
folderObserver.processWatchEvent(MODIFY, WATCHED_DIRECTORY.toPath().relativize(file.toPath()));
folderObserver.processWatchEvent(MODIFY, file.toPath().toAbsolutePath());
verify(modelRepoMock, times(2)).addOrRefreshModel(eq(file.getName()), any());
verifyNoMoreInteractions(modelRepoMock);
@ -183,7 +183,7 @@ public class FolderObserverTest extends JavaTest {
Files.writeString(file.toPath(), INITIAL_FILE_CONTENT, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
waitForAssert(() -> assertThat(file.exists(), is(true)));
folderObserver.processWatchEvent(CREATE, WATCHED_DIRECTORY.toPath().relativize(file.toPath()));
folderObserver.processWatchEvent(CREATE, file.toPath().toAbsolutePath());
verifyNoInteractions(modelRepoMock);
}
@ -203,7 +203,7 @@ public class FolderObserverTest extends JavaTest {
File file = new File(EXISTING_SUBDIR_PATH, "NewlyCreatedMockFile.java");
Files.writeString(file.toPath(), INITIAL_FILE_CONTENT, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
waitForAssert(() -> assertThat(file.exists(), is(true)));
folderObserver.processWatchEvent(CREATE, WATCHED_DIRECTORY.toPath().relativize(file.toPath()));
folderObserver.processWatchEvent(CREATE, file.toPath().toAbsolutePath());
verifyNoInteractions(modelRepoMock);
}
@ -256,7 +256,7 @@ public class FolderObserverTest extends JavaTest {
Files.writeString(file.toPath(), INITIAL_FILE_CONTENT, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
waitForAssert(() -> assertThat(file.exists(), is(true)));
folderObserver.processWatchEvent(CREATE, WATCHED_DIRECTORY.toPath().relativize(file.toPath()));
folderObserver.processWatchEvent(CREATE, file.toPath().toAbsolutePath());
verifyNoInteractions(modelRepoMock);
}
@ -275,13 +275,11 @@ public class FolderObserverTest extends JavaTest {
File mockFileWithValidExt = new File(EXISTING_SUBDIR_PATH, "MockFileForModification." + validExtension);
Files.writeString(mockFileWithValidExt.toPath(), INITIAL_FILE_CONTENT, StandardCharsets.UTF_8,
StandardOpenOption.CREATE);
localFolderObserver.processWatchEvent(CREATE,
WATCHED_DIRECTORY.toPath().relativize(mockFileWithValidExt.toPath()));
localFolderObserver.processWatchEvent(CREATE, mockFileWithValidExt.toPath().toAbsolutePath());
Files.writeString(mockFileWithValidExt.toPath(), "Additional content", StandardCharsets.UTF_8,
StandardOpenOption.APPEND);
localFolderObserver.processWatchEvent(MODIFY,
WATCHED_DIRECTORY.toPath().relativize(mockFileWithValidExt.toPath()));
localFolderObserver.processWatchEvent(MODIFY, mockFileWithValidExt.toPath().toAbsolutePath());
verify(modelRepoMock, times(2)).addOrRefreshModel(eq(mockFileWithValidExt.getName()), any());
}
@ -325,7 +323,7 @@ public class FolderObserverTest extends JavaTest {
walk.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
}
folderObserver.processWatchEvent(CREATE, Path.of(EXISTING_SUBDIR_PATH.getName(), filename));
folderObserver.processWatchEvent(CREATE, Path.of(EXISTING_SUBDIR_PATH.getName(), filename).toAbsolutePath());
verifyNoInteractions(modelRepoMock);
}
}

View File

@ -130,8 +130,7 @@ public class YamlModelRepositoryImpl implements WatchService.WatchEventListener,
public FileVisitResult visitFile(@NonNullByDefault({}) Path file,
@NonNullByDefault({}) BasicFileAttributes attrs) throws IOException {
if (attrs.isRegularFile()) {
Path relativePath = watchPath.relativize(file);
processWatchEvent(CREATE, relativePath);
processWatchEvent(CREATE, file);
}
return FileVisitResult.CONTINUE;
}
@ -156,10 +155,10 @@ public class YamlModelRepositoryImpl implements WatchService.WatchEventListener,
// The method is "synchronized" to avoid concurrent files processing
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public synchronized void processWatchEvent(Kind kind, Path path) {
Path fullPath = watchPath.resolve(path);
String modelName = path.toString();
if (path.startsWith("automation") || !modelName.endsWith(".yaml")) {
public synchronized void processWatchEvent(Kind kind, Path fullPath) {
Path relativePath = watchPath.relativize(fullPath);
String modelName = relativePath.toString();
if (relativePath.startsWith("automation") || !modelName.endsWith(".yaml")) {
logger.trace("Ignored {}", fullPath);
return;
}

View File

@ -103,8 +103,8 @@ public class YamlModelRepositoryImplTest {
modelRepository.addYamlModelListener(secondTypeListener1);
modelRepository.addYamlModelListener(secondTypeListener2);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
verify(firstTypeListener).addedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
verify(firstTypeListener).addedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
@ -158,8 +158,8 @@ public class YamlModelRepositoryImplTest {
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
modelRepository.addYamlModelListener(firstTypeListener);
modelRepository.addYamlModelListener(secondTypeListener1);
@ -217,23 +217,23 @@ public class YamlModelRepositoryImplTest {
// File in v1 format
Files.copy(SOURCE_PATH.resolve("modelFileUpdatePost.yaml"), fullModelPath);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
verify(firstTypeListener).addedModel(eq(MODEL_NAME), any());
Files.copy(SOURCE_PATH.resolve("modelFileUpdatePre.yaml"), fullModelPath, StandardCopyOption.REPLACE_EXISTING);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, fullModelPath);
verify(firstTypeListener, times(2)).addedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
verify(firstTypeListener).updatedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
verify(firstTypeListener).removedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
// File in v2 format
Files.copy(SOURCE_PATH.resolve("modelV2FileUpdatePost.yaml"), fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
verify(firstTypeListener).addedModel(eq(MODEL2_NAME), any());
Files.copy(SOURCE_PATH.resolve("modelV2FileUpdatePre.yaml"), fullModel2Path,
StandardCopyOption.REPLACE_EXISTING);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, fullModel2Path);
verify(firstTypeListener, times(2)).addedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
verify(firstTypeListener).updatedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
verify(firstTypeListener).removedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
@ -282,11 +282,11 @@ public class YamlModelRepositoryImplTest {
// File in v1 format
Files.copy(SOURCE_PATH.resolve("modelFileUpdatePost.yaml"), fullModelPath);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
verify(firstTypeListener).addedModel(eq(MODEL_NAME), any());
Files.copy(SOURCE_PATH.resolve(v1File), fullModelPath, StandardCopyOption.REPLACE_EXISTING);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, fullModelPath);
verify(firstTypeListener).removedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
Collection<FirstTypeDTO> firstTypeElements = firstTypeCaptor.getAllValues().getFirst();
@ -298,11 +298,11 @@ public class YamlModelRepositoryImplTest {
// File in v2 format
Files.copy(SOURCE_PATH.resolve("modelV2FileUpdatePost.yaml"), fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
verify(firstTypeListener).addedModel(eq(MODEL2_NAME), any());
Files.copy(SOURCE_PATH.resolve(v2File), fullModel2Path, StandardCopyOption.REPLACE_EXISTING);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.MODIFY, fullModel2Path);
verify(firstTypeListener).removedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
assertThat(firstTypeCaptor.getAllValues(), hasSize(2));
@ -322,8 +322,8 @@ public class YamlModelRepositoryImplTest {
// File in v1 format
Files.copy(SOURCE_PATH.resolve("modelFileUpdatePost.yaml"), fullModelPath);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.DELETE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
modelRepository.processWatchEvent(WatchService.Kind.DELETE, fullModelPath);
verify(firstTypeListener).addedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
verify(firstTypeListener, never()).updatedModel(any(), any());
@ -331,8 +331,8 @@ public class YamlModelRepositoryImplTest {
// File in v2 format
Files.copy(SOURCE_PATH.resolve("modelV2FileUpdatePost.yaml"), fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.DELETE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.DELETE, fullModel2Path);
verify(firstTypeListener).addedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
verify(firstTypeListener, never()).updatedModel(any(), any());
@ -367,7 +367,7 @@ public class YamlModelRepositoryImplTest {
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.addYamlModelListener(firstTypeListener);
modelRepository.addYamlModelListener(secondTypeListener1);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
FirstTypeDTO added = new FirstTypeDTO("element3", "description3");
modelRepository.addElementToModel(MODEL_NAME, added);
@ -388,7 +388,7 @@ public class YamlModelRepositoryImplTest {
assertThat(yaml.load(actualFileContent), equalTo(yaml.load(expectedFileContent)));
Files.copy(SOURCE_PATH.resolve("modifyModelV2InitialContent.yaml"), fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
modelRepository.addElementToModel(MODEL2_NAME, added);
modelRepository.addElementToModel(MODEL2_NAME, added2);
verify(firstTypeListener, times(2)).addedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
@ -424,7 +424,7 @@ public class YamlModelRepositoryImplTest {
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.addYamlModelListener(firstTypeListener);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
FirstTypeDTO updated = new FirstTypeDTO("element1", "newDescription1");
modelRepository.updateElementInModel(MODEL_NAME, updated);
@ -440,7 +440,7 @@ public class YamlModelRepositoryImplTest {
assertThat(yaml.load(actualFileContent), equalTo(yaml.load(expectedFileContent)));
Files.copy(SOURCE_PATH.resolve("modifyModelV2InitialContent.yaml"), fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
modelRepository.updateElementInModel(MODEL2_NAME, updated);
verify(firstTypeListener).addedModel(eq(MODEL2_NAME), any());
verify(firstTypeListener).updatedModel(eq(MODEL2_NAME), firstTypeCaptor.capture());
@ -464,7 +464,7 @@ public class YamlModelRepositoryImplTest {
Files.copy(SOURCE_PATH.resolve("modifyModelInitialContent.yaml"), fullModelPath);
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
FirstTypeDTO removed = new FirstTypeDTO("element1", "description1");
modelRepository.addYamlModelListener(firstTypeListener);
@ -481,7 +481,7 @@ public class YamlModelRepositoryImplTest {
assertThat(yaml.load(actualFileContent), equalTo(yaml.load(expectedFileContent)));
Files.copy(SOURCE_PATH.resolve("modifyModelV2InitialContent.yaml"), fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
modelRepository.removeElementFromModel(MODEL2_NAME, removed);
verify(firstTypeListener).addedModel(eq(MODEL2_NAME), any());
verify(firstTypeListener, never()).updatedModel(any(), any());
@ -505,7 +505,7 @@ public class YamlModelRepositoryImplTest {
Files.copy(SOURCE_PATH.resolve("modelFileAddedOrRemoved.yaml"), fullModelPath);
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
FirstTypeDTO added = new FirstTypeDTO("element3", "description3");
modelRepository.addElementToModel(MODEL_NAME, added);
@ -523,7 +523,7 @@ public class YamlModelRepositoryImplTest {
assertThat(yaml.load(actualFileContent), equalTo(yaml.load(expectedFileContent)));
Files.copy(SOURCE_PATH.resolve("modelV2FileAddedOrRemoved.yaml"), fullModel2Path);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
modelRepository.addElementToModel(MODEL2_NAME, added);
modelRepository.removeElementFromModel(MODEL2_NAME, removed);
modelRepository.updateElementInModel(MODEL2_NAME, updated);
@ -546,7 +546,7 @@ public class YamlModelRepositoryImplTest {
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.addYamlModelListener(firstTypeListener);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
verify(firstTypeListener).addedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
verify(firstTypeListener, never()).updatedModel(any(), any());
@ -570,7 +570,7 @@ public class YamlModelRepositoryImplTest {
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.addYamlModelListener(firstTypeListener);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
verify(firstTypeListener).addedModel(eq(MODEL_NAME), firstTypeCaptor.capture());
verify(firstTypeListener, never()).updatedModel(any(), any());
@ -594,7 +594,7 @@ public class YamlModelRepositoryImplTest {
YamlModelRepositoryImpl modelRepository = new YamlModelRepositoryImpl(watchServiceMock);
modelRepository.addYamlModelListener(firstTypeListener);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
verify(firstTypeListener, never()).addedModel(any(), any());
verify(firstTypeListener, never()).updatedModel(any(), any());
@ -619,8 +619,8 @@ public class YamlModelRepositoryImplTest {
modelRepository.addYamlModelListener(secondTypeListener1);
modelRepository.addYamlModelListener(secondTypeListener2);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, MODEL2_PATH);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModelPath);
modelRepository.processWatchEvent(WatchService.Kind.CREATE, fullModel2Path);
verify(secondTypeListener1).addedModel(eq(MODEL_NAME), secondTypeCaptor1.capture());
verify(secondTypeListener1, never()).addedModel(eq(MODEL2_NAME), any());

View File

@ -67,8 +67,7 @@ public class FileTransformationProvider implements WatchService.WatchEventListen
watchService.registerListener(this, transformationPath);
// read initial contents
try (Stream<Path> files = Files.walk(transformationPath)) {
files.filter(Files::isRegularFile).map(transformationPath::relativize)
.forEach(f -> processWatchEvent(CREATE, f));
files.filter(Files::isRegularFile).forEach(f -> processWatchEvent(CREATE, f));
} catch (IOException e) {
logger.warn("Could not list files in '{}', transformation configurations might be missing: {}",
transformationPath, e.getMessage());
@ -96,8 +95,8 @@ public class FileTransformationProvider implements WatchService.WatchEventListen
}
@Override
public void processWatchEvent(WatchService.Kind kind, Path path) {
Path finalPath = transformationPath.resolve(path);
public void processWatchEvent(WatchService.Kind kind, Path fullPath) {
Path path = transformationPath.relativize(fullPath);
try {
if (kind == DELETE) {
Transformation oldElement = transformationConfigurations.remove(path);
@ -105,7 +104,7 @@ public class FileTransformationProvider implements WatchService.WatchEventListen
logger.trace("Removed configuration from file '{}", path);
listeners.forEach(listener -> listener.removed(this, oldElement));
}
} else if (Files.isRegularFile(finalPath) && !Files.isHidden(finalPath)
} else if (Files.isRegularFile(fullPath) && !Files.isHidden(fullPath)
&& ((kind == CREATE) || (kind == MODIFY))) {
String fileName = path.getFileName().toString();
Matcher m = FILENAME_PATTERN.matcher(fileName);
@ -122,7 +121,7 @@ public class FileTransformationProvider implements WatchService.WatchEventListen
return;
}
String content = new String(Files.readAllBytes(finalPath));
String content = new String(Files.readAllBytes(fullPath));
String uid = path.toString();
Transformation newElement = new Transformation(uid, uid, fileExtension, Map.of(FUNCTION, content));

View File

@ -89,10 +89,11 @@ public class FileTransformationProviderTest {
@Test
public void testAddingConfigurationIsPropagated() throws IOException {
Files.writeString(transformationPath.resolve(ADDED_FILENAME), ADDED_CONTENT);
Path path = transformationPath.resolve(ADDED_FILENAME);
Files.writeString(path, ADDED_CONTENT);
Transformation addedConfiguration = new Transformation(ADDED_FILENAME.toString(), ADDED_FILENAME.toString(),
FOO_TYPE, Map.of(FUNCTION, ADDED_CONTENT));
provider.processWatchEvent(CREATE, ADDED_FILENAME);
provider.processWatchEvent(CREATE, path);
// assert registry is notified and internal cache updated
Mockito.verify(listenerMock).added(provider, addedConfiguration);
@ -101,10 +102,11 @@ public class FileTransformationProviderTest {
@Test
public void testUpdatingConfigurationIsPropagated() throws IOException {
Files.writeString(transformationPath.resolve(INITIAL_FILENAME), "updated");
Path path = transformationPath.resolve(INITIAL_FILENAME);
Files.writeString(path, "updated");
Transformation updatedConfiguration = new Transformation(INITIAL_FILENAME.toString(),
INITIAL_FILENAME.toString(), FOO_TYPE, Map.of(FUNCTION, "updated"));
provider.processWatchEvent(MODIFY, INITIAL_FILENAME);
provider.processWatchEvent(MODIFY, path);
Mockito.verify(listenerMock).updated(provider, INITIAL_CONFIGURATION, updatedConfiguration);
assertThat(provider.getAll(), contains(updatedConfiguration));
@ -113,7 +115,7 @@ public class FileTransformationProviderTest {
@Test
public void testDeletingConfigurationIsPropagated() {
provider.processWatchEvent(DELETE, INITIAL_FILENAME);
provider.processWatchEvent(DELETE, transformationPath.resolve(INITIAL_FILENAME));
Mockito.verify(listenerMock).removed(provider, INITIAL_CONFIGURATION);
assertThat(provider.getAll(), not(contains(INITIAL_CONFIGURATION)));
@ -128,15 +130,14 @@ public class FileTransformationProviderTest {
Transformation expected = new Transformation(fileName, fileName, FOO_TYPE, Map.of(FUNCTION, INITIAL_CONTENT));
provider.processWatchEvent(CREATE, Path.of(fileName));
provider.processWatchEvent(CREATE, path);
assertThat(provider.getAll(), hasItem(expected));
}
@Test
public void testMissingExtensionIsIgnored() throws IOException {
Path extensionMissing = Path.of("extensionMissing");
Path path = transformationPath.resolve(extensionMissing);
Files.writeString(path, INITIAL_CONTENT);
Path extensionMissing = transformationPath.resolve("extensionMissing");
Files.writeString(extensionMissing, INITIAL_CONTENT);
provider.processWatchEvent(CREATE, extensionMissing);
provider.processWatchEvent(MODIFY, extensionMissing);
@ -146,9 +147,8 @@ public class FileTransformationProviderTest {
@Test
public void testIgnoredExtensionIsIgnored() throws IOException {
Path extensionIgnored = Path.of("extensionIgnore.txt");
Path path = transformationPath.resolve(extensionIgnored);
Files.writeString(path, INITIAL_CONTENT);
Path extensionIgnored = transformationPath.resolve("extensionIgnore.txt");
Files.writeString(extensionIgnored, INITIAL_CONTENT);
provider.processWatchEvent(CREATE, extensionIgnored);
provider.processWatchEvent(MODIFY, extensionIgnored);

View File

@ -289,8 +289,8 @@ public class WatchServiceImpl implements WatchService, DirectoryChangeListener {
private record Listener(Path rootPath, WatchEventListener watchEventListener) {
void notify(Path path, Kind kind) {
watchEventListener.processWatchEvent(kind, rootPath.relativize(path));
void notify(Path fullPath, Kind kind) {
watchEventListener.processWatchEvent(kind, fullPath);
}
static Predicate<Listener> isListener(WatchEventListener watchEventListener) {

View File

@ -119,9 +119,9 @@ public interface WatchService {
* Notify Listener about watch event
*
* @param kind the {@link Kind} of this event
* @param path the relative path of the file associated with this event
* @param fullPath the absolute path of the file associated with this event
*/
void processWatchEvent(Kind kind, Path path);
void processWatchEvent(Kind kind, Path fullPath);
}
enum Kind {

View File

@ -48,6 +48,7 @@ import org.osgi.framework.BundleContext;
@MockitoSettings(strictness = Strictness.LENIENT)
public class WatchServiceImplTest extends JavaTest {
private static final String SUB_DIR_PATH_NAME = "subDir";
private static final String SUB_DIR2_PATH_NAME = "subDir2";
private static final String TEST_FILE_NAME = "testFile";
public @Mock @NonNullByDefault({}) WatchServiceImpl.WatchServiceConfiguration configurationMock;
@ -78,19 +79,18 @@ public class WatchServiceImplTest extends JavaTest {
watchService.registerListener(listener, rootPath, false);
Path testFile = rootPath.resolve(TEST_FILE_NAME);
Path relativeTestFilePath = Path.of(TEST_FILE_NAME);
Files.writeString(testFile, "initial content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.CREATE);
assertEvent(testFile, Kind.CREATE);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.MODIFY);
assertEvent(testFile, Kind.MODIFY);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertNoEvent();
Files.delete(testFile);
assertEvent(relativeTestFilePath, Kind.DELETE);
assertEvent(testFile, Kind.DELETE);
}
@Test
@ -101,19 +101,18 @@ public class WatchServiceImplTest extends JavaTest {
watchService.registerListener(listener, rootPath, true);
Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NAME);
Path relativeTestFilePath = Path.of(SUB_DIR_PATH_NAME, TEST_FILE_NAME);
Files.writeString(testFile, "initial content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.CREATE);
assertEvent(testFile, Kind.CREATE);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.MODIFY);
assertEvent(testFile, Kind.MODIFY);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertNoEvent();
Files.delete(testFile);
assertEvent(relativeTestFilePath, Kind.DELETE);
assertEvent(testFile, Kind.DELETE);
}
@Test
@ -122,21 +121,21 @@ public class WatchServiceImplTest extends JavaTest {
// listener is only listening to sub-dir of root
watchService.registerListener(listener, Path.of(SUB_DIR_PATH_NAME), false);
watchService.registerListener(listener, Path.of(SUB_DIR2_PATH_NAME), false);
Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NAME);
Path relativeTestFilePath = Path.of(TEST_FILE_NAME);
Files.writeString(testFile, "initial content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.CREATE);
assertEvent(testFile, Kind.CREATE);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.MODIFY);
assertEvent(testFile, Kind.MODIFY);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertNoEvent();
Files.delete(testFile);
assertEvent(relativeTestFilePath, Kind.DELETE);
assertEvent(testFile, Kind.DELETE);
}
@Test
@ -168,36 +167,70 @@ public class WatchServiceImplTest extends JavaTest {
assertNoEvent();
Path testFile = subDirSubDir.resolve(TEST_FILE_NAME);
Path relativeTestFilePath = rootPath.relativize(testFile);
Files.writeString(testFile, "initial content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.CREATE);
assertEvent(testFile, Kind.CREATE);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertEvent(relativeTestFilePath, Kind.MODIFY);
assertEvent(testFile, Kind.MODIFY);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertNoEvent();
Files.delete(testFile);
assertEvent(relativeTestFilePath, Kind.DELETE);
assertEvent(testFile, Kind.DELETE);
Files.delete(subDirSubDir);
assertNoEvent();
}
@Test
public void testWatchMultiplePaths() throws IOException, InterruptedException {
Files.createDirectories(rootPath.resolve(SUB_DIR_PATH_NAME));
Files.createDirectories(rootPath.resolve(SUB_DIR2_PATH_NAME));
watchService.registerListener(listener, List.of(Path.of(SUB_DIR_PATH_NAME), Path.of(SUB_DIR2_PATH_NAME)), true);
Path testFile = rootPath.resolve(SUB_DIR_PATH_NAME).resolve(TEST_FILE_NAME);
Path testFile2 = rootPath.resolve(SUB_DIR2_PATH_NAME).resolve(TEST_FILE_NAME);
Files.writeString(testFile, "initial content", StandardCharsets.UTF_8);
assertEvent(testFile, Kind.CREATE);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertEvent(testFile, Kind.MODIFY);
Files.writeString(testFile, "modified content", StandardCharsets.UTF_8);
assertNoEvent();
Files.writeString(testFile2, "initial content", StandardCharsets.UTF_8);
assertEvent(testFile2, Kind.CREATE);
Files.writeString(testFile2, "modified content", StandardCharsets.UTF_8);
assertEvent(testFile2, Kind.MODIFY);
Files.writeString(testFile2, "modified content", StandardCharsets.UTF_8);
assertNoEvent();
Files.delete(testFile);
assertEvent(testFile, Kind.DELETE);
Files.delete(testFile2);
assertEvent(testFile2, Kind.DELETE);
}
private void assertNoEvent() throws InterruptedException {
Thread.sleep(5000);
assertThat(listener.events, empty());
}
private void assertEvent(Path path, Kind kind) throws InterruptedException {
private void assertEvent(Path fullPath, Kind kind) throws InterruptedException {
waitForAssert(() -> assertThat(listener.events, not(empty())));
Thread.sleep(500);
assertThat(listener.events, hasSize(1));
assertThat(listener.events, hasItem(new Event(path, kind)));
assertThat(listener.events, hasItem(new Event(fullPath, kind)));
listener.events.clear();
}
@ -205,11 +238,11 @@ public class WatchServiceImplTest extends JavaTest {
List<Event> events = new CopyOnWriteArrayList<>();
@Override
public void processWatchEvent(Kind kind, Path path) {
events.add(new Event(path, kind));
public void processWatchEvent(Kind kind, Path fullPath) {
events.add(new Event(fullPath, kind));
}
}
record Event(Path path, Kind kind) {
record Event(Path fullPath, Kind kind) {
}
}