From 34f34e936e26a4d7a974e87a52e94324987ccd80 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sun, 22 Dec 2024 12:13:21 +0000 Subject: [PATCH 01/28] Removed padding override in DialogUtil to allow application code add its own padding --- .../java/dev/webfx/stack/ui/dialog/DialogUtil.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java b/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java index 0e2a11d78..2e7455207 100644 --- a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java +++ b/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java @@ -12,7 +12,6 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.geometry.HPos; -import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.geometry.VPos; import javafx.scene.Node; @@ -42,9 +41,10 @@ public static DialogCallback showModalNodeInGoldLayout(Region modalNode, Pane pa } public static DialogCallback showModalNodeInGoldLayout(Region modalNode, Pane parent, double percentageWidth, double percentageHeight) { - Insets padding = modalNode.getPadding(); + //Insets padding = modalNode.getPadding(); return showModalNode(LayoutUtil.createGoldLayout(decorate(modalNode), percentageWidth, percentageHeight), parent) - .addCloseHook(() -> modalNode.setPadding(padding)); + //.addCloseHook(() -> modalNode.setPadding(padding)) + ; } public static DialogCallback showModalNode(Region modalNode, Pane parent) { @@ -68,9 +68,11 @@ private static void setUpModalNodeResizeRelocate(Region modalNode, Pane parent, public static BorderPane decorate(Node content) { // Setting max width/height to pref width/height (otherwise the grid pane takes all space with cells in top left corner) - if (content instanceof Region) - LayoutUtil.setMaxSizeToPref(LayoutUtil.createPadding((Region) content, 10)); - BorderPane decorator = new BorderPane(content); + if (content instanceof Region) { + Region region = (Region) content; + LayoutUtil.setMaxSizeToPref(region); + } + BorderPane decorator = LayoutUtil.createPadding(new BorderPane(content), 10); decorator.backgroundProperty().bind(dialogBackgroundProperty()); decorator.borderProperty().bind(dialogBorderProperty()); decorator.setMinHeight(0d); From b00e0a2f24de9bebb761ea14e6ac040e5d552739 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sun, 22 Dec 2024 12:13:38 +0000 Subject: [PATCH 02/28] Added SimpleDialogBuilder class --- .../controls/dialog/SimpleDialogBuilder.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java diff --git a/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java new file mode 100644 index 000000000..eca658603 --- /dev/null +++ b/webfx-stack-ui-controls/src/main/java/dev/webfx/stack/ui/controls/dialog/SimpleDialogBuilder.java @@ -0,0 +1,32 @@ +package dev.webfx.stack.ui.controls.dialog; + +import dev.webfx.stack.ui.dialog.DialogCallback; +import javafx.scene.layout.Region; + +/** + * @author Bruno Salmon + */ +public class SimpleDialogBuilder implements DialogBuilder { + + private final Region content; + private DialogCallback dialogCallback; + + public SimpleDialogBuilder(Region content) { + this.content = content; + } + + @Override + public Region build() { + return content; + } + + @Override + public void setDialogCallback(DialogCallback dialogCallback) { + this.dialogCallback = dialogCallback; + } + + @Override + public DialogCallback getDialogCallback() { + return dialogCallback; + } +} From 35ce04b50103bac7390be53f050694818ed06e70 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sun, 22 Dec 2024 12:14:19 +0000 Subject: [PATCH 03/28] Added newToggleButton() factory class in I18nControls --- .../main/java/dev/webfx/stack/i18n/controls/I18nControls.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java b/webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java index 01b2fdf44..448d0803e 100644 --- a/webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java +++ b/webfx-stack-i18n-controls/src/main/java/dev/webfx/stack/i18n/controls/I18nControls.java @@ -89,4 +89,8 @@ public static CheckBox newCheckBox(Object i18nKey, Object... args) { return bindI18nProperties(new CheckBox(), i18nKey, args); } + public static ToggleButton newToggleButton(Object i18nKey, Object... args) { + return bindI18nProperties(new ToggleButton(), i18nKey, args); + } + } From 5cb4ff7bb8af72d58d605759cd104cc972e761f0 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 28 Dec 2024 16:26:19 +0000 Subject: [PATCH 04/28] Replaced File by Blob in Cloudinary --- webfx-stack-cloud-image-client/pom.xml | 8 ++++---- .../cloud/image/impl/client/ClientImageService.java | 4 ++-- .../src/main/java/module-info.java | 2 +- webfx-stack-cloud-image-cloudinary/pom.xml | 6 +++--- .../cloud/image/impl/cloudinary/Cloudinary.java | 13 ++++++++----- .../src/main/java/module-info.java | 2 +- webfx-stack-cloud-image/pom.xml | 6 +++--- .../webfx/stack/cloud/image/CloudImageService.java | 5 ++--- .../src/main/java/module-info.java | 2 +- 9 files changed, 25 insertions(+), 23 deletions(-) diff --git a/webfx-stack-cloud-image-client/pom.xml b/webfx-stack-cloud-image-client/pom.xml index 7bb530d05..8b6a10514 100644 --- a/webfx-stack-cloud-image-client/pom.xml +++ b/webfx-stack-cloud-image-client/pom.xml @@ -23,25 +23,25 @@ dev.webfx - webfx-platform-conf + webfx-platform-blob 0.1.0-SNAPSHOT dev.webfx - webfx-platform-console + webfx-platform-conf 0.1.0-SNAPSHOT dev.webfx - webfx-platform-fetch + webfx-platform-console 0.1.0-SNAPSHOT dev.webfx - webfx-platform-file + webfx-platform-fetch 0.1.0-SNAPSHOT diff --git a/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java b/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java index 85a707cf7..744db15af 100644 --- a/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java +++ b/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java @@ -1,13 +1,13 @@ package dev.webfx.stack.cloud.image.impl.client; import dev.webfx.platform.async.Future; +import dev.webfx.platform.blob.Blob; import dev.webfx.platform.conf.ConfigLoader; import dev.webfx.platform.console.Console; import dev.webfx.platform.fetch.CorsMode; import dev.webfx.platform.fetch.Fetch; import dev.webfx.platform.fetch.FetchOptions; import dev.webfx.platform.fetch.FormData; -import dev.webfx.platform.file.File; import dev.webfx.platform.util.http.HttpMethod; import dev.webfx.platform.util.http.HttpResponseStatus; import dev.webfx.stack.cloud.image.CloudImageService; @@ -51,7 +51,7 @@ public Future exists(String id) { }); } - public Future upload(File file, String id, boolean overwrite) { + public Future upload(Blob file, String id, boolean overwrite) { return Fetch.fetch(uploadUrl, new FetchOptions() .setMethod(HttpMethod.POST) .setMode(CorsMode.NO_CORS) diff --git a/webfx-stack-cloud-image-client/src/main/java/module-info.java b/webfx-stack-cloud-image-client/src/main/java/module-info.java index 0e1b4f61f..ab730d6b5 100644 --- a/webfx-stack-cloud-image-client/src/main/java/module-info.java +++ b/webfx-stack-cloud-image-client/src/main/java/module-info.java @@ -4,10 +4,10 @@ // Direct dependencies modules requires webfx.platform.async; + requires webfx.platform.blob; requires webfx.platform.conf; requires webfx.platform.console; requires webfx.platform.fetch; - requires webfx.platform.file; requires webfx.platform.util.http; requires webfx.stack.cloud.image; diff --git a/webfx-stack-cloud-image-cloudinary/pom.xml b/webfx-stack-cloud-image-cloudinary/pom.xml index 89d97eb06..d9a0f5a9a 100644 --- a/webfx-stack-cloud-image-cloudinary/pom.xml +++ b/webfx-stack-cloud-image-cloudinary/pom.xml @@ -23,19 +23,19 @@ dev.webfx - webfx-platform-conf + webfx-platform-blob 0.1.0-SNAPSHOT dev.webfx - webfx-platform-fetch + webfx-platform-conf 0.1.0-SNAPSHOT dev.webfx - webfx-platform-file + webfx-platform-fetch 0.1.0-SNAPSHOT diff --git a/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java b/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java index 88b44513c..be4dea5ad 100644 --- a/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java +++ b/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java @@ -1,17 +1,20 @@ package dev.webfx.stack.cloud.image.impl.cloudinary; import dev.webfx.platform.async.Future; +import dev.webfx.platform.blob.Blob; import dev.webfx.platform.conf.ConfigLoader; import dev.webfx.platform.fetch.*; -import dev.webfx.platform.file.File; -import dev.webfx.platform.util.http.HttpHeaders; -import dev.webfx.platform.util.http.HttpMethod; import dev.webfx.platform.util.Strings; import dev.webfx.platform.util.collection.Collections; +import dev.webfx.platform.util.http.HttpHeaders; +import dev.webfx.platform.util.http.HttpMethod; import dev.webfx.stack.cloud.image.impl.fetchbased.FetchBasedCloudImageService; import dev.webfx.stack.hash.sha1.Sha1; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; /** * @author Bruno Salmon @@ -43,7 +46,7 @@ public Future exists(String id) { .map(Response::ok); } - public Future upload(File file, String id, boolean overwrite) { + public Future upload(Blob file, String id, boolean overwrite) { return fetchJsonObject( "https://api.cloudinary.com/v1_1/" + cloudName + "/image/upload", HttpMethod.POST, diff --git a/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java b/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java index 517de7810..a26d909dc 100644 --- a/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java +++ b/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java @@ -4,9 +4,9 @@ // Direct dependencies modules requires webfx.platform.async; + requires webfx.platform.blob; requires webfx.platform.conf; requires webfx.platform.fetch; - requires webfx.platform.file; requires webfx.platform.util; requires webfx.platform.util.http; requires webfx.stack.cloud.image; diff --git a/webfx-stack-cloud-image/pom.xml b/webfx-stack-cloud-image/pom.xml index 87803014d..bb98529e6 100644 --- a/webfx-stack-cloud-image/pom.xml +++ b/webfx-stack-cloud-image/pom.xml @@ -29,19 +29,19 @@ dev.webfx - webfx-platform-fetch + webfx-platform-blob 0.1.0-SNAPSHOT dev.webfx - webfx-platform-fetch-ast-json + webfx-platform-fetch 0.1.0-SNAPSHOT dev.webfx - webfx-platform-file + webfx-platform-fetch-ast-json 0.1.0-SNAPSHOT diff --git a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java index 8c8ad1d74..ad2f9f1ed 100644 --- a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java +++ b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java @@ -1,8 +1,7 @@ package dev.webfx.stack.cloud.image; -import dev.webfx.platform.ast.ReadOnlyAstObject; import dev.webfx.platform.async.Future; -import dev.webfx.platform.file.File; +import dev.webfx.platform.blob.Blob; /** * @author Bruno Salmon @@ -11,7 +10,7 @@ public interface CloudImageService { Future exists(String id); - Future upload(File file, String id, boolean overwrite); + Future upload(Blob file, String id, boolean overwrite); Future delete(String id, boolean invalidate); diff --git a/webfx-stack-cloud-image/src/main/java/module-info.java b/webfx-stack-cloud-image/src/main/java/module-info.java index 51e812655..894fb9a4d 100644 --- a/webfx-stack-cloud-image/src/main/java/module-info.java +++ b/webfx-stack-cloud-image/src/main/java/module-info.java @@ -5,9 +5,9 @@ // Direct dependencies modules requires transitive webfx.platform.ast; requires webfx.platform.async; + requires webfx.platform.blob; requires webfx.platform.fetch; requires webfx.platform.fetch.ast.json; - requires webfx.platform.file; requires webfx.platform.util.http; // Exported packages From 5d8e352a16393e6cd5b86f32f8ec5f1709aa5368 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 28 Dec 2024 16:26:50 +0000 Subject: [PATCH 05/28] Corrected equals method in DynamicEntity --- .../java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java index 59a6ffcab..f7dc0b227 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java @@ -128,7 +128,11 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DynamicEntity that = (DynamicEntity) o; - return id.equals(that.id); + if (!id.equals(that.id)) + return false; +// if (!fieldValues.equals(that.fieldValues)) +// return false; + return Objects.equals(underlyingEntity, that.underlyingEntity); } @Override From be6d6d6b2603498f6ded211dd128822fbfb39a40 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Dec 2024 15:43:38 +0000 Subject: [PATCH 06/28] Initial international dictionaries creation --- ...bfx-magiclink@en.properties => webfx-magiclink_en.properties} | 0 .../src/main/webfx/i18n/webfx-stack-logout@fr.properties | 1 - ...x-stack-time@en.properties => webfx-stack-time_en.properties} | 0 ...x-stack-time@fr.properties => webfx-stack-time_fr.properties} | 0 4 files changed, 1 deletion(-) rename webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/webfx/i18n/{webfx-magiclink@en.properties => webfx-magiclink_en.properties} (100%) delete mode 100644 webfx-stack-authn-logout-client/src/main/webfx/i18n/webfx-stack-logout@fr.properties rename webfx-stack-i18n-time-plugin/src/main/webfx/i18n/{webfx-stack-time@en.properties => webfx-stack-time_en.properties} (100%) rename webfx-stack-i18n-time-plugin/src/main/webfx/i18n/{webfx-stack-time@fr.properties => webfx-stack-time_fr.properties} (100%) diff --git a/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/webfx/i18n/webfx-magiclink@en.properties b/webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/webfx/i18n/webfx-magiclink_en.properties similarity index 100% rename from webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/webfx/i18n/webfx-magiclink@en.properties rename to webfx-stack-authn-login-ui-gateway-magiclink-plugin/src/main/webfx/i18n/webfx-magiclink_en.properties diff --git a/webfx-stack-authn-logout-client/src/main/webfx/i18n/webfx-stack-logout@fr.properties b/webfx-stack-authn-logout-client/src/main/webfx/i18n/webfx-stack-logout@fr.properties deleted file mode 100644 index 12c74f222..000000000 --- a/webfx-stack-authn-logout-client/src/main/webfx/i18n/webfx-stack-logout@fr.properties +++ /dev/null @@ -1 +0,0 @@ -Logout = Se déconnecter \ No newline at end of file diff --git a/webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time@en.properties b/webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time_en.properties similarity index 100% rename from webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time@en.properties rename to webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time_en.properties diff --git a/webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time@fr.properties b/webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time_fr.properties similarity index 100% rename from webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time@fr.properties rename to webfx-stack-i18n-time-plugin/src/main/webfx/i18n/webfx-stack-time_fr.properties From a59deea8d5ccfc9b437a17625136e422e8e306b0 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 30 Dec 2024 16:02:26 +0000 Subject: [PATCH 07/28] Added a test of (operationRequest == null) and return false if that's the case in OperationActionRegistry --- .../stack/ui/operation/action/OperationActionRegistry.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java b/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java index 0691fe3da..84dc96e07 100644 --- a/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java +++ b/webfx-stack-ui-operation-action/src/main/java/dev/webfx/stack/ui/operation/action/OperationActionRegistry.java @@ -166,6 +166,8 @@ private boolean bindOperationActionGraphicalPropertiesNow(OperationAction // The binding is possible only if a graphical action has been registered for that operation // Instantiating an operation request just to have the request class or operation code A operationRequest = newOperationActionRequest(executableOperationAction); + if (operationRequest == null) + return false; // Registering the operation action (should it be done only once?) Class operationRequestClass = operationRequest.getClass(); From f5d05533f06d1cd478d0e80ab8233fbfc477b513 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Mon, 30 Dec 2024 16:53:37 +0000 Subject: [PATCH 08/28] Fixed issue for retrieving foreign entities from an updated entity created from another store --- .../dev/webfx/stack/orm/entity/UpdateStore.java | 9 +++++++-- .../webfx/stack/orm/entity/impl/DynamicEntity.java | 14 +++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java index 468f31f71..f5b0391b7 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/UpdateStore.java @@ -6,6 +6,7 @@ import dev.webfx.stack.db.submit.SubmitArgument; import dev.webfx.stack.db.submit.SubmitResult; import dev.webfx.stack.orm.domainmodel.DataSourceModel; +import dev.webfx.stack.orm.entity.impl.DynamicEntity; import dev.webfx.stack.orm.entity.impl.UpdateStoreImpl; import dev.webfx.stack.orm.entity.result.EntityChanges; @@ -25,8 +26,12 @@ default E insertEntity(Object domainClassId, Object primaryKe E insertEntity(EntityId entityId); default E updateEntity(E entity) { - updateEntity(entity.getId()); - return copyEntity(entity); + E updatedEntity = updateEntity(entity.getId()); + if (updatedEntity instanceof DynamicEntity && entity != updatedEntity) { + DynamicEntity dynamicEntity = (DynamicEntity) updatedEntity; + dynamicEntity.setUnderlyingEntity(entity); + } + return updatedEntity; } E updateEntity(EntityId entityId); diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java index 59a6ffcab..cf968cbc1 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java @@ -19,7 +19,7 @@ public class DynamicEntity implements Entity { private EntityId id; private final EntityStore store; - private final Entity underlyingEntity; + private Entity underlyingEntity; private final Map fieldValues = new HashMap<>(); // fields used by EntityBindings only: private Map fieldProperties; // lazy instantiation @@ -32,6 +32,10 @@ protected DynamicEntity(EntityId id, EntityStore store) { underlyingEntity = underlyingStore != null ? underlyingStore.getEntity(id) : null; } + public void setUnderlyingEntity(Entity underlyingEntity) { // meant to be called by UpdateStore.updateEntity() only + this.underlyingEntity = underlyingEntity; + } + @Override public EntityId getId() { return id; @@ -97,6 +101,14 @@ public EntityId getForeignEntityId(Object foreignFieldId) { return null; } + @Override + public E getForeignEntity(Object foreignFieldId) { + E foreignEntity = Entity.super.getForeignEntity(foreignFieldId); + if (foreignEntity == null && underlyingEntity != null) + foreignEntity = underlyingEntity.getForeignEntity(foreignFieldId); + return foreignEntity; + } + @Override public void setFieldValue(Object domainFieldId, Object value) { fieldValues.put(domainFieldId, value); From f1985b845e4406d33f726dd2a405b9cc7392c8a7 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 1 Jan 2025 10:32:56 +0000 Subject: [PATCH 09/28] Commented LayoutUtil.setMaxSizeToPref() in DialogUtil.decorate() --- .../src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java b/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java index 2e7455207..748af233c 100644 --- a/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java +++ b/webfx-stack-ui-dialog/src/main/java/dev/webfx/stack/ui/dialog/DialogUtil.java @@ -67,12 +67,14 @@ private static void setUpModalNodeResizeRelocate(Region modalNode, Pane parent, } public static BorderPane decorate(Node content) { + /* Commented out because the content may set its own max width/height (ex: Festival creator dialog) + // TODO: completely remove if no side effect, or set the max size to pref size only if the content has no max size set // Setting max width/height to pref width/height (otherwise the grid pane takes all space with cells in top left corner) if (content instanceof Region) { Region region = (Region) content; LayoutUtil.setMaxSizeToPref(region); - } - BorderPane decorator = LayoutUtil.createPadding(new BorderPane(content), 10); + }*/ + BorderPane decorator = LayoutUtil.createPadding(new BorderPane(content), 0); decorator.backgroundProperty().bind(dialogBackgroundProperty()); decorator.borderProperty().bind(dialogBorderProperty()); decorator.setMinHeight(0d); From f70846fbbffbf0300c24ff234256c5b0052e30a6 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 1 Jan 2025 14:11:14 +0000 Subject: [PATCH 10/28] Added a parameter in CloudImageService to prevent cache to display an old image --- .../java/dev/webfx/stack/cloud/image/CloudImageService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java index ad2f9f1ed..ecdceb16c 100644 --- a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java +++ b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java @@ -24,6 +24,9 @@ default String url(String source, int width, int height) { url = url.replace(":height", "" + height); else url = url.replace("/h_:height", ""); // temporary + + //We add a random parameter to prevent the cache to display an old image + url = url + "?t=" + System.currentTimeMillis(); return url; } From 500a702069939967e9d8e0bfb5c5af3da5932a71 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 1 Jan 2025 16:35:53 +0000 Subject: [PATCH 11/28] Ensured ClientImageService always wait for urlPattern to be loaded before returning --- .../image/impl/client/ClientImageService.java | 61 +++++++++++-------- .../stack/cloud/image/CloudImageService.java | 5 +- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java b/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java index 744db15af..cf8ceacf8 100644 --- a/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java +++ b/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java @@ -1,6 +1,7 @@ package dev.webfx.stack.cloud.image.impl.client; import dev.webfx.platform.async.Future; +import dev.webfx.platform.async.Promise; import dev.webfx.platform.blob.Blob; import dev.webfx.platform.conf.ConfigLoader; import dev.webfx.platform.console.Console; @@ -23,8 +24,11 @@ public class ClientImageService implements CloudImageService { private String uploadUrl; private String deleteUrl; private String urlPattern; + private final Future urlPatternFuture; public ClientImageService() { + Promise urlPatternPromise = Promise.promise(); + urlPatternFuture = urlPatternPromise.future(); ConfigLoader.onConfigLoaded(CONFIG_PATH, config -> { existsUrl = config.getString("existsUrl"); uploadUrl = config.getString("uploadUrl"); @@ -33,45 +37,52 @@ public ClientImageService() { Fetch.fetchText(urlPatternUrl, new FetchOptions() .setMethod(HttpMethod.GET) .setMode(CorsMode.NO_CORS) - ) - .onFailure(Console::log) - .onSuccess(text -> urlPattern = text); + ) + .onFailure(Console::log) + .onSuccess(text -> urlPattern = text) + .onComplete(ar -> urlPatternPromise.complete()); }); } + // Helper method to ensure that we return the future only when urlPattern is loaded (because the client may need to + // call CloudImageService.url() method - which requires urlPattern to be loaded - after returning the future). + private Future whenUrlPatternLoaded(Future future) { + return urlPatternFuture.compose(v -> future); + } + public Future exists(String id) { - return Fetch.fetch(existsUrl, new FetchOptions() - .setMethod(HttpMethod.POST) - .setMode(CorsMode.NO_CORS) - .setBody(new FormData().append("id", id)) + return whenUrlPatternLoaded(Fetch.fetch(existsUrl, new FetchOptions() + .setMethod(HttpMethod.POST) + .setMode(CorsMode.NO_CORS) + .setBody(new FormData().append("id", id)) ).compose(response -> { if (response.ok()) return Future.succeededFuture(response.status() == HttpResponseStatus.OK_200); // OK_200 = exists, NO_CONTENT_204 = doesn't exist return Future.failedFuture("Failed to call " + existsUrl + ", status = " + response.statusText()); - }); + })); } public Future upload(Blob file, String id, boolean overwrite) { - return Fetch.fetch(uploadUrl, new FetchOptions() - .setMethod(HttpMethod.POST) - .setMode(CorsMode.NO_CORS) - .setBody(new FormData() - .append("id", id) - .append("overwrite", overwrite) - .append("file", file, id) - ) - ).map(response -> null); + return whenUrlPatternLoaded(Fetch.fetch(uploadUrl, new FetchOptions() + .setMethod(HttpMethod.POST) + .setMode(CorsMode.NO_CORS) + .setBody(new FormData() + .append("id", id) + .append("overwrite", overwrite) + .append("file", file, id) + ) + ).map(response -> null)); } public Future delete(String id, boolean invalidate) { - return Fetch.fetch(deleteUrl, new FetchOptions() - .setMethod(HttpMethod.POST) - .setMode(CorsMode.NO_CORS) - .setBody(new FormData() - .append("id", id) - .append("invalidate", invalidate) - ) - ).map(response -> null); + return whenUrlPatternLoaded(Fetch.fetch(deleteUrl, new FetchOptions() + .setMethod(HttpMethod.POST) + .setMode(CorsMode.NO_CORS) + .setBody(new FormData() + .append("id", id) + .append("invalidate", invalidate) + ) + ).map(response -> null)); } @Override diff --git a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java index ecdceb16c..07a2a639e 100644 --- a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java +++ b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java @@ -15,7 +15,10 @@ public interface CloudImageService { Future delete(String id, boolean invalidate); default String url(String source, int width, int height) { - String url = urlPattern().replace(":source", source); + String urlPattern = urlPattern(); + if (urlPattern == null) + throw new IllegalStateException("[CloudImageService] urlPattern is null"); + String url = urlPattern.replace(":source", source); if (width > 0) url = url.replace(":width", "" + width); else From 9dd7c707036cc67e57743c23d93a86339412d24c Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 1 Jan 2025 17:00:48 +0000 Subject: [PATCH 12/28] Added invalidate=true in Cloudinary to fix CDN image cache issue --- .../dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java | 1 + 1 file changed, 1 insertion(+) diff --git a/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java b/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java index be4dea5ad..df4ae3005 100644 --- a/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java +++ b/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java @@ -54,6 +54,7 @@ public Future upload(Blob file, String id, boolean overwrite) { signFormData(new FormData() .append("public_id", id) .append("overwrite", overwrite) + .append("invalidate", true) // Otherwise the new image might not be displayed immediately after upload ).append("file", file, id) ) ).map(json -> null); From 12f9daabc03ce99b24d3e151a40e7b09600fed7c Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 3 Jan 2025 11:30:31 +0000 Subject: [PATCH 13/28] Renamed file parameter to blob in Cloudinary API --- .../stack/cloud/image/impl/client/ClientImageService.java | 4 ++-- .../java/dev/webfx/stack/cloud/image/CloudImageService.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java b/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java index cf8ceacf8..cbe8f9873 100644 --- a/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java +++ b/webfx-stack-cloud-image-client/src/main/java/dev/webfx/stack/cloud/image/impl/client/ClientImageService.java @@ -62,14 +62,14 @@ public Future exists(String id) { })); } - public Future upload(Blob file, String id, boolean overwrite) { + public Future upload(Blob blob, String id, boolean overwrite) { return whenUrlPatternLoaded(Fetch.fetch(uploadUrl, new FetchOptions() .setMethod(HttpMethod.POST) .setMode(CorsMode.NO_CORS) .setBody(new FormData() .append("id", id) .append("overwrite", overwrite) - .append("file", file, id) + .append("file", blob, id) ) ).map(response -> null)); } diff --git a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java index 07a2a639e..95d4889aa 100644 --- a/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java +++ b/webfx-stack-cloud-image/src/main/java/dev/webfx/stack/cloud/image/CloudImageService.java @@ -10,7 +10,7 @@ public interface CloudImageService { Future exists(String id); - Future upload(Blob file, String id, boolean overwrite); + Future upload(Blob blob, String id, boolean overwrite); Future delete(String id, boolean invalidate); From 50f39fa9371a93f2d6438c74e687b1bb9149a033 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 3 Jan 2025 11:39:04 +0000 Subject: [PATCH 14/28] Enabled json reply in Cloudinary --- webfx-stack-cloud-image-cloudinary/pom.xml | 12 ++++++++++++ .../image/impl/cloudinary/Cloudinary.java | 19 +++++++++++++++---- .../src/main/java/module-info.java | 2 ++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/webfx-stack-cloud-image-cloudinary/pom.xml b/webfx-stack-cloud-image-cloudinary/pom.xml index d9a0f5a9a..c40c514ae 100644 --- a/webfx-stack-cloud-image-cloudinary/pom.xml +++ b/webfx-stack-cloud-image-cloudinary/pom.xml @@ -15,6 +15,12 @@ + + dev.webfx + webfx-platform-ast + 0.1.0-SNAPSHOT + + dev.webfx webfx-platform-async @@ -33,6 +39,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-platform-console + 0.1.0-SNAPSHOT + + dev.webfx webfx-platform-fetch diff --git a/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java b/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java index df4ae3005..0527a3382 100644 --- a/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java +++ b/webfx-stack-cloud-image-cloudinary/src/main/java/dev/webfx/stack/cloud/image/impl/cloudinary/Cloudinary.java @@ -1,8 +1,11 @@ package dev.webfx.stack.cloud.image.impl.cloudinary; +import dev.webfx.platform.ast.AST; +import dev.webfx.platform.ast.ReadOnlyAstObject; import dev.webfx.platform.async.Future; import dev.webfx.platform.blob.Blob; import dev.webfx.platform.conf.ConfigLoader; +import dev.webfx.platform.console.Console; import dev.webfx.platform.fetch.*; import dev.webfx.platform.util.Strings; import dev.webfx.platform.util.collection.Collections; @@ -21,6 +24,7 @@ */ public class Cloudinary extends FetchBasedCloudImageService { + private static final boolean LOG_JSON_REPLY = true; private static final String CONFIG_PATH = "webfx.stack.cloud.image.cloudinary"; private String cloudName; @@ -46,7 +50,7 @@ public Future exists(String id) { .map(Response::ok); } - public Future upload(Blob file, String id, boolean overwrite) { + public Future upload(Blob blob, String id, boolean overwrite) { return fetchJsonObject( "https://api.cloudinary.com/v1_1/" + cloudName + "/image/upload", HttpMethod.POST, @@ -55,9 +59,9 @@ public Future upload(Blob file, String id, boolean overwrite) { .append("public_id", id) .append("overwrite", overwrite) .append("invalidate", true) // Otherwise the new image might not be displayed immediately after upload - ).append("file", file, id) + ).append("file", blob, id) ) - ).map(json -> null); + ).map(json -> logJsonReply("upload", json)); } public Future delete(String id, boolean invalidate) { @@ -70,7 +74,14 @@ public Future delete(String id, boolean invalidate) { .append("invalidate", invalidate) ) ) - ).map(json -> null); + ).map(json -> logJsonReply("delete", json)); + } + + private static Void logJsonReply(String operation, ReadOnlyAstObject jsonReply) { + if (LOG_JSON_REPLY) { + Console.log("[CLOUDINARY] - " + operation + " - json reply = " + AST.formatObject(jsonReply, "json")); + } + return null; } @Override diff --git a/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java b/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java index a26d909dc..dc0bebe26 100644 --- a/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java +++ b/webfx-stack-cloud-image-cloudinary/src/main/java/module-info.java @@ -3,9 +3,11 @@ module webfx.stack.cloud.image.cloudinary { // Direct dependencies modules + requires webfx.platform.ast; requires webfx.platform.async; requires webfx.platform.blob; requires webfx.platform.conf; + requires webfx.platform.console; requires webfx.platform.fetch; requires webfx.platform.util; requires webfx.platform.util.http; From a0ce0fcaf4805b8a7e0788b43c7a8f6031fe8ca0 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Fri, 3 Jan 2025 13:18:52 +0000 Subject: [PATCH 15/28] Added optional includeUnderlyingStore parameter (false by default) in EntityStore getEntity() & getOrCreateEntity() methods (this also fixes Update.updateEntity() returning entity not from UpdateStore) --- .../webfx/stack/orm/entity/EntityStore.java | 40 ++++++++++++++----- .../orm/entity/impl/EntityStoreImpl.java | 4 +- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/EntityStore.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/EntityStore.java index 536cf1896..d94d18a5a 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/EntityStore.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/EntityStore.java @@ -71,27 +71,51 @@ default E createEntity(Object domainClassId, Object primaryKe E createEntity(EntityId id); default E getEntity(Class entityClass, Object primaryKey) { - return getEntity((Object) entityClass, primaryKey); + return getEntity(entityClass, primaryKey, false); } default E getEntity(Object domainClassId, Object primaryKey) { - return primaryKey == null ? null : getEntity(getEntityId(domainClassId, primaryKey)); + return getEntity(domainClassId, primaryKey, false); } - E getEntity(EntityId entityId); + default E getEntity(EntityId entityId) { + return getEntity(entityId, false); + } + + default E getEntity(Class entityClass, Object primaryKey, boolean includeUnderlyingStore) { + return getEntity((Object) entityClass, primaryKey, includeUnderlyingStore); + } + + default E getEntity(Object domainClassId, Object primaryKey, boolean includeUnderlyingStore) { + return primaryKey == null ? null : getEntity(getEntityId(domainClassId, primaryKey), includeUnderlyingStore); + } + + E getEntity(EntityId entityId, boolean includeUnderlyingStore); default E getOrCreateEntity(Class entityClass, Object primaryKey) { - return getOrCreateEntity((Object) entityClass, primaryKey); + return getOrCreateEntity(entityClass, primaryKey, false); } default E getOrCreateEntity(Object domainClassId, Object primaryKey) { - return primaryKey == null ? null : getOrCreateEntity(getEntityId(domainClassId, primaryKey)); + return getOrCreateEntity(domainClassId, primaryKey, false); } default E getOrCreateEntity(EntityId id) { + return getOrCreateEntity(id, false); + } + + default E getOrCreateEntity(Class entityClass, Object primaryKey, boolean includeUnderlyingStore) { + return getOrCreateEntity((Object) entityClass, primaryKey, includeUnderlyingStore); + } + + default E getOrCreateEntity(Object domainClassId, Object primaryKey, boolean includeUnderlyingStore) { + return primaryKey == null ? null : getOrCreateEntity(getEntityId(domainClassId, primaryKey), includeUnderlyingStore); + } + + default E getOrCreateEntity(EntityId id, boolean includeUnderlyingStore) { if (id == null) return null; - E entity = getEntity(id); + E entity = getEntity(id, includeUnderlyingStore); if (entity == null) entity = createEntity(id); return entity; @@ -100,9 +124,7 @@ default E getOrCreateEntity(EntityId id) { default E copyEntity(E entity) { if (entity == null) return null; - E copy = getOrCreateEntity(entity.getId()); - if (copy.getStore() != this) // Ensuring the copy is in this store - copy = createEntity(entity.getId()); + E copy = getOrCreateEntity(entity.getId(), false); // Ensuring the copy is in this store if (copy != entity) ((DynamicEntity) copy).copyAllFieldsFrom(entity); return copy; diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/EntityStoreImpl.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/EntityStoreImpl.java index 3e47c07cc..90b31f8a6 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/EntityStoreImpl.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/EntityStoreImpl.java @@ -64,9 +64,9 @@ public void applyEntityIdRefactor(EntityId oldId, EntityId newId) { // Entity management @Override - public E getEntity(EntityId entityId) { + public E getEntity(EntityId entityId, boolean includeUnderlyingStore) { E entity = (E) entities.get(entityId); - if (entity == null && underlyingStore != null) + if (entity == null && underlyingStore != null && includeUnderlyingStore) entity = underlyingStore.getEntity(entityId); return entity; } From 1c3b5df3d961e7241e2573f6cc22a122df0c7b61 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 6 Jan 2025 10:42:24 +0000 Subject: [PATCH 16/28] Changed the SQL requests to add the bookableScheduledItem Feature --- .../java/dev/webfx/stack/ui/validation/ValidationSupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index bb2f74b8b..5d413a19f 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -291,7 +291,7 @@ public void addEmailNotEqualValidation(TextField emailInput, String forbiddenVal public void addUrlValidation(TextField urlInput, Node where, ObservableStringValue errorMessage) { // Define the URL pattern (basic) - String urlPattern = "^(https?://)(www\\.)?[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/.*)?$"; + String urlPattern = "^(https?://)(www\\.)?[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/[a-zA-Z0-9%._/-]*)?$\n"; Pattern pattern = Pattern.compile(urlPattern); addValidationRule( @@ -330,7 +330,7 @@ public void addMinimumDurationValidation(TextField timeInput, Node where, Observ public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue errorMessage) { // Define the URL pattern (basic) - String urlPattern = "^(https?|srt|rtmp|rtsp)://[\\w.-]+(:\\d+)?(/[\\w./-]*)?(\\?[\\w=&%.-]*)?(#[\\w!:.=&,-]*)?$"; + String urlPattern = "^((https?|srt|rtmp|rtsp)://[\\w.-]+(:\\d+)?(/[\\w.%/-]*)?(\\\\?[\\w=&%.-]*)?(#[\\w!:.=&,-]*)?)?$\n"; Pattern pattern = Pattern.compile(urlPattern); // Create the validation rule From b58cc4ebcbdcbc510deaa7e6169653041486e8e1 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 6 Jan 2025 14:55:35 +0000 Subject: [PATCH 17/28] Corrected the url pattern in ValidationSupport --- .../java/dev/webfx/stack/ui/validation/ValidationSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index 5d413a19f..27268f63b 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -330,7 +330,7 @@ public void addMinimumDurationValidation(TextField timeInput, Node where, Observ public void addUrlOrEmptyValidation(TextField urlInput, ObservableStringValue errorMessage) { // Define the URL pattern (basic) - String urlPattern = "^((https?|srt|rtmp|rtsp)://[\\w.-]+(:\\d+)?(/[\\w.%/-]*)?(\\\\?[\\w=&%.-]*)?(#[\\w!:.=&,-]*)?)?$\n"; + String urlPattern = "^(https|srt|rtmp|rtsp)://(www\\.)?[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/[a-zA-Z0-9%._/-]*)$"; Pattern pattern = Pattern.compile(urlPattern); // Create the validation rule From 9d6f4899c2960ad5e4eae086ba63f22e6e457b7d Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 8 Jan 2025 15:58:34 +0000 Subject: [PATCH 18/28] Added topological sort for deletes on UpdateStore submit --- .../dev/webfx/stack/orm/entity/Entity.java | 12 +++-- .../stack/orm/entity/impl/DynamicEntity.java | 14 ++++-- .../orm/entity/impl/UpdateStoreImpl.java | 2 +- .../orm/entity/result/EntityChanges.java | 3 ++ .../entity/result/EntityChangesBuilder.java | 11 ++++- .../EntityChangesToSubmitBatchGenerator.java | 46 +++++++++++++++++-- .../entity/result/impl/EntityChangesImpl.java | 10 +++- 7 files changed, 80 insertions(+), 18 deletions(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java index 7223cb18d..cc88d35f3 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java @@ -1,15 +1,15 @@ package dev.webfx.stack.orm.entity; +import dev.webfx.platform.async.Future; +import dev.webfx.platform.util.Booleans; +import dev.webfx.platform.util.Numbers; +import dev.webfx.platform.util.Strings; +import dev.webfx.platform.util.time.Times; import dev.webfx.stack.orm.domainmodel.DomainClass; import dev.webfx.stack.orm.domainmodel.DomainField; import dev.webfx.stack.orm.expression.Expression; import dev.webfx.stack.orm.expression.terms.Dot; import dev.webfx.stack.orm.expression.terms.ExpressionArray; -import dev.webfx.platform.util.Booleans; -import dev.webfx.platform.util.time.Times; -import dev.webfx.platform.util.Numbers; -import dev.webfx.platform.util.Strings; -import dev.webfx.platform.async.Future; import java.time.Instant; import java.time.LocalDate; @@ -67,6 +67,8 @@ default boolean isNew() { boolean isFieldLoaded(Object domainFieldId); + Collection getLoadedFields(); + /** * Return the field value as a boolean. If the type is not a boolean, this can result in runtime errors. */ diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java index a8f1266ba..6571b670e 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java @@ -7,9 +7,7 @@ import dev.webfx.stack.orm.entity.EntityStore; import dev.webfx.stack.orm.entity.UpdateStore; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.function.BiConsumer; /** @@ -72,6 +70,14 @@ public boolean isFieldLoaded(Object domainFieldId) { return false; } + @Override + public Collection getLoadedFields() { + Set loadFields = fieldValues.keySet(); + if (underlyingEntity != null) + loadFields.addAll(underlyingEntity.getLoadedFields()); + return loadFields; + } + @Override public void setForeignField(Object foreignFieldId, Object foreignFieldValue) { EntityId foreignEntityId; @@ -173,7 +179,7 @@ private StringBuilder toString(StringBuilder sb, boolean pk) { return sb; } - // methods meant to be used by EntityBindings only + // methods are public but meant to be used by EntityBindings only public Object getFieldProperty(Object fieldId) { return fieldProperties == null ? null : fieldProperties.get(fieldId); diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java index 71639bf64..463b9d33a 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java @@ -24,7 +24,7 @@ */ public final class UpdateStoreImpl extends EntityStoreImpl implements UpdateStore { - private final EntityChangesBuilder changesBuilder = EntityChangesBuilder.create(); + private final EntityChangesBuilder changesBuilder = EntityChangesBuilder.create().setUpdateStore(this); private DataScope submitScope; private Object hasChangesProperty; // managed by EntityBindings diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChanges.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChanges.java index a3db84460..73bcbfcea 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChanges.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChanges.java @@ -1,6 +1,7 @@ package dev.webfx.stack.orm.entity.result; import dev.webfx.stack.orm.entity.EntityId; +import dev.webfx.stack.orm.entity.UpdateStore; import java.util.Collection; @@ -13,4 +14,6 @@ public interface EntityChanges { Collection getDeletedEntityIds(); + UpdateStore getUpdateStore(); + } diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesBuilder.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesBuilder.java index b1e4a672c..b8dca81cd 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesBuilder.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesBuilder.java @@ -4,6 +4,7 @@ import dev.webfx.stack.orm.domainmodel.DomainClass; import dev.webfx.stack.orm.entity.EntityDomainClassIdRegistry; import dev.webfx.stack.orm.entity.EntityId; +import dev.webfx.stack.orm.entity.UpdateStore; import dev.webfx.stack.orm.entity.result.impl.EntityChangesImpl; import java.util.Collection; @@ -18,6 +19,7 @@ public final class EntityChangesBuilder { private Collection deletedEntities; private boolean hasChanges; private Consumer hasChangesPropertyUpdater; // used by EntityBindings only + private UpdateStore updateStore; // Optional, used to sort the deleted entities when provided private EntityChangesBuilder() {} @@ -107,6 +109,11 @@ private EntityChangesBuilder updateHasChanges() { return this; } + public EntityChangesBuilder setUpdateStore(UpdateStore updateStore) { + this.updateStore = updateStore; + return this; + } + private EntityResultBuilder rsb() { if (rsb == null) rsb = EntityResultBuilder.create(); @@ -114,14 +121,14 @@ private EntityResultBuilder rsb() { } public EntityChanges build() { - return new EntityChangesImpl(rsb == null ? null : rsb.build(), deletedEntities); + return new EntityChangesImpl(rsb == null ? null : rsb.build(), deletedEntities, updateStore); } public static EntityChangesBuilder create() { return new EntityChangesBuilder(); } - // method meant to be used by EntityBindings only + // method is public but meant to be used by EntityBindings only public void setHasChangesPropertyUpdater(Consumer hasChangesPropertyUpdater) { this.hasChangesPropertyUpdater = hasChangesPropertyUpdater; diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java index ba2c26e35..702c75cb9 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java @@ -12,8 +12,10 @@ import dev.webfx.stack.orm.dql.sqlcompiler.lci.CompilerDomainModelReader; import dev.webfx.stack.orm.dql.sqlcompiler.sql.SqlCompiled; import dev.webfx.stack.orm.dql.sqlcompiler.sql.dbms.DbmsSqlSyntax; +import dev.webfx.stack.orm.entity.Entity; import dev.webfx.stack.orm.entity.EntityId; import dev.webfx.stack.orm.entity.EntityStore; +import dev.webfx.stack.orm.entity.UpdateStore; import dev.webfx.stack.orm.expression.Expression; import dev.webfx.stack.orm.expression.parser.lci.ParserDomainModelReader; import dev.webfx.stack.orm.expression.terms.*; @@ -272,11 +274,10 @@ private int getGeneratedKeyIndexShift(int batchIndex, List batchIndexTr void generateDeletes() { Collection deletedEntities = changes.getDeletedEntityIds(); if (deletedEntities != null && !deletedEntities.isEmpty()) { - /* Commented delete sort (not working), so for now the application code is responsible for sequencing deletes - List deletedList = new ArrayList<>(deletedEntities); - // Sorting according to classes references - deletedList.sort(comparing(id -> id.getDomainClass().getName())); - */ + UpdateStore updateStore = changes.getUpdateStore(); + if (updateStore != null) { + deletedEntities = new TopologicalSort(deletedEntities, updateStore).sort(); + } deletedEntities.forEach(this::generateDelete); } } @@ -341,4 +342,39 @@ SubmitArgument newSubmitArgument(String language, String statement, Object... pa .build(); } } + + private static class TopologicalSort { + private final Collection entityIds; + private final UpdateStore updateStore; + private final Set visited = new HashSet<>(); + private final List sorted = new ArrayList<>(); + + public TopologicalSort(Collection entityIds, UpdateStore updateStore) { + this.entityIds = entityIds; + this.updateStore = updateStore; + } + + public List sort() { + for (EntityId entityId : entityIds) { + if (!visited.contains(entityId)) { + deepFirstSearch(entityId); + } + } + return sorted; + } + + private void deepFirstSearch(EntityId entityId) { + visited.add(entityId); + Entity entity = updateStore.getEntity(entityId); + if (entity != null) { + for (Object loadedField : entity.getLoadedFields()) { + Object fieldValue = entity.getFieldValue(loadedField); + if (entityIds.contains(fieldValue) && !visited.contains(fieldValue)) { + deepFirstSearch((EntityId) fieldValue); + } + } + } + sorted.add(entityId); + } + } } diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/impl/EntityChangesImpl.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/impl/EntityChangesImpl.java index 58ca34336..95be4042d 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/impl/EntityChangesImpl.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/impl/EntityChangesImpl.java @@ -1,6 +1,7 @@ package dev.webfx.stack.orm.entity.result.impl; import dev.webfx.stack.orm.entity.EntityId; +import dev.webfx.stack.orm.entity.UpdateStore; import dev.webfx.stack.orm.entity.result.EntityChanges; import dev.webfx.stack.orm.entity.result.EntityResult; @@ -13,10 +14,12 @@ public final class EntityChangesImpl implements EntityChanges { private final EntityResult insertedUpdatedEntities; private final Collection deletedEntities; + private final UpdateStore updateStore; // Optional, used to sort delete entities if provided - public EntityChangesImpl(EntityResult insertedUpdatedEntities, Collection deletedEntities) { + public EntityChangesImpl(EntityResult insertedUpdatedEntities, Collection deletedEntities, UpdateStore updateStore) { this.insertedUpdatedEntities = insertedUpdatedEntities; this.deletedEntities = deletedEntities; + this.updateStore = updateStore; } @Override @@ -28,4 +31,9 @@ public EntityResult getInsertedUpdatedEntityResult() { public Collection getDeletedEntityIds() { return deletedEntities; } + + @Override + public UpdateStore getUpdateStore() { + return updateStore; + } } From d5b725e3fbaf12c3dfe91235683921fac8059ce2 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Thu, 9 Jan 2025 10:31:49 +0000 Subject: [PATCH 19/28] Fixed issues with topological sort for deletes on UpdateStore submit --- .../stack/orm/entity/impl/DynamicEntity.java | 4 +++- .../EntityChangesToSubmitBatchGenerator.java | 23 ++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java index 6571b670e..1ecf55bb1 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java @@ -73,8 +73,10 @@ public boolean isFieldLoaded(Object domainFieldId) { @Override public Collection getLoadedFields() { Set loadFields = fieldValues.keySet(); - if (underlyingEntity != null) + if (underlyingEntity != null) { + loadFields = new HashSet<>(loadFields); // because ketSet() returns an immutable set loadFields.addAll(underlyingEntity.getLoadedFields()); + } return loadFields; } diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java index 702c75cb9..ddff64b82 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/result/EntityChangesToSubmitBatchGenerator.java @@ -334,12 +334,12 @@ void addToBatch(String language, String statement, Object... parameters) { SubmitArgument newSubmitArgument(String language, String statement, Object... parameters) { return SubmitArgument.builder() - .setDataSourceId(dataSourceId) - .setDataScope(dataScope) - .setLanguage(language) - .setStatement(statement) - .setParameters(parameters) - .build(); + .setDataSourceId(dataSourceId) + .setDataScope(dataScope) + .setLanguage(language) + .setStatement(statement) + .setParameters(parameters) + .build(); } } @@ -355,21 +355,28 @@ public TopologicalSort(Collection entityIds, UpdateStore updateStore) } public List sort() { + // Doing a deep first search, which will sort the independent entities first, and then the entities referring + // to them. for (EntityId entityId : entityIds) { if (!visited.contains(entityId)) { deepFirstSearch(entityId); } } + // We reverse the previous sort, because if we delete first the independent entities whose other entities + // refer to (meaning that there are foreign keys pointing to them), then the database will raise a constraint + // exception. We need to proceed the deletes in the exact opposite order (deleting first the entities + // referring to other entities). + Collections.reverse(sorted); return sorted; } private void deepFirstSearch(EntityId entityId) { visited.add(entityId); - Entity entity = updateStore.getEntity(entityId); + Entity entity = updateStore.getEntity(entityId, true); if (entity != null) { for (Object loadedField : entity.getLoadedFields()) { Object fieldValue = entity.getFieldValue(loadedField); - if (entityIds.contains(fieldValue) && !visited.contains(fieldValue)) { + if (fieldValue instanceof EntityId && entityIds.contains(fieldValue) && !visited.contains(fieldValue)) { deepFirstSearch((EntityId) fieldValue); } } From 3dc1f2d1a69bf4acc8f7a83aa1be9169b45b3857 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 9 Jan 2025 10:34:54 +0000 Subject: [PATCH 20/28] Fixed obscure issue with EntityBindings.hasChangesProperty() --- .../webfx/stack/orm/entity/binding/EntityBindings.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java b/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java index cfa6ce6a6..d53eb663a 100644 --- a/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java +++ b/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java @@ -26,7 +26,13 @@ public static BooleanExpression hasChangesProperty(UpdateStore updateStore) { BooleanProperty hasChangesProperty = (BooleanProperty) updateStoreImpl.getHasChangesProperty(); if (hasChangesProperty == null) { EntityChangesBuilder changesBuilder = updateStoreImpl.getChangesBuilder(); - hasChangesProperty = new SimpleBooleanProperty(changesBuilder.hasChanges()); + hasChangesProperty = new SimpleBooleanProperty(changesBuilder.hasChanges()) { + @Override + protected void invalidated() { + get(); // For some reason, it's necessary to call get() here, otherwise the bindings depending on + // this property might not be called 🤷 + } + }; changesBuilder.setHasChangesPropertyUpdater(hasChangesProperty::set); } return hasChangesProperty; From c0ecceacfd9402cc641cbdd0472223f33c99057a Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Thu, 9 Jan 2025 15:40:25 +0000 Subject: [PATCH 21/28] Fixed i18n default implementation: bracket interpretation was calling toString() even when not necessary and value was not a String (ex: json) --- .../webfx/stack/i18n/spi/impl/I18nProviderImpl.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java index cf28245d9..f348b5f6f 100644 --- a/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java +++ b/webfx-stack-i18n/src/main/java/dev/webfx/stack/i18n/spi/impl/I18nProviderImpl.java @@ -235,8 +235,12 @@ public & TokenKey> Object interpretBracketsAndDefaultInToken // the original language (ex: FR). Object resolvedValue = getDictionaryTokenValueImpl(new I18nSubKey(sToken.substring(i1 + 1, i2), i18nKey), tokenKey, originalDictionary, false, originalDictionary, false, skipMessageLoading); // If the bracket token has been resolved, we return it with the parts before and after the brackets - if (resolvedValue != null) - tokenValue = (i1 == 0 ? "" : sToken.substring(0, i1)) + resolvedValue + sToken.substring(i2 + 1); + if (resolvedValue != null) { + if (i1 == 0 && i2 == sToken.length() - 1) // except if there are no parts before and after the brackets + tokenValue = resolvedValue; // in which case we return the resolved object as is (possibly not a String) + else + tokenValue = (i1 == 0 ? "" : sToken.substring(0, i1)) + resolvedValue + sToken.substring(i2 + 1); + } } } } @@ -338,7 +342,7 @@ public void scheduleMessageLoading(Object i18nKey, boolean inDefaultLanguage) { // (presumably in the same animation frame) we do the actual load of these keys. dictionaryLoadingScheduled = UiScheduler.scheduleDeferred(() -> { // Making a copy of the keys before clearing it for the next possible schedule - Set loadingKeys = new HashSet<>(keysToLoad); + Set loadingKeys = new HashSet<>(keysToLoad); // ConcurrentModificationException observed keysToLoad.clear(); // Extracting the message keys to load from them (in case they are different) Set messageKeysToLoad = loadingKeys.stream() From 08570ccdb982fc3b99bac8fe3c6bebee4341f54a Mon Sep 17 00:00:00 2001 From: David Date: Sat, 11 Jan 2025 10:35:48 +0000 Subject: [PATCH 22/28] Added a null test in applyCommittedChangesToUnderlyingStore of UpdateStoreImpl to prevent a null pointer exception --- .../stack/orm/entity/impl/UpdateStoreImpl.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java index 463b9d33a..25bc49632 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java @@ -134,17 +134,19 @@ private void applyCommittedChangesToUnderlyingStore() { if (underlyingStore != null) { EntityChanges changes = changesBuilder.build(); EntityResult insertedUpdatedEntityResult = changes.getInsertedUpdatedEntityResult(); - for (EntityId entityId : insertedUpdatedEntityResult.getEntityIds()) { - Entity underlyingEntity = underlyingStore.getEntity(entityId); - if (underlyingEntity != null) { - for (Object fieldId : insertedUpdatedEntityResult.getFieldIds(entityId)) { - if (fieldId != null) { - Object fieldValue = insertedUpdatedEntityResult.getFieldValue(entityId, fieldId); - underlyingEntity.setFieldValue(fieldId, fieldValue); + if (insertedUpdatedEntityResult != null) { + for (EntityId entityId : insertedUpdatedEntityResult.getEntityIds()) { + Entity underlyingEntity = underlyingStore.getEntity(entityId); + if (underlyingEntity != null) { + for (Object fieldId : insertedUpdatedEntityResult.getFieldIds(entityId)) { + if (fieldId != null) { + Object fieldValue = insertedUpdatedEntityResult.getFieldValue(entityId, fieldId); + underlyingEntity.setFieldValue(fieldId, fieldValue); + } } } + clearAllUpdatedValuesFromUpdatedEntity(entityId); } - clearAllUpdatedValuesFromUpdatedEntity(entityId); } } } From 5a8f0b9740e4b01c807a0ddf559e68c1b73cd35f Mon Sep 17 00:00:00 2001 From: David Date: Sat, 11 Jan 2025 10:36:06 +0000 Subject: [PATCH 23/28] Simplify the urlValidation in ValidationSupport --- .../java/dev/webfx/stack/ui/validation/ValidationSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java index 27268f63b..1c733d396 100644 --- a/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java +++ b/webfx-stack-ui-validation/src/main/java/dev/webfx/stack/ui/validation/ValidationSupport.java @@ -291,7 +291,7 @@ public void addEmailNotEqualValidation(TextField emailInput, String forbiddenVal public void addUrlValidation(TextField urlInput, Node where, ObservableStringValue errorMessage) { // Define the URL pattern (basic) - String urlPattern = "^(https?://)(www\\.)?[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}(/[a-zA-Z0-9%._/-]*)?$\n"; + String urlPattern = "^(https?://).+\\..+$"; Pattern pattern = Pattern.compile(urlPattern); addValidationRule( From 295c4fa376ae7b987e5fad5b0b466833faceaae6 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Sat, 11 Jan 2025 12:10:04 +0000 Subject: [PATCH 24/28] Added few utility methods in EntityBindings --- webfx-stack-orm-entity-binding/pom.xml | 6 ++++++ .../stack/orm/entity/binding/EntityBindings.java | 14 +++++++++++++- .../src/main/java/module-info.java | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/webfx-stack-orm-entity-binding/pom.xml b/webfx-stack-orm-entity-binding/pom.xml index 351ea3a20..44a312908 100644 --- a/webfx-stack-orm-entity-binding/pom.xml +++ b/webfx-stack-orm-entity-binding/pom.xml @@ -21,6 +21,12 @@ provided + + org.openjfx + javafx-graphics + provided + + dev.webfx webfx-platform-javatime-emul-j2cl diff --git a/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java b/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java index d53eb663a..2667c47a0 100644 --- a/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java +++ b/webfx-stack-orm-entity-binding/src/main/java/dev/webfx/stack/orm/entity/binding/EntityBindings.java @@ -11,6 +11,7 @@ import dev.webfx.stack.orm.entity.result.EntityResult; import javafx.beans.binding.BooleanExpression; import javafx.beans.property.*; +import javafx.scene.Node; import java.util.ArrayList; import java.util.List; @@ -30,7 +31,7 @@ public static BooleanExpression hasChangesProperty(UpdateStore updateStore) { @Override protected void invalidated() { get(); // For some reason, it's necessary to call get() here, otherwise the bindings depending on - // this property might not be called 🤷 + // this property might not be updated 🤷 } }; changesBuilder.setHasChangesPropertyUpdater(hasChangesProperty::set); @@ -38,6 +39,17 @@ protected void invalidated() { return hasChangesProperty; } + public static BooleanExpression hasNoChangesProperty(UpdateStore updateStore) { + return hasChangesProperty(updateStore).not(); + } + + public static void disableNodesWhenUpdateStoreHasNoChanges(UpdateStore updateStore, Node... nodes) { + BooleanExpression hasNoChanges = hasNoChangesProperty(updateStore); + for (Node node : nodes) + node.disableProperty().bind(hasNoChanges); + + } + public static BooleanProperty getBooleanFieldProperty(Entity entity, String fieldId) { return (BooleanProperty) getFieldProperty(entity, fieldId, SimpleBooleanProperty::new); } diff --git a/webfx-stack-orm-entity-binding/src/main/java/module-info.java b/webfx-stack-orm-entity-binding/src/main/java/module-info.java index a93a6f9c3..fd55c82b3 100644 --- a/webfx-stack-orm-entity-binding/src/main/java/module-info.java +++ b/webfx-stack-orm-entity-binding/src/main/java/module-info.java @@ -4,6 +4,7 @@ // Direct dependencies modules requires javafx.base; + requires javafx.graphics; requires webfx.platform.util; requires webfx.stack.orm.entity; From cb3a776517b61c45de1637bda49f104da0e44985 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 13 Jan 2025 12:09:58 +0000 Subject: [PATCH 25/28] Fixed issues with entities implementation before going to prod --- .../java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java index 1ecf55bb1..417726f73 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java @@ -152,7 +152,7 @@ public boolean equals(Object o) { return false; // if (!fieldValues.equals(that.fieldValues)) // return false; - return Objects.equals(underlyingEntity, that.underlyingEntity); + return underlyingEntity == null || that.underlyingEntity == null || Objects.equals(underlyingEntity, that.underlyingEntity); } @Override From 0c0f06794774c8c61eb51fe47f96cd48f5af66a6 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Tue, 14 Jan 2025 21:38:42 +0000 Subject: [PATCH 26/28] Introduced setLoadedFieldValue() in Entity API --- .../java/dev/webfx/stack/orm/entity/Entity.java | 4 +++- .../webfx/stack/orm/entity/impl/DynamicEntity.java | 14 ++++++++++++-- .../QueryResultToEntitiesMapper.java | 2 +- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java index cc88d35f3..382bc6db8 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java @@ -119,7 +119,9 @@ default boolean isNew() { * @param domainFieldId the domain field unique id in the domain model * @param value the value to store in this entity field */ - void setFieldValue(Object domainFieldId, Object value); + void setFieldValue(Object domainFieldId, Object value); // considers this value change for submit in UpdateStore + + void setLoadedFieldValue(Object domainFieldId, Object value); // ignores this value change for submit in UpdateStore void setForeignField(Object foreignFieldId, Object foreignFieldValue); diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java index 417726f73..94707f7fd 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java @@ -117,10 +117,19 @@ public E getForeignEntity(Object foreignFieldId) { return foreignEntity; } + @Override + public void setLoadedFieldValue(Object domainFieldId, Object value) { + setFieldValue(domainFieldId, value, true); + } + @Override public void setFieldValue(Object domainFieldId, Object value) { - fieldValues.put(domainFieldId, value); - if (store instanceof UpdateStore) { + setFieldValue(domainFieldId, value, false); + } + + private void setFieldValue(Object domainFieldId, Object value, boolean loaded) { + fieldValues.put(domainFieldId, value); // TODO: what if it's a loaded value and previous value was not? + if (!loaded && store instanceof UpdateStore) { Object underlyingValue = underlyingEntity != null ? underlyingEntity.getFieldValue(domainFieldId) : null; boolean isUnderlyingValueLoaded = underlyingValue != null || underlyingEntity != null && underlyingEntity.isFieldLoaded(domainFieldId); ((UpdateStoreImpl) store).onInsertedOrUpdatedEntityFieldChange(id, domainFieldId, value, underlyingValue, isUnderlyingValueLoaded); @@ -132,6 +141,7 @@ public void setFieldValue(Object domainFieldId, Object value) { } } + public void copyAllFieldsFrom(Entity entity) { DynamicEntity dynamicEntity = (DynamicEntity) entity; fieldValues.putAll(dynamicEntity.fieldValues); diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java index 5af68def5..5e58434e9 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java @@ -68,7 +68,7 @@ public static EntityList mapQueryResultToEntities(QueryRes } } } - targetEntity.setFieldValue(fieldId, value); + targetEntity.setLoadedFieldValue(fieldId, value); //System.out.println(targetEntity.getId().toString() + '.' + columnMapping.getDomainFieldId() + " = " + value); } // And finally adding this entity to the list From 010b087f80cffaeec21938655aaaa156fa20ceec Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 15 Jan 2025 09:30:34 +0000 Subject: [PATCH 27/28] Added warning in UpdateStoreImpl when making changes during submit --- .../stack/orm/entity/impl/UpdateStoreImpl.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java index 25bc49632..2979d2260 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/UpdateStoreImpl.java @@ -27,6 +27,7 @@ public final class UpdateStoreImpl extends EntityStoreImpl implements UpdateStor private final EntityChangesBuilder changesBuilder = EntityChangesBuilder.create().setUpdateStore(this); private DataScope submitScope; private Object hasChangesProperty; // managed by EntityBindings + private boolean submitting; public UpdateStoreImpl(DataSourceModel dataSourceModel) { super(dataSourceModel); @@ -45,6 +46,7 @@ public EntityChanges getEntityChanges() { public E insertEntity(EntityId entityId) { if (!entityId.isNew()) throw new IllegalArgumentException("entityId must be new"); + logWarningIfChangesDuringSubmit(); E entity = createEntity(entityId); changesBuilder.addInsertedEntityId(entityId); return entity; @@ -52,11 +54,13 @@ public E insertEntity(EntityId entityId) { @Override public E updateEntity(EntityId entityId) { + logWarningIfChangesDuringSubmit(); changesBuilder.addUpdatedEntityId(entityId); return getOrCreateEntity(entityId); } void onInsertedOrUpdatedEntityFieldChange(EntityId id, Object domainFieldId, Object value, Object underlyingValue, boolean isUnderlyingValueLoaded) { + logWarningIfChangesDuringSubmit(); // If the user enters back the original value, we completely clear that field from the changes if (isUnderlyingValueLoaded && Numbers.identicalObjectsOrNumberValues(value, underlyingValue)) { changesBuilder.removeFieldChange(id, domainFieldId); @@ -77,19 +81,23 @@ public Future> submitChanges(SubmitArgument... initialSubmit createSubmitBatchGenerator(getEntityChanges(), getDataSourceModel(), submitScope, initialSubmits); Batch argBatch = updateBatchGenerator.generate(); Console.log("Executing submit batch " + Arrays.toStringWithLineFeeds(argBatch.getArray())); + submitting = true; return SubmitService.executeSubmitBatch(argBatch).compose(resBatch -> { // TODO: perf optimization: make these steps optional if not required by application code markChangesAsCommitted(); updateBatchGenerator.applyGeneratedKeys(resBatch, this); + submitting = false; return Future.succeededFuture(resBatch); }); } catch (Exception e) { + submitting = false; return Future.failedFuture(e); } } @Override public void deleteEntity(EntityId entityId) { + logWarningIfChangesDuringSubmit(); changesBuilder.addDeletedEntityId(entityId); } @@ -151,6 +159,11 @@ private void applyCommittedChangesToUnderlyingStore() { } } + private void logWarningIfChangesDuringSubmit() { + if (submitting) + Console.log("[UpdateStore][WARNING] ⚠️ Making changes during submitChanges() is not yet supported, and leads to inconsistent UpdateStore state.", new Exception("Please use this exception stacktrace to identify the faulty call")); + } + // methods meant to be used by EntityBindings only From 8f4120ab8e7c8aa32915ed9af08b1ebe72faa907 Mon Sep 17 00:00:00 2001 From: Bruno Salmon Date: Wed, 15 Jan 2025 15:20:56 +0000 Subject: [PATCH 28/28] Removed Entity.setLoadedFieldValue() (using setFieldValue() with ThreadLocalEntityLoadingContext instead) --- .../dev/webfx/stack/orm/entity/Entity.java | 4 +- .../stack/orm/entity/impl/DynamicEntity.java | 13 +-- .../impl/ThreadLocalEntityLoadingContext.java | 36 +++++++ .../QueryResultToEntitiesMapper.java | 96 ++++++++++--------- 4 files changed, 89 insertions(+), 60 deletions(-) create mode 100644 webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/ThreadLocalEntityLoadingContext.java diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java index 382bc6db8..cc88d35f3 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/Entity.java @@ -119,9 +119,7 @@ default boolean isNew() { * @param domainFieldId the domain field unique id in the domain model * @param value the value to store in this entity field */ - void setFieldValue(Object domainFieldId, Object value); // considers this value change for submit in UpdateStore - - void setLoadedFieldValue(Object domainFieldId, Object value); // ignores this value change for submit in UpdateStore + void setFieldValue(Object domainFieldId, Object value); void setForeignField(Object foreignFieldId, Object foreignFieldValue); diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java index 94707f7fd..99090a58f 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/DynamicEntity.java @@ -117,19 +117,10 @@ public E getForeignEntity(Object foreignFieldId) { return foreignEntity; } - @Override - public void setLoadedFieldValue(Object domainFieldId, Object value) { - setFieldValue(domainFieldId, value, true); - } - - @Override public void setFieldValue(Object domainFieldId, Object value) { - setFieldValue(domainFieldId, value, false); - } - - private void setFieldValue(Object domainFieldId, Object value, boolean loaded) { + boolean loadedValue = ThreadLocalEntityLoadingContext.isThreadLocalEntityLoading(); fieldValues.put(domainFieldId, value); // TODO: what if it's a loaded value and previous value was not? - if (!loaded && store instanceof UpdateStore) { + if (!loadedValue && store instanceof UpdateStore) { Object underlyingValue = underlyingEntity != null ? underlyingEntity.getFieldValue(domainFieldId) : null; boolean isUnderlyingValueLoaded = underlyingValue != null || underlyingEntity != null && underlyingEntity.isFieldLoaded(domainFieldId); ((UpdateStoreImpl) store).onInsertedOrUpdatedEntityFieldChange(id, domainFieldId, value, underlyingValue, isUnderlyingValueLoaded); diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/ThreadLocalEntityLoadingContext.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/ThreadLocalEntityLoadingContext.java new file mode 100644 index 000000000..14ff25373 --- /dev/null +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/impl/ThreadLocalEntityLoadingContext.java @@ -0,0 +1,36 @@ +package dev.webfx.stack.orm.entity.impl; + +/** + * Usage: + * + * try (var context = ThreadLocalEntityLoadingContext.open(true)) { + * ... + * any call to DynamicEntity.setFieldValue() won't record this as modification in an UpdateStore + * ... + * } + * + * @author Bruno Salmon + */ +public final class ThreadLocalEntityLoadingContext implements AutoCloseable { + + private static final ThreadLocal entityLoadingThreadLocal = new ThreadLocal<>(); + + private final Boolean previousEntityLoading = entityLoadingThreadLocal.get(); + + private ThreadLocalEntityLoadingContext(Boolean entityLoading) { + entityLoadingThreadLocal.set(entityLoading); + } + + @Override + public void close() { + entityLoadingThreadLocal.set(previousEntityLoading); + } + + public static ThreadLocalEntityLoadingContext open(Boolean entityLoading) { + return entityLoading == null ? null : new ThreadLocalEntityLoadingContext(entityLoading); + } + + public static boolean isThreadLocalEntityLoading() { + return Boolean.TRUE.equals(entityLoadingThreadLocal.get()); + } +} diff --git a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java index 5e58434e9..00c783ac2 100644 --- a/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java +++ b/webfx-stack-orm-entity/src/main/java/dev/webfx/stack/orm/entity/query_result_to_entities/QueryResultToEntitiesMapper.java @@ -9,6 +9,7 @@ import dev.webfx.stack.orm.entity.Entity; import dev.webfx.stack.orm.entity.EntityList; import dev.webfx.stack.orm.entity.EntityStore; +import dev.webfx.stack.orm.entity.impl.ThreadLocalEntityLoadingContext; import java.time.LocalDate; import java.time.LocalDateTime; @@ -23,56 +24,59 @@ public static EntityList mapQueryResultToEntities(QueryRes EntityList entities = store.getOrCreateEntityList(listId); entities.clear(); // Now iterating along the query result to create one entity per record - if (rs != null) - for (int rowIndex = 0, rowCount = rs.getRowCount(); rowIndex < rowCount; rowIndex++) { - // Retrieving the primary key of this record - Object primaryKey = rs.getValue(rowIndex, rowMapping.getPrimaryKeyColumnIndex()); - // Creating the entity (empty for now) - E entity = store.getOrCreateEntity(rowMapping.getDomainClassId(), primaryKey); - // Now populating the entity values by iterating along the other column indexes (though column mappings) - for (QueryColumnToEntityFieldMapping columnMapping : rowMapping.getColumnMappings()) { - // The target entity (to affect the column value to) is normally the current entity - Entity targetEntity = entity; - // However if this column index is associated with a join, it actually refers to a foreign entity, so let's check this - QueryColumnToEntityFieldMapping joinMapping = columnMapping.getForeignIdColumnMapping(); - if (joinMapping != null) { // Yes it is a join - // So let's first get the row id of the database foreign record - Object foreignKey = rs.getValue(rowIndex, joinMapping.getColumnIndex()); - // If it is null, there is nothing to do (finally no target entity) - if (foreignKey == null) - continue; - // And creating the foreign entity (or getting the same instance if already created) - targetEntity = store.getOrCreateEntity(joinMapping.getForeignClassId(), foreignKey); // And finally using is as the target entity - } - // Now that we have the target entity, getting the value for the column index - Object value = rs.getValue(rowIndex, columnMapping.getColumnIndex()); - // If this is a foreign key (when foreignClassId is filled), we transform the value into a link to the foreign entity - if (value != null && columnMapping.getForeignClassId() != null) - value = store.getOrCreateEntity(columnMapping.getForeignClassId(), value).getId(); - // Now everything is ready to set the field on the target entity - Object fieldId = columnMapping.getDomainFieldId(); - // Some conversion to do if it is a domain field - if (fieldId instanceof DomainField) { - DomainField domainField = (DomainField) fieldId; - // First, getting the field id - fieldId = domainField.getId(); - // And second, converting the dates possibly returned as String by the QueryService into LocalDate or LocalDateTime objects - if (value != null && domainField.getType() == PrimType.DATE && value instanceof String) { - LocalDateTime localDateTime = Times.toLocalDateTime((String) value); - if (localDateTime != null) - value = localDateTime; - else { - LocalDate localDate = Times.toLocalDate((String) value); - if (localDate != null) - value = localDate; + if (rs != null) { + try (var context = ThreadLocalEntityLoadingContext.open(true)) { + for (int rowIndex = 0, rowCount = rs.getRowCount(); rowIndex < rowCount; rowIndex++) { + // Retrieving the primary key of this record + Object primaryKey = rs.getValue(rowIndex, rowMapping.getPrimaryKeyColumnIndex()); + // Creating the entity (empty for now) + E entity = store.getOrCreateEntity(rowMapping.getDomainClassId(), primaryKey); + // Now populating the entity values by iterating along the other column indexes (though column mappings) + for (QueryColumnToEntityFieldMapping columnMapping : rowMapping.getColumnMappings()) { + // The target entity (to affect the column value to) is normally the current entity + Entity targetEntity = entity; + // However if this column index is associated with a join, it actually refers to a foreign entity, so let's check this + QueryColumnToEntityFieldMapping joinMapping = columnMapping.getForeignIdColumnMapping(); + if (joinMapping != null) { // Yes it is a join + // So let's first get the row id of the database foreign record + Object foreignKey = rs.getValue(rowIndex, joinMapping.getColumnIndex()); + // If it is null, there is nothing to do (finally no target entity) + if (foreignKey == null) + continue; + // And creating the foreign entity (or getting the same instance if already created) + targetEntity = store.getOrCreateEntity(joinMapping.getForeignClassId(), foreignKey); // And finally using is as the target entity + } + // Now that we have the target entity, getting the value for the column index + Object value = rs.getValue(rowIndex, columnMapping.getColumnIndex()); + // If this is a foreign key (when foreignClassId is filled), we transform the value into a link to the foreign entity + if (value != null && columnMapping.getForeignClassId() != null) + value = store.getOrCreateEntity(columnMapping.getForeignClassId(), value).getId(); + // Now everything is ready to set the field on the target entity + Object fieldId = columnMapping.getDomainFieldId(); + // Some conversion to do if it is a domain field + if (fieldId instanceof DomainField) { + DomainField domainField = (DomainField) fieldId; + // First, getting the field id + fieldId = domainField.getId(); + // And second, converting the dates possibly returned as String by the QueryService into LocalDate or LocalDateTime objects + if (value != null && domainField.getType() == PrimType.DATE && value instanceof String) { + LocalDateTime localDateTime = Times.toLocalDateTime((String) value); + if (localDateTime != null) + value = localDateTime; + else { + LocalDate localDate = Times.toLocalDate((String) value); + if (localDate != null) + value = localDate; + } + } } + targetEntity.setFieldValue(fieldId, value); + //System.out.println(targetEntity.getId().toString() + '.' + columnMapping.getDomainFieldId() + " = " + value); } + // And finally adding this entity to the list + entities.add(entity); } - targetEntity.setLoadedFieldValue(fieldId, value); - //System.out.println(targetEntity.getId().toString() + '.' + columnMapping.getDomainFieldId() + " = " + value); } - // And finally adding this entity to the list - entities.add(entity); } //Logger.log("Ok : " + entities); return entities;