From b4752368cbe0c74c418dcaf225be0d39798e5a30 Mon Sep 17 00:00:00 2001 From: Daniel Graf Date: Mon, 18 May 2026 09:10:29 +0200 Subject: [PATCH 1/2] refactor: decouple MetadataService file handling with new MetaDataProvider interface - Replaced direct file handling logic in `MetadataService` with the `MetaDataProvider` interface. - Introduced `FileMetaDataProvider` for file-based metadata loading, improving testability. - Adjusted ImportService to use configurable `paikka.version` property. - Added comprehensive unit tests for metadata loading logic. --- .../paikka/service/FileMetaDataProvider.java | 49 +++++++++++++++ .../paikka/service/MetaDataProvider.java | 26 ++++++++ .../paikka/service/MetadataService.java | 58 ++++-------------- .../service/importer/ImportService.java | 7 ++- src/main/resources/application.properties | 1 + .../paikka/service/ImportServiceTest.java | 2 +- .../paikka/service/MetadataServiceTest.java | 59 +++++++++++++++++++ src/test/resources/metadata.json | 1 + 8 files changed, 154 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/dedicatedcode/paikka/service/FileMetaDataProvider.java create mode 100644 src/main/java/com/dedicatedcode/paikka/service/MetaDataProvider.java create mode 100644 src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java create mode 100644 src/test/resources/metadata.json diff --git a/src/main/java/com/dedicatedcode/paikka/service/FileMetaDataProvider.java b/src/main/java/com/dedicatedcode/paikka/service/FileMetaDataProvider.java new file mode 100644 index 0000000..ebf2d45 --- /dev/null +++ b/src/main/java/com/dedicatedcode/paikka/service/FileMetaDataProvider.java @@ -0,0 +1,49 @@ +/* + * This file is part of paikka. + * + * Paikka is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 or + * any later version. + * + * Paikka is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * You should have received a copy of the GNU Affero General Public License + * along with Paikka. If not, see . + */ + +package com.dedicatedcode.paikka.service; + +import com.dedicatedcode.paikka.config.PaikkaConfiguration; +import org.springframework.stereotype.Service; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Service +public class FileMetaDataProvider implements MetaDataProvider { + private static final String METADATA_FILE_NAME = "paikka_metadata.json"; + + private final PaikkaConfiguration configuration; + private final Path metadataPath; + + public FileMetaDataProvider(PaikkaConfiguration configuration) { + this.configuration = configuration; + this.metadataPath = Paths.get(configuration.getDataDir(), METADATA_FILE_NAME); + } + + @Override + public boolean exists() { + return false; + } + + @Override + public InputStream get() throws FileNotFoundException { + return new FileInputStream(metadataPath.toFile()); + } +} diff --git a/src/main/java/com/dedicatedcode/paikka/service/MetaDataProvider.java b/src/main/java/com/dedicatedcode/paikka/service/MetaDataProvider.java new file mode 100644 index 0000000..98f6eb6 --- /dev/null +++ b/src/main/java/com/dedicatedcode/paikka/service/MetaDataProvider.java @@ -0,0 +1,26 @@ +/* + * This file is part of paikka. + * + * Paikka is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 or + * any later version. + * + * Paikka is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * You should have received a copy of the GNU Affero General Public License + * along with Paikka. If not, see . + */ + +package com.dedicatedcode.paikka.service; + +import java.io.FileNotFoundException; +import java.io.InputStream; + +public interface MetaDataProvider { + boolean exists(); + + InputStream get() throws FileNotFoundException; +} diff --git a/src/main/java/com/dedicatedcode/paikka/service/MetadataService.java b/src/main/java/com/dedicatedcode/paikka/service/MetadataService.java index 309abdd..215153a 100644 --- a/src/main/java/com/dedicatedcode/paikka/service/MetadataService.java +++ b/src/main/java/com/dedicatedcode/paikka/service/MetadataService.java @@ -16,7 +16,6 @@ package com.dedicatedcode.paikka.service; -import com.dedicatedcode.paikka.config.PaikkaConfiguration; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; @@ -25,11 +24,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.Instant; -import java.time.format.DateTimeParseException; +import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -40,17 +35,16 @@ public class MetadataService { private static final Logger logger = LoggerFactory.getLogger(MetadataService.class); - private static final String METADATA_FILE_NAME = "paikka_metadata.json"; - private final PaikkaConfiguration config; private final ObjectMapper objectMapper; + private final MetaDataProvider provider; private volatile PaikkaMetadata metadata; // Change type to PaikkaMetadata - public MetadataService(PaikkaConfiguration config, ObjectMapper objectMapper) { - this.config = config; + public MetadataService(MetaDataProvider provider, ObjectMapper objectMapper) { + this.provider = provider; this.objectMapper = objectMapper; - this.metadata = null; // Initialize with null, will be loaded in @PostConstruct + this.metadata = null; } @PostConstruct @@ -62,20 +56,18 @@ public void init() { * Loads the metadata from the paikka_metadata.json file. * This method is synchronized to prevent race conditions during reload. */ - public synchronized void loadMetadata() { - Path metadataPath = Paths.get(config.getDataDir(), METADATA_FILE_NAME); - - if (!Files.exists(metadataPath)) { - logger.warn("Metadata file not found at {}. Running without metadata.", metadataPath); + private synchronized void loadMetadata() { + if (!provider.exists()) { + logger.warn("Metadata file not!. Running without metadata."); this.metadata = null; // Set to null if file not found return; } - try { - this.metadata = objectMapper.readValue(metadataPath.toFile(), PaikkaMetadata.class); // Deserialize to PaikkaMetadata - logger.info("Metadata loaded successfully from {}", metadataPath); + try (InputStream is = provider.get()) { + this.metadata = objectMapper.readValue(is, PaikkaMetadata.class); // Deserialize to PaikkaMetadata + logger.info("Metadata loaded successfully"); } catch (IOException e) { - logger.error("Failed to load metadata from {}: {}", metadataPath, e.getMessage()); + logger.error("Failed to load metadata:", e); this.metadata = null; // Set to null on error } } @@ -115,30 +107,4 @@ public Map getMetadata() { metadataMap.put("paikkaVersion", metadata.paikkaVersion()); return Collections.unmodifiableMap(metadataMap); } - - /** - * Returns the import timestamp as an Instant. - * If metadata is not available or timestamp is invalid, returns Optional.empty(). - */ - public Optional getImportTimestamp() { - return Optional.ofNullable(metadata) - .map(PaikkaMetadata::importTimestamp) - .flatMap(timestampStr -> { - try { - return Optional.of(Instant.parse(timestampStr)); - } catch (DateTimeParseException e) { - logger.warn("Invalid importTimestamp format in metadata: {}", timestampStr); - return Optional.empty(); - } - }); - } - - /** - * Returns the PAIKKA application version that generated the data. - */ - public String getPaikkaVersion() { - return Optional.ofNullable(metadata) - .map(PaikkaMetadata::paikkaVersion) - .orElse("unknown"); - } } diff --git a/src/main/java/com/dedicatedcode/paikka/service/importer/ImportService.java b/src/main/java/com/dedicatedcode/paikka/service/importer/ImportService.java index 56e5ffa..52e3190 100644 --- a/src/main/java/com/dedicatedcode/paikka/service/importer/ImportService.java +++ b/src/main/java/com/dedicatedcode/paikka/service/importer/ImportService.java @@ -36,6 +36,7 @@ import org.locationtech.jts.geom.*; import org.locationtech.jts.io.WKBWriter; import org.rocksdb.*; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.IOException; @@ -65,6 +66,7 @@ public class ImportService { private final S2Helper s2Helper; private final GeometrySimplificationService geometrySimplificationService; private final PaikkaConfiguration config; + private final String version; private final Map tagCache = new ConcurrentHashMap<>(1000); @@ -73,10 +75,11 @@ public class ImportService { private final AtomicLong sequence = new AtomicLong(0); private final AtomicLong buildingSequence = new AtomicLong(0); - public ImportService(S2Helper s2Helper, GeometrySimplificationService geometrySimplificationService, PaikkaConfiguration config) { + public ImportService(S2Helper s2Helper, GeometrySimplificationService geometrySimplificationService, PaikkaConfiguration config, @Value("${paikka.version:1.0.0}") String version) { this.s2Helper = s2Helper; this.geometrySimplificationService = geometrySimplificationService; this.config = config; + this.version = version; this.fileReadWindowSize = calculateFileReadWindowSize(); } @@ -289,7 +292,7 @@ private void writeMetadataFile(List pbfFiles, Path dataDirectory) throws I String importTimestamp = DateTimeFormatter.ISO_INSTANT.format(now); String dataVersion = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss").withZone(ZoneOffset.UTC).format(now); ObjectMapper objectMapper = new ObjectMapper(); - PaikkaMetadata metadata = new PaikkaMetadata(importTimestamp, dataVersion, pbfFiles.stream().map(path -> path.getFileName().toString()).toList(), S2Helper.GRID_LEVEL, "1.0.0"); + PaikkaMetadata metadata = new PaikkaMetadata(importTimestamp, dataVersion, pbfFiles.stream().map(path -> path.getFileName().toString()).toList(), S2Helper.GRID_LEVEL, version); objectMapper.writeValue(metadataPath.toFile(), metadata); System.out.println("\n\033[1;32mMetadata file written to: " + metadataPath + "\033[0m"); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9d40717..49537ed 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,6 +12,7 @@ spring.web.resources.cache.cachecontrol.cache-public=true spring.web.resources.chain.strategy.content.enabled=true spring.web.resources.chain.strategy.content.paths=/css/**,/js/**,/img/**,/fonts/** +paikka.version=1.1.7 paikka.data-dir=./data paikka.import.threads=16 paikka.import.chunk-size=100000 diff --git a/src/test/java/com/dedicatedcode/paikka/service/ImportServiceTest.java b/src/test/java/com/dedicatedcode/paikka/service/ImportServiceTest.java index c3df746..0cd4751 100644 --- a/src/test/java/com/dedicatedcode/paikka/service/ImportServiceTest.java +++ b/src/test/java/com/dedicatedcode/paikka/service/ImportServiceTest.java @@ -68,7 +68,7 @@ void setUp() throws Exception { GeometrySimplificationService geometrySimplificationService = new GeometrySimplificationService(); S2Helper s2Helper = new S2Helper(); - ImportService importService = new ImportService(s2Helper, geometrySimplificationService, config); + ImportService importService = new ImportService(s2Helper, geometrySimplificationService, config, "1.0.0"); importService.importData(Collections.singletonList(tempImportFile.toString()), tempDataDir.toString()); } diff --git a/src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java b/src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java new file mode 100644 index 0000000..f0312b3 --- /dev/null +++ b/src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java @@ -0,0 +1,59 @@ +/* + * This file is part of paikka. + * + * Paikka is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 or + * any later version. + * + * Paikka is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Affero General Public License for more details. + * You should have received a copy of the GNU Affero General Public License + * along with Paikka. If not, see . + */ + +package com.dedicatedcode.paikka.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class MetadataServiceTest { + @SuppressWarnings("unchecked") + @Test + void shouldLoadMetadata() { + MetadataService candidata = new MetadataService(new MetaDataProvider() { + @Override + public boolean exists() { + return true; + } + + @Override + public InputStream get() throws FileNotFoundException { + return getClass().getResourceAsStream("/metadata.json"); + } + }, new ObjectMapper()); + candidata.reload(); + + Map loaded = candidata.getMetadata(); + assertNotNull(loaded); + assertEquals("2026-05-17T07:47:51.028675223Z", loaded.get("importTimestamp")); + assertEquals("20260517-074751", loaded.get("dataVersion")); + assertEquals(12, loaded.get("gridLevel")); + assertEquals("1.0.0", loaded.get("paikkaVersion")); + List files = (List) loaded.get("files"); + assertNotNull(files); + assertEquals(2, files.size()); + assertEquals("planet-filtered.pbf", files.get(0)); + assertEquals("test.pbf", files.get(1)); + } +} \ No newline at end of file diff --git a/src/test/resources/metadata.json b/src/test/resources/metadata.json new file mode 100644 index 0000000..e960c62 --- /dev/null +++ b/src/test/resources/metadata.json @@ -0,0 +1 @@ +{"importTimestamp":"2026-05-17T07:47:51.028675223Z","dataVersion":"20260517-074751","file":["planet-filtered.pbf", "test.pbf"],"gridLevel":12,"paikkaVersion":"1.0.0"} \ No newline at end of file From b2552fcd8be9097cf614aa5726b25cb4122db9df Mon Sep 17 00:00:00 2001 From: Daniel Graf Date: Mon, 18 May 2026 09:10:47 +0200 Subject: [PATCH 2/2] fix: correct variable name typo in `MetadataServiceTest` --- .../dedicatedcode/paikka/service/MetadataServiceTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java b/src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java index f0312b3..af3dbbb 100644 --- a/src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java +++ b/src/test/java/com/dedicatedcode/paikka/service/MetadataServiceTest.java @@ -31,7 +31,7 @@ class MetadataServiceTest { @SuppressWarnings("unchecked") @Test void shouldLoadMetadata() { - MetadataService candidata = new MetadataService(new MetaDataProvider() { + MetadataService candidate = new MetadataService(new MetaDataProvider() { @Override public boolean exists() { return true; @@ -42,9 +42,9 @@ public InputStream get() throws FileNotFoundException { return getClass().getResourceAsStream("/metadata.json"); } }, new ObjectMapper()); - candidata.reload(); + candidate.reload(); - Map loaded = candidata.getMetadata(); + Map loaded = candidate.getMetadata(); assertNotNull(loaded); assertEquals("2026-05-17T07:47:51.028675223Z", loaded.get("importTimestamp")); assertEquals("20260517-074751", loaded.get("dataVersion"));