Fix file processing in FileTransformationProvider (#3457)
Signed-off-by: Jan N. Klug <github@klug.nrw>pull/3467/head
parent
f529a7b77f
commit
4390d515d6
|
@ -12,22 +12,23 @@
|
|||
*/
|
||||
package org.openhab.core.transform;
|
||||
|
||||
import static org.openhab.core.service.WatchService.Kind.CREATE;
|
||||
import static org.openhab.core.service.WatchService.Kind.DELETE;
|
||||
import static org.openhab.core.service.WatchService.Kind.MODIFY;
|
||||
import static org.openhab.core.transform.Transformation.FUNCTION;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.nio.file.WatchEvent;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.openhab.core.common.registry.ProviderChangeListener;
|
||||
import org.openhab.core.service.WatchService;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
|
@ -46,13 +47,9 @@ import org.slf4j.LoggerFactory;
|
|||
@NonNullByDefault
|
||||
@Component(service = TransformationProvider.class, immediate = true)
|
||||
public class FileTransformationProvider implements WatchService.WatchEventListener, TransformationProvider {
|
||||
private static final WatchEvent.Kind<?>[] WATCH_EVENTS = { StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY };
|
||||
private static final Set<String> IGNORED_EXTENSIONS = Set.of("txt", "swp");
|
||||
private static final Pattern FILENAME_PATTERN = Pattern
|
||||
.compile("(?<filename>.+?)(_(?<language>[a-z]{2}))?\\.(?<extension>[^.]*)$");
|
||||
private static final Path TRANSFORMATION_PATH = Path.of(OpenHAB.getConfigFolder(),
|
||||
TransformationService.TRANSFORM_FOLDER_NAME);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FileTransformationProvider.class);
|
||||
|
||||
|
@ -64,19 +61,13 @@ public class FileTransformationProvider implements WatchService.WatchEventListen
|
|||
@Activate
|
||||
public FileTransformationProvider(
|
||||
@Reference(target = WatchService.CONFIG_WATCHER_FILTER) WatchService watchService) {
|
||||
this(watchService, TRANSFORMATION_PATH);
|
||||
}
|
||||
|
||||
// constructor package private used for testing
|
||||
FileTransformationProvider(WatchService watchService, Path transformationPath) {
|
||||
this.transformationPath = transformationPath;
|
||||
this.watchService = watchService;
|
||||
this.transformationPath = watchService.getWatchPath().resolve(TransformationService.TRANSFORM_FOLDER_NAME);
|
||||
|
||||
watchService.registerListener(this, transformationPath);
|
||||
// read initial contents
|
||||
try {
|
||||
Files.walk(transformationPath).filter(Files::isRegularFile)
|
||||
.forEach(f -> processPath(WatchService.Kind.CREATE, f));
|
||||
try (Stream<Path> files = Files.walk(transformationPath)) {
|
||||
files.filter(Files::isRegularFile).map(transformationPath::relativize).forEach(f -> processPath(CREATE, f));
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not list files in '{}', transformation configurations might be missing: {}",
|
||||
transformationPath, e.getMessage());
|
||||
|
@ -104,14 +95,14 @@ public class FileTransformationProvider implements WatchService.WatchEventListen
|
|||
}
|
||||
|
||||
private void processPath(WatchService.Kind kind, Path path) {
|
||||
if (kind == WatchService.Kind.DELETE) {
|
||||
Path finalPath = transformationPath.resolve(path);
|
||||
if (kind == DELETE) {
|
||||
Transformation oldElement = transformationConfigurations.remove(path);
|
||||
if (oldElement != null) {
|
||||
logger.trace("Removed configuration from file '{}", path);
|
||||
listeners.forEach(listener -> listener.removed(this, oldElement));
|
||||
}
|
||||
} else if (Files.isRegularFile(path)
|
||||
&& (kind == WatchService.Kind.CREATE || kind == WatchService.Kind.MODIFY)) {
|
||||
} else if (Files.isRegularFile(finalPath) && ((kind == CREATE) || (kind == MODIFY))) {
|
||||
try {
|
||||
String fileName = path.getFileName().toString();
|
||||
Matcher m = FILENAME_PATTERN.matcher(fileName);
|
||||
|
@ -128,8 +119,8 @@ public class FileTransformationProvider implements WatchService.WatchEventListen
|
|||
return;
|
||||
}
|
||||
|
||||
String content = new String(Files.readAllBytes(path));
|
||||
String uid = transformationPath.relativize(path).toString();
|
||||
String content = new String(Files.readAllBytes(finalPath));
|
||||
String uid = path.toString();
|
||||
|
||||
Transformation newElement = new Transformation(uid, uid, fileExtension, Map.of(FUNCTION, content));
|
||||
Transformation oldElement = transformationConfigurations.put(path, newElement);
|
||||
|
|
|
@ -18,22 +18,23 @@ import static org.hamcrest.Matchers.hasItem;
|
|||
import static org.hamcrest.Matchers.not;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.core.service.WatchService.Kind.CREATE;
|
||||
import static org.openhab.core.service.WatchService.Kind.DELETE;
|
||||
import static org.openhab.core.service.WatchService.Kind.MODIFY;
|
||||
import static org.openhab.core.transform.Transformation.FUNCTION;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
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.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
@ -54,38 +55,32 @@ import org.openhab.core.service.WatchService;
|
|||
public class FileTransformationProviderTest {
|
||||
private static final String FOO_TYPE = "foo";
|
||||
private static final String INITIAL_CONTENT = "initial";
|
||||
private static final String INITIAL_FILENAME = INITIAL_CONTENT + "." + FOO_TYPE;
|
||||
private static final Transformation INITIAL_CONFIGURATION = new Transformation(INITIAL_FILENAME, INITIAL_FILENAME,
|
||||
FOO_TYPE, Map.of(FUNCTION, INITIAL_CONTENT));
|
||||
private static final Path INITIAL_FILENAME = Path.of(INITIAL_CONTENT + "." + FOO_TYPE);
|
||||
private static final Transformation INITIAL_CONFIGURATION = new Transformation(INITIAL_FILENAME.toString(),
|
||||
INITIAL_FILENAME.toString(), FOO_TYPE, Map.of(FUNCTION, INITIAL_CONTENT));
|
||||
private static final String ADDED_CONTENT = "added";
|
||||
private static final String ADDED_FILENAME = ADDED_CONTENT + "." + FOO_TYPE;
|
||||
private static final Path ADDED_FILENAME = Path.of(ADDED_CONTENT + "." + FOO_TYPE);
|
||||
|
||||
private @Mock @NonNullByDefault({}) WatchService watchService;
|
||||
private @Mock @NonNullByDefault({}) WatchService watchServiceMock;
|
||||
private @Mock @NonNullByDefault({}) ProviderChangeListener<@NonNull Transformation> listenerMock;
|
||||
|
||||
private @NonNullByDefault({}) FileTransformationProvider provider;
|
||||
private @NonNullByDefault({}) Path targetPath;
|
||||
private @NonNullByDefault({}) @TempDir Path configPath;
|
||||
private @NonNullByDefault({}) Path transformationPath;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws IOException {
|
||||
// create directory
|
||||
targetPath = Files.createTempDirectory("fileTest");
|
||||
when(watchServiceMock.getWatchPath()).thenReturn(configPath);
|
||||
transformationPath = configPath.resolve(TransformationService.TRANSFORM_FOLDER_NAME);
|
||||
|
||||
// set initial content
|
||||
Files.write(targetPath.resolve(INITIAL_FILENAME), INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
|
||||
// create transformation directory and set initial content
|
||||
Files.createDirectories(transformationPath);
|
||||
Files.writeString(transformationPath.resolve(INITIAL_FILENAME), INITIAL_CONTENT);
|
||||
|
||||
provider = new FileTransformationProvider(watchService, targetPath);
|
||||
provider = new FileTransformationProvider(watchServiceMock);
|
||||
provider.addProviderChangeListener(listenerMock);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() throws IOException {
|
||||
try (Stream<Path> walk = Files.walk(targetPath)) {
|
||||
walk.map(Path::toFile).forEach(File::delete);
|
||||
}
|
||||
Files.deleteIfExists(targetPath);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitialConfigurationIsPresent() {
|
||||
// assert that initial configuration is present
|
||||
|
@ -94,13 +89,10 @@ public class FileTransformationProviderTest {
|
|||
|
||||
@Test
|
||||
public void testAddingConfigurationIsPropagated() throws IOException {
|
||||
Path path = targetPath.resolve(ADDED_FILENAME);
|
||||
|
||||
Files.write(path, ADDED_CONTENT.getBytes(StandardCharsets.UTF_8));
|
||||
Transformation addedConfiguration = new Transformation(ADDED_FILENAME, ADDED_FILENAME, FOO_TYPE,
|
||||
Map.of(FUNCTION, ADDED_CONTENT));
|
||||
|
||||
provider.processWatchEvent(WatchService.Kind.CREATE, path);
|
||||
Files.writeString(transformationPath.resolve(ADDED_FILENAME), ADDED_CONTENT);
|
||||
Transformation addedConfiguration = new Transformation(ADDED_FILENAME.toString(), ADDED_FILENAME.toString(),
|
||||
FOO_TYPE, Map.of(FUNCTION, ADDED_CONTENT));
|
||||
provider.processWatchEvent(CREATE, ADDED_FILENAME);
|
||||
|
||||
// assert registry is notified and internal cache updated
|
||||
Mockito.verify(listenerMock).added(provider, addedConfiguration);
|
||||
|
@ -109,12 +101,10 @@ public class FileTransformationProviderTest {
|
|||
|
||||
@Test
|
||||
public void testUpdatingConfigurationIsPropagated() throws IOException {
|
||||
Path path = targetPath.resolve(INITIAL_FILENAME);
|
||||
Files.write(path, "updated".getBytes(StandardCharsets.UTF_8));
|
||||
Transformation updatedConfiguration = new Transformation(INITIAL_FILENAME, INITIAL_FILENAME, FOO_TYPE,
|
||||
Map.of(FUNCTION, "updated"));
|
||||
|
||||
provider.processWatchEvent(WatchService.Kind.MODIFY, path);
|
||||
Files.writeString(transformationPath.resolve(INITIAL_FILENAME), "updated");
|
||||
Transformation updatedConfiguration = new Transformation(INITIAL_FILENAME.toString(),
|
||||
INITIAL_FILENAME.toString(), FOO_TYPE, Map.of(FUNCTION, "updated"));
|
||||
provider.processWatchEvent(MODIFY, INITIAL_FILENAME);
|
||||
|
||||
Mockito.verify(listenerMock).updated(provider, INITIAL_CONFIGURATION, updatedConfiguration);
|
||||
assertThat(provider.getAll(), contains(updatedConfiguration));
|
||||
|
@ -123,9 +113,7 @@ public class FileTransformationProviderTest {
|
|||
|
||||
@Test
|
||||
public void testDeletingConfigurationIsPropagated() {
|
||||
Path path = targetPath.resolve(INITIAL_FILENAME);
|
||||
|
||||
provider.processWatchEvent(WatchService.Kind.DELETE, path);
|
||||
provider.processWatchEvent(DELETE, INITIAL_FILENAME);
|
||||
|
||||
Mockito.verify(listenerMock).removed(provider, INITIAL_CONFIGURATION);
|
||||
assertThat(provider.getAll(), not(contains(INITIAL_CONFIGURATION)));
|
||||
|
@ -134,22 +122,23 @@ public class FileTransformationProviderTest {
|
|||
@Test
|
||||
public void testLanguageIsProperlyParsed() throws IOException {
|
||||
String fileName = "test_de." + FOO_TYPE;
|
||||
Path path = targetPath.resolve(fileName);
|
||||
Path path = transformationPath.resolve(fileName);
|
||||
|
||||
Files.write(path, INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
|
||||
Files.writeString(path, INITIAL_CONTENT);
|
||||
|
||||
Transformation expected = new Transformation(fileName, fileName, FOO_TYPE, Map.of(FUNCTION, INITIAL_CONTENT));
|
||||
|
||||
provider.processWatchEvent(WatchService.Kind.CREATE, path);
|
||||
provider.processWatchEvent(CREATE, Path.of(fileName));
|
||||
assertThat(provider.getAll(), hasItem(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingExtensionIsIgnored() throws IOException {
|
||||
Path path = targetPath.resolve("extensionMissing");
|
||||
Files.write(path, INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
|
||||
provider.processWatchEvent(WatchService.Kind.CREATE, path);
|
||||
provider.processWatchEvent(WatchService.Kind.MODIFY, path);
|
||||
Path extensionMissing = Path.of("extensionMissing");
|
||||
Path path = transformationPath.resolve(extensionMissing);
|
||||
Files.writeString(path, INITIAL_CONTENT);
|
||||
provider.processWatchEvent(CREATE, extensionMissing);
|
||||
provider.processWatchEvent(MODIFY, extensionMissing);
|
||||
|
||||
Mockito.verify(listenerMock, never()).added(any(), any());
|
||||
Mockito.verify(listenerMock, never()).updated(any(), any(), any());
|
||||
|
@ -157,10 +146,11 @@ public class FileTransformationProviderTest {
|
|||
|
||||
@Test
|
||||
public void testIgnoredExtensionIsIgnored() throws IOException {
|
||||
Path path = targetPath.resolve("extensionIgnore.txt");
|
||||
Files.write(path, INITIAL_CONTENT.getBytes(StandardCharsets.UTF_8));
|
||||
provider.processWatchEvent(WatchService.Kind.CREATE, path);
|
||||
provider.processWatchEvent(WatchService.Kind.MODIFY, path);
|
||||
Path extensionIgnored = Path.of("extensionIgnore.txt");
|
||||
Path path = transformationPath.resolve(extensionIgnored);
|
||||
Files.writeString(path, INITIAL_CONTENT);
|
||||
provider.processWatchEvent(CREATE, extensionIgnored);
|
||||
provider.processWatchEvent(MODIFY, extensionIgnored);
|
||||
|
||||
Mockito.verify(listenerMock, never()).added(any(), any());
|
||||
Mockito.verify(listenerMock, never()).updated(any(), any(), any());
|
||||
|
|
Loading…
Reference in New Issue