diff --git a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageDefinition.java b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageDefinition.java index dca690d6..4f8443f3 100644 --- a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageDefinition.java +++ b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageDefinition.java @@ -81,11 +81,10 @@ public ImageEncoder getEncoder() { /** * Computes and returns a hash string representation for this instance. - * Note: This method currently returns the default hashCode as a string, which might not guarantee uniqueness. * * @return A string representing the hash of this instance. */ String getHashString() { - return Integer.toString(hashCode()); + return ImageManager.computeImageDefinitionHash(this); } } \ No newline at end of file diff --git a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageManager.java b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageManager.java index e0f4f234..a8ceecb8 100644 --- a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageManager.java +++ b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/ImageManager.java @@ -151,7 +151,7 @@ CompletableFuture loadFXImageFuture(ImageSource source, ImageTransformer * @param imageDefinition the image definition * @return the computed MD5 hash as a string */ - private String computeImageDefinitionHash(ImageDefinition imageDefinition) { + public static String computeImageDefinitionHash(ImageDefinition imageDefinition) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); byte[] hashBytes = digest.digest(imageDefinition.toJSON().toString().getBytes(StandardCharsets.UTF_8)); diff --git a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceFile.java b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceFile.java index 9e7d890c..fad67d35 100644 --- a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceFile.java +++ b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceFile.java @@ -69,6 +69,7 @@ public JSONObject toJSON() { JSONObject json = new JSONObject(); json.put("type", getClass().getSimpleName()); json.put("path", ImageUtils.escapeJson(file.getAbsolutePath())); + json.put("modified", file.lastModified()); return json; } } \ No newline at end of file diff --git a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceResource.java b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceResource.java index 85f269ec..a616d7ac 100644 --- a/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceResource.java +++ b/jpro-image-manager/src/main/java/one/jpro/platform/image/manager/source/ImageSourceResource.java @@ -67,8 +67,19 @@ public long identityHashValue() { public JSONObject toJSON() { JSONObject json = new JSONObject(); json.put("type", getClass().getSimpleName()); - // Escaping might be necessary depending on the structure of resourcePath. json.put("resourcePath", ImageUtils.escapeJson(resourcePath)); + // get last modified date + // It's important, so the images get recreated, when the files in the jar are updated + try { + URL resourceUrl = getClass().getResource(resourcePath); + if (resourceUrl != null) { + // we don't access any stream here, so we don't need to close it + URLConnection conn = resourceUrl.openConnection(); + json.put("modified", conn.getLastModified()); + } + } catch (Exception ex) { + throw new RuntimeException("Error obtaining modification date for resource: " + resourcePath, ex); + } return json; } diff --git a/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/ImageManagerTest.java b/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/ImageManagerTest.java index 05e87483..97220e01 100644 --- a/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/ImageManagerTest.java +++ b/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/ImageManagerTest.java @@ -14,8 +14,14 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import java.io.IOException; +import java.nio.file.Files; + import java.awt.image.BufferedImage; import java.io.File; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileTime; +import java.time.Instant; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -26,6 +32,9 @@ class ImageManagerTest { ImageManager manager; ImageDefinition def; + File imageDef2File; + ImageDefinition imageDef2; + @BeforeEach @@ -44,6 +53,12 @@ void setUp() throws InterruptedException { ImageTransformer transformer = new ImageTransformerFitWidth(200); ImageEncoder encoder = new ImageEncoderPNG(); def = new ImageDefinition(source, transformer, encoder); + + imageDef2File = new File("testImage.png"); + ImageSource source2 = new ImageSourceFile(imageDef2File); + ImageTransformer transformer2 = new ImageTransformerFitWidth(200); + ImageEncoder encoder2 = new ImageEncoderPNG(); + imageDef2 = new ImageDefinition(source2, transformer2, encoder2); } @Test @@ -68,6 +83,45 @@ void testImageNotCreatedTwice() { assertEquals(imageFileBefore.lastModified(), imageFileAfter.lastModified(), "Image was created again."); } + @Test + void testImageRecreateOnChange() throws IOException { + // Copy file1 to imageDef2File + File file1 = new File(getClass().getResource("/testImage.png").getFile()); + File file2 = new File(getClass().getResource("/logo.png").getFile()); + assertTrue(file1.exists()); + assertTrue(file2.exists()); + + Files.copy(file1.toPath(), imageDef2File.toPath(), StandardCopyOption.REPLACE_EXISTING); + + var image1 = manager.loadImage(imageDef2); + String hash1 = imageDef2.getHashString(); + System.out.println("ModifiedDate1: " + imageDef2File.lastModified()); + + Files.copy(file2.toPath(), imageDef2File.toPath(), StandardCopyOption.REPLACE_EXISTING); + System.out.println("ModifiedDate2: " + imageDef2File.lastModified()); + Files.setLastModifiedTime(imageDef2File.toPath(), FileTime.from(Instant.now())); + System.out.println("ModifiedDate3: " + imageDef2File.lastModified()); + + var image2 = manager.loadImage(imageDef2); + String hash2 = imageDef2.getHashString(); + + System.out.println("hash1: " + hash1); + System.out.println("hash2: " + hash2); + assertNotEquals(hash1, hash2, "changed image file should have different hash"); + + File imageFile1 = image1.getFile(); + File imageFile2 = image2.getFile(); + + assertTrue(imageFile1.exists()); + assertTrue(imageFile2.exists()); + + System.out.println("imageFile1: " + imageFile1.lastModified()); + System.out.println("imageFile2: " + imageFile2.lastModified()); + + assertNotEquals(imageFile1.lastModified(), imageFile2.lastModified(), + "Image was not created again."); + } + @Test void testLoadImageFuture() { CompletableFuture future = manager.loadImageFuture(def); diff --git a/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceFileTest.java b/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceFileTest.java index 46db8ee6..e3025ab3 100644 --- a/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceFileTest.java +++ b/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceFileTest.java @@ -36,6 +36,7 @@ public void testToJson() { JSONObject json = new JSONObject(); json.put("type", "ImageSourceFile"); json.put("path", ImageUtils.escapeJson(testImageFile.getAbsolutePath())); + json.put("modified", testImageFile.lastModified()); assertTrue(imageSource.toJSON().similar(json)); } diff --git a/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceResourceTest.java b/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceResourceTest.java index 5d070b0d..d3a1916c 100644 --- a/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceResourceTest.java +++ b/jpro-image-manager/src/test/java/one/jpro/platform/image/manager/source/ImageSourceResourceTest.java @@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test; import java.awt.image.BufferedImage; +import java.net.URL; +import java.net.URLConnection; import static org.junit.jupiter.api.Assertions.*; @@ -30,12 +32,18 @@ void testIdentityHashValue_validResourcePath_returnsHash() { } @Test - void testToJson_returnsExpectedJson() { + void testToJson_returnsExpectedJson() throws Exception { ImageSourceResource resource = new ImageSourceResource("/testImage.png"); JSONObject json = new JSONObject(); json.put("type", "ImageSourceResource"); json.put("resourcePath", "/testImage.png"); + URL resourceUrl = getClass().getResource("/testImage.png"); + if (resourceUrl != null) { + // we don't access any stream here, so we don't need to close it + URLConnection conn = resourceUrl.openConnection(); + json.put("modified", conn.getLastModified()); + } assertTrue(resource.toJSON().similar(json)); } } \ No newline at end of file