diff --git a/src/main/java/fr/birdia/tile19/exception/BlankImageException.java b/src/main/java/fr/birdia/tile19/exception/BlankImageException.java new file mode 100644 index 0000000..6770a7c --- /dev/null +++ b/src/main/java/fr/birdia/tile19/exception/BlankImageException.java @@ -0,0 +1,7 @@ +package fr.birdia.tile19.exception; + +public class BlankImageException extends RuntimeException { + public BlankImageException(String message) { + super(message); + } +} diff --git a/src/main/java/fr/birdia/tile19/service/ImageExtenderService.java b/src/main/java/fr/birdia/tile19/service/ImageExtenderService.java index 691c306..c23c868 100644 --- a/src/main/java/fr/birdia/tile19/service/ImageExtenderService.java +++ b/src/main/java/fr/birdia/tile19/service/ImageExtenderService.java @@ -5,6 +5,7 @@ import fr.birdia.tile19.concurrency.Workers; import fr.birdia.tile19.model.airbus.AirbusProperties; import fr.birdia.tile19.service.airbus.AirbusPNEOService; +import fr.birdia.tile19.validator.ImageValidator; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; @@ -40,18 +41,21 @@ public class ImageExtenderService { private ImageDegraderService imageDegraderService; private AirbusPNEOService airbusPNEOService; private AirbusProperties airbusProperties; + private ImageValidator imageValidator; public ImageExtenderService( TilesDownloaderService downloader, TilesMergerService merger, Workers workers, ImageDegraderService imageDegraderService, - AirbusPNEOService airbusPNEOService) { + AirbusPNEOService airbusPNEOService, + ImageValidator imageValidator) { this.tileDownloader = downloader; this.tileMerger = merger; this.workers = workers; this.imageDegraderService = imageDegraderService; this.airbusPNEOService = airbusPNEOService; + this.imageValidator = imageValidator; } public String getLastUpdatedAtAirbus() { diff --git a/src/main/java/fr/birdia/tile19/service/TilesDownloaderService.java b/src/main/java/fr/birdia/tile19/service/TilesDownloaderService.java index 5202c2e..b49f7fb 100644 --- a/src/main/java/fr/birdia/tile19/service/TilesDownloaderService.java +++ b/src/main/java/fr/birdia/tile19/service/TilesDownloaderService.java @@ -2,6 +2,7 @@ import fr.birdia.tile19.model.airbus.AirbusProperties; import fr.birdia.tile19.service.airbus.AirbusPNEOService; +import fr.birdia.tile19.validator.ImageValidator; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; @@ -35,6 +36,7 @@ public class TilesDownloaderService { private final XYZToBBOXService xyzToBBoxService; private final AirbusPNEOService airbusPNEOService; private RestTemplate restTemplate; + private ImageValidator imageValidator; static double[] tileToLatLon(int x, int y, int zoom) { int n = (int) Math.pow(2, zoom); @@ -141,7 +143,10 @@ public BufferedImage download(int xTile, int yTile, int zoom, String server, Str String contentType = response.headers().firstValue("Content-Type").orElse(""); if (contentType.startsWith("image")) { try (InputStream is = response.body()) { - return ImageIO.read(is); + BufferedImage img = ImageIO.read(is); + log.info("Process image validator"); + imageValidator.accept(img); + return img; } } else { String error = new String(response.body().readAllBytes(), StandardCharsets.UTF_8); diff --git a/src/main/java/fr/birdia/tile19/validator/ImageValidator.java b/src/main/java/fr/birdia/tile19/validator/ImageValidator.java new file mode 100644 index 0000000..7666a65 --- /dev/null +++ b/src/main/java/fr/birdia/tile19/validator/ImageValidator.java @@ -0,0 +1,68 @@ +package fr.birdia.tile19.validator; + +import static org.springframework.http.MediaType.APPLICATION_JSON; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.birdia.tile19.exception.BlankImageException; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import javax.imageio.ImageIO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +@Slf4j +public class ImageValidator implements Consumer { + private static final String IMAGE_VALIDATOR_API_URL = System.getenv("IMAGE_VALIDATOR_API_URL"); + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + + public ImageValidator(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + this.objectMapper = new ObjectMapper(); + } + + @Override + public void accept(BufferedImage image) { + if (image == null) { + throw new BlankImageException("Image is null."); + } + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "jpeg", baos); + byte[] imageBytes = baos.toByteArray(); + String base64 = Base64.getEncoder().encodeToString(imageBytes); + Map body = new HashMap<>(); + body.put("base64image", base64); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(APPLICATION_JSON); + HttpEntity> request = new HttpEntity<>(body, headers); + ResponseEntity response = + restTemplate.postForEntity(IMAGE_VALIDATOR_API_URL, request, String.class); + + if (!response.getStatusCode().is2xxSuccessful()) { + throw new RuntimeException("Validator API error: " + response.getStatusCode()); + } + + JsonNode jsonNode = objectMapper.readTree(response.getBody()); + log.info("Validator response raw: {}", response.getBody()); + boolean isCorrupted = jsonNode.get("isCorrupted").asBoolean(); + + if (isCorrupted) { + throw new BlankImageException("Image is corrupted."); + } + + } catch (IOException e) { + throw new RuntimeException("Error processing image", e); + } + } +} diff --git a/src/test/java/fr/birdia/tile19/ImageExtenderIT.java b/src/test/java/fr/birdia/tile19/ImageExtenderIT.java index 4e05b49..a412565 100644 --- a/src/test/java/fr/birdia/tile19/ImageExtenderIT.java +++ b/src/test/java/fr/birdia/tile19/ImageExtenderIT.java @@ -4,6 +4,7 @@ import static fr.birdia.tile19.model.TileExtenderRequestBody.ShiftDirection.UP_DOWN_SIDE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import fr.birdia.tile19.conf.FacadeIT; import fr.birdia.tile19.endpoint.rest.controller.TileExtenderController; @@ -175,6 +176,28 @@ public TileExtenderRequestBody airbusBody() { .build(); } + public TileExtenderRequestBody throwBlankExceptionBody() { + return TileExtenderRequestBody.builder() + .x(521906) + .y(368610) + .z(20) + .server("geoserver") + .layer("PCRS") + .shiftNb(0) + .isCropped(false) + .latitude(47.047005283518075) + .longitude(-0.8176651895582759) + .isOpaque(false) + .build(); + } + + @Test + void test_throw_blank_exception() throws Exception { + assertThrows( + RuntimeException.class, + () -> tileExtenderController.extendImage(throwBlankExceptionBody())); + } + @Test void test_ign_tile19() throws Exception { ResponseEntity response = tileExtenderController.extendImage(ignBody()); @@ -183,6 +206,7 @@ void test_ign_tile19() throws Exception { @ParameterizedTest(name = "Download image for {0}") @MethodSource("cityTileProvider") + @Disabled public void extend_dijon(CityTileTestCase city) throws Exception { log.info("Processing city={}", city.city()); TileExtenderRequestBody body = createTileExtenderRequestBodyFrom(city); diff --git a/src/test/java/fr/birdia/tile19/unit/ImageExtenderServiceTest.java b/src/test/java/fr/birdia/tile19/unit/ImageExtenderServiceTest.java index 56e24fe..f0e3b37 100644 --- a/src/test/java/fr/birdia/tile19/unit/ImageExtenderServiceTest.java +++ b/src/test/java/fr/birdia/tile19/unit/ImageExtenderServiceTest.java @@ -11,6 +11,7 @@ import fr.birdia.tile19.service.TilesMergerService; import fr.birdia.tile19.service.XYZToBBOXService; import fr.birdia.tile19.service.airbus.AirbusPNEOService; +import fr.birdia.tile19.validator.ImageValidator; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.File; @@ -26,6 +27,7 @@ public class ImageExtenderServiceTest { XYZToBBOXService xyzToBBOXService = new XYZToBBOXService(); RestTemplate restTemplate = new RestTemplate(); + ImageValidator imageValidator = new ImageValidator(restTemplate); AirbusPNEOService airbusPNEOService = new AirbusPNEOService( restTemplate, @@ -33,12 +35,13 @@ public class ImageExtenderServiceTest { System.getenv("AIRBUS_API_KEY"), System.getenv("AIRBUS_SEARCHAPI_BASEURL")); TilesDownloaderService downloader = - new TilesDownloaderService(xyzToBBOXService, airbusPNEOService, restTemplate); + new TilesDownloaderService(xyzToBBOXService, airbusPNEOService, restTemplate, imageValidator); TilesMergerService merger = new TilesMergerService(); Workers workers = new Workers(); ImageDegraderService imageDegrader = new ImageDegraderService(); ImageExtenderService extender = - new ImageExtenderService(downloader, merger, workers, imageDegrader, airbusPNEOService); + new ImageExtenderService( + downloader, merger, workers, imageDegrader, airbusPNEOService, imageValidator); @Test public void full_herault_image_extension_ok() throws Exception { diff --git a/src/test/java/fr/birdia/tile19/unit/TilesDownloaderTest.java b/src/test/java/fr/birdia/tile19/unit/TilesDownloaderTest.java index 819e1a0..4c6b2e6 100644 --- a/src/test/java/fr/birdia/tile19/unit/TilesDownloaderTest.java +++ b/src/test/java/fr/birdia/tile19/unit/TilesDownloaderTest.java @@ -1,10 +1,11 @@ package fr.birdia.tile19.unit; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; import fr.birdia.tile19.service.TilesDownloaderService; import fr.birdia.tile19.service.XYZToBBOXService; import fr.birdia.tile19.service.airbus.AirbusPNEOService; +import fr.birdia.tile19.validator.ImageValidator; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; @@ -23,8 +24,9 @@ public class TilesDownloaderTest { System.getenv("AIRBUS_AUTHENTICATION_BASEURL"), System.getenv("AIRBUS_API_KEY"), System.getenv("AIRBUS_SEARCHAPI_BASEURL")); + ImageValidator imageValidator = new ImageValidator(restTemplate); TilesDownloaderService tilesDownloaderService = - new TilesDownloaderService(xyzToBBOXService, airbusPNEOService, restTemplate); + new TilesDownloaderService(xyzToBBOXService, airbusPNEOService, restTemplate, imageValidator); @Test public void tiles_downloader_geoserver_ok() throws IOException, InterruptedException {