From 1dd112a44e421ae0a5161632e128d86d9e6dd5c7 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Fri, 30 Jan 2026 09:52:23 +0100 Subject: [PATCH 01/13] ESB-827: deferred versioning operations --- pom.xml | 17 +++- .../versioning/IFDeferredVersioning.java | 29 +++++++ .../versioning/IVersioningManager.java | 8 +- .../versioning/VersioningManager.java | 84 ++++++++++++++----- .../aps/versioningManagersConfig.xml | 12 ++- .../versioning/TestVersionAction.java | 18 ++-- ...ntVersioningControllerIntegrationTest.java | 6 +- .../web/content/IFDeferredVersioningTest.java | 66 +++++++++++++++ 8 files changed, 204 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java create mode 100644 src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java diff --git a/pom.xml b/pom.xml index a37da17..0490719 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ entando-plugin-jpversioning org.entando.entando.plugins war - 6.5.1 + 6.5.2 Entando Plugin: Content Versioning Manages the versioning of portal content, storing the history of performed operations @@ -99,7 +99,7 @@ org.entando entando-core-bom - 6.5.0-ENG-4806 + 6.5.1 pom import @@ -111,6 +111,7 @@ org.entando.entando.plugins entando-plugin-jacms classes + 6.5.4 org.entando.entando @@ -146,6 +147,7 @@ org.entando.entando.plugins entando-plugin-jacms test-jar + 6.5.4 org.springframework @@ -163,6 +165,16 @@ org.mockito mockito-junit-jupiter test + + + org.mockito + mockito-core + + + + + org.mockito + mockito-inline org.assertj @@ -201,6 +213,5 @@ 2.14.1 jar - diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java new file mode 100644 index 0000000..be70c35 --- /dev/null +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java @@ -0,0 +1,29 @@ +package com.agiletec.plugins.jpversioning.aps.system.services.versioning; + +import com.agiletec.aps.system.ApsSystemUtils.ApsDeepDebug; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Supplier; +import org.entando.entando.aps.system.services.IFeatureFlag; + +public interface IFDeferredVersioning extends IFeatureFlag { + boolean DEFERRED_VERSIONING_ENABLED = checkEnabled(); + + default boolean isEnabled() { + return DEFERRED_VERSIONING_ENABLED; + } + + static boolean checkEnabled() { + return IFeatureFlag.readEnablementStatus("DEFERRED_VERSIONING"); + } + + static void possiblyDeferred(Executor executor, Supplier action, String method) { + if (checkEnabled()) { + ApsDeepDebug.print("deferred-versioning", "running versioning task in a separate thread: " + method); + CompletableFuture.runAsync(action::get, executor); + } else { + action.get(); + } + } + +} diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java index 0506ca7..a14b295 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java @@ -24,6 +24,8 @@ import com.agiletec.aps.system.common.entity.IEntityManager; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; import org.entando.entando.ent.exception.EntException; import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; @@ -47,8 +49,8 @@ public interface IVersioningManager { public ContentVersion getVersion(long id) throws EntException; public ContentVersion getLastVersion(String contentId) throws EntException; - - public void saveContentVersion(String contentId) throws EntException; + + public void saveContentVersion(String contentId) throws EntException; public void deleteVersion(long versionid) throws EntException; @@ -56,4 +58,4 @@ public interface IVersioningManager { public Content getContent(ContentVersion contentVersion) throws EntException; -} \ No newline at end of file +} diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index 32c2980..129c440 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -21,19 +21,6 @@ */ package com.agiletec.plugins.jpversioning.aps.system.services.versioning; -import java.io.StringReader; -import java.util.List; - -import javax.xml.parsers.SAXParser; - -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.entando.entando.ent.exception.EntException; -import org.entando.entando.ent.util.EntLogging.EntLogger; -import org.entando.entando.ent.util.EntLogging.EntLogFactory; -import org.entando.entando.ent.util.EntSafeXmlUtils; -import org.xml.sax.InputSource; - import com.agiletec.aps.system.common.AbstractService; import com.agiletec.aps.system.common.entity.parse.EntityHandler; import com.agiletec.aps.system.services.baseconfig.ConfigInterface; @@ -43,8 +30,20 @@ import com.agiletec.plugins.jacms.aps.system.services.content.model.ContentRecordVO; import com.agiletec.plugins.jpversioning.aps.system.JpversioningSystemConstants; import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.util.EntLogging.EntLogFactory; +import org.entando.entando.ent.util.EntLogging.EntLogger; +import org.entando.entando.ent.util.EntSafeXmlUtils; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** @@ -55,6 +54,7 @@ public class VersioningManager extends AbstractService implements IVersioningMan private static final EntLogger _logger = EntLogFactory.getSanitizedLogger(VersioningManager.class); + @Override public void init() throws Exception { String deleteMidVersions = this.getConfigManager().getParam(JpversioningSystemConstants.CONFIG_PARAM_DELETE_MID_VERSIONS); @@ -62,13 +62,23 @@ public void init() throws Exception { _logger.debug("{} ready", this.getClass().getName()); } + @Before("execution(* com.agiletec.plugins.jacms.aps.system.services.content.IContentManager.saveContent(..)) && args(content)") public void onSaveContent(Content content) { try { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onSaveContent", ex); + } + return null; + }, "onSaveContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onSaveContent", e); } @@ -80,7 +90,16 @@ public void onInsertOnLineContent(Content content) { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onInsertOnLineContent", ex); + } + return null; + }, "onInsertOnLineContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onInsertOnLineContent", e); } @@ -92,7 +111,16 @@ public void onRemoveOnLineContent(Content content) { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onRemoveOnLineContent", ex); + } + return null; + }, "onRemoveOnLineContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onRemoveOnLineContent", e); } @@ -104,7 +132,16 @@ public void onDeleteContent(Content content) { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onDeleteContent", ex); + } + return null; + }, "onDeleteContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onDeleteContent", e); } @@ -217,7 +254,7 @@ public Content getContent(ContentVersion contentVersion) throws EntException { /** * Crea un'entità specifica valorizzata in base alla sua definizione in xml - * ed al tipo. + * e al tipo. * * @param entityTypeCode Il codice del tipo di entità. * @param xml L'xml dell'entità specifica. @@ -277,7 +314,7 @@ protected String getXmlAttributeRootElementName() { /** * Setta il nome dell'attributo della root dell'xml rappresentante la - * singola entità. Il metodo è ad uso della definizione del servizio + * singola entità. Il metodo è a uso della definizione del servizio * nell'xml di configurazione di spring. Di default, la definizione del * servizio astratto nella configurazione di spring presenta una un nome * base "entity"; questa definizione và sostituita nella definizione del @@ -352,6 +389,14 @@ public void setConfigManager(ConfigInterface configManager) { this._configManager = configManager; } + public Executor getExecutor() { + return _executor; + } + + public void setExecutor(Executor executor) { + this._executor = executor; + } + private boolean _deleteMidVersions; private EntityHandler _entityHandler; @@ -363,5 +408,6 @@ public void setConfigManager(ConfigInterface configManager) { private IContentManager _contentManager; private ICategoryManager _categoryManager; private ConfigInterface _configManager; + private Executor _executor; } diff --git a/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml b/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml index 1431987..a9c9012 100644 --- a/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml +++ b/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml @@ -9,7 +9,16 @@ http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> - + + + + + + + + + + @@ -24,6 +33,7 @@ + actionMock = mock(Supplier.class); + String methodName = "saveContent"; + + try (MockedStatic mock = mockStatic(IFDeferredVersioning.class)) { + mock.when(IFDeferredVersioning::checkEnabled).thenReturn(true); + mock.when(() -> IFDeferredVersioning.possiblyDeferred( + any(Executor.class), + any(Supplier.class), + anyString() + )).thenCallRealMethod(); + + IFDeferredVersioning.possiblyDeferred(executorMock, actionMock, methodName); + // the executor gets called!!! + verify(executorMock, times(1)).execute(any(Runnable.class)); + } + } + + @Test + void possiblyDeferred_disabled() { + Executor executorMock = mock(Executor.class); + Supplier actionMock = mock(Supplier.class); + String methodName = "saveContent"; + + try (MockedStatic mocked = mockStatic(IFDeferredVersioning.class)) { + // Feature flag disabled + mocked.when(IFDeferredVersioning::checkEnabled).thenReturn(false); + + mocked.when(() -> IFDeferredVersioning.possiblyDeferred( + any(Executor.class), + any(Supplier.class), + anyString()) + ).thenCallRealMethod(); + + IFDeferredVersioning.possiblyDeferred(executorMock, actionMock, methodName); + + verify(actionMock, times(1)).get(); + // Executor never gets called!!! + verify(executorMock, never()).execute(any()); + } + } + +} From 2454591c4ecc6d0cef8625ad7561f7df904d9503 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Fri, 30 Jan 2026 11:14:03 +0100 Subject: [PATCH 02/13] ESB-827: added deferred versioning operations --- pom.xml | 16 +++- .../versioning/IFDeferredVersioning.java | 29 +++++++ .../versioning/IVersioningManager.java | 4 +- .../versioning/VersioningManager.java | 82 ++++++++++++++----- .../aps/versioningManagersConfig.xml | 10 +++ .../versioning/TestVersionAction.java | 18 ++-- ...ntVersioningControllerIntegrationTest.java | 4 +- .../web/content/IFDeferredVersioningTest.java | 66 +++++++++++++++ 8 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java create mode 100644 src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java diff --git a/pom.xml b/pom.xml index 0be6722..3335acc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,12 @@ org.entando entando-core-parent - 6.5.0-ENG-5128-PR-156 + 6.5.0 entando-plugin-jpversioning org.entando.entando.plugins war - 6.5.1 + 6.5.2 Entando Plugin: Content Versioning Manages the versioning of portal content, storing the history of performed operations @@ -99,7 +99,7 @@ org.entando entando-core-bom - 6.5.0-ENG-5128 + 6.5.1 pom import @@ -163,6 +163,16 @@ org.mockito mockito-junit-jupiter test + + + org.mockito + mockito-core + + + + + org.mockito + mockito-inline org.assertj diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java new file mode 100644 index 0000000..be70c35 --- /dev/null +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java @@ -0,0 +1,29 @@ +package com.agiletec.plugins.jpversioning.aps.system.services.versioning; + +import com.agiletec.aps.system.ApsSystemUtils.ApsDeepDebug; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.function.Supplier; +import org.entando.entando.aps.system.services.IFeatureFlag; + +public interface IFDeferredVersioning extends IFeatureFlag { + boolean DEFERRED_VERSIONING_ENABLED = checkEnabled(); + + default boolean isEnabled() { + return DEFERRED_VERSIONING_ENABLED; + } + + static boolean checkEnabled() { + return IFeatureFlag.readEnablementStatus("DEFERRED_VERSIONING"); + } + + static void possiblyDeferred(Executor executor, Supplier action, String method) { + if (checkEnabled()) { + ApsDeepDebug.print("deferred-versioning", "running versioning task in a separate thread: " + method); + CompletableFuture.runAsync(action::get, executor); + } else { + action.get(); + } + } + +} diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java index 0506ca7..6eea2e7 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IVersioningManager.java @@ -24,6 +24,8 @@ import com.agiletec.aps.system.common.entity.IEntityManager; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; import org.entando.entando.ent.exception.EntException; import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; @@ -56,4 +58,4 @@ public interface IVersioningManager { public Content getContent(ContentVersion contentVersion) throws EntException; -} \ No newline at end of file +} diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index 32c2980..42e7215 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -21,19 +21,6 @@ */ package com.agiletec.plugins.jpversioning.aps.system.services.versioning; -import java.io.StringReader; -import java.util.List; - -import javax.xml.parsers.SAXParser; - -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Before; -import org.entando.entando.ent.exception.EntException; -import org.entando.entando.ent.util.EntLogging.EntLogger; -import org.entando.entando.ent.util.EntLogging.EntLogFactory; -import org.entando.entando.ent.util.EntSafeXmlUtils; -import org.xml.sax.InputSource; - import com.agiletec.aps.system.common.AbstractService; import com.agiletec.aps.system.common.entity.parse.EntityHandler; import com.agiletec.aps.system.services.baseconfig.ConfigInterface; @@ -43,8 +30,20 @@ import com.agiletec.plugins.jacms.aps.system.services.content.model.ContentRecordVO; import com.agiletec.plugins.jpversioning.aps.system.JpversioningSystemConstants; import java.io.IOException; +import java.io.StringReader; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.entando.entando.ent.exception.EntException; +import org.entando.entando.ent.util.EntLogging.EntLogFactory; +import org.entando.entando.ent.util.EntLogging.EntLogger; +import org.entando.entando.ent.util.EntSafeXmlUtils; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** @@ -68,7 +67,16 @@ public void onSaveContent(Content content) { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onSaveContent", ex); + } + return null; + }, "onSaveContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onSaveContent", e); } @@ -80,7 +88,16 @@ public void onInsertOnLineContent(Content content) { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onInsertOnLineContent", ex); + } + return null; + }, "onInsertOnLineContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onInsertOnLineContent", e); } @@ -92,7 +109,16 @@ public void onRemoveOnLineContent(Content content) { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onRemoveOnLineContent", ex); + } + return null; + }, "onRemoveOnLineContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onRemoveOnLineContent", e); } @@ -104,7 +130,16 @@ public void onDeleteContent(Content content) { if (!this.hasToVersionContent(content)) { return; } - this.saveContentVersion(content.getId()); + IFDeferredVersioning.possiblyDeferred(_executor, + () -> { + try { + saveContentVersion(content.getId()); + + } catch (EntException ex) { + _logger.error("error in (deferred) onDeleteContent", ex); + } + return null; + }, "onDeleteContent, " + content.getId()); } catch (Exception e) { _logger.error("error in onDeleteContent", e); } @@ -217,7 +252,7 @@ public Content getContent(ContentVersion contentVersion) throws EntException { /** * Crea un'entità specifica valorizzata in base alla sua definizione in xml - * ed al tipo. + * e al tipo. * * @param entityTypeCode Il codice del tipo di entità. * @param xml L'xml dell'entità specifica. @@ -277,7 +312,7 @@ protected String getXmlAttributeRootElementName() { /** * Setta il nome dell'attributo della root dell'xml rappresentante la - * singola entità. Il metodo è ad uso della definizione del servizio + * singola entità. Il metodo è a uso della definizione del servizio * nell'xml di configurazione di spring. Di default, la definizione del * servizio astratto nella configurazione di spring presenta una un nome * base "entity"; questa definizione và sostituita nella definizione del @@ -352,6 +387,14 @@ public void setConfigManager(ConfigInterface configManager) { this._configManager = configManager; } + public Executor getExecutor() { + return _executor; + } + + public void setExecutor(Executor executor) { + this._executor = executor; + } + private boolean _deleteMidVersions; private EntityHandler _entityHandler; @@ -363,5 +406,6 @@ public void setConfigManager(ConfigInterface configManager) { private IContentManager _contentManager; private ICategoryManager _categoryManager; private ConfigInterface _configManager; + private Executor _executor; } diff --git a/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml b/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml index 1431987..0d5ed83 100644 --- a/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml +++ b/src/main/resources/spring/plugins/jpversioning/aps/versioningManagersConfig.xml @@ -10,6 +10,15 @@ + + + + + + + + + @@ -24,6 +33,7 @@ + actionMock = mock(Supplier.class); + String methodName = "saveContent"; + + try (MockedStatic mock = mockStatic(IFDeferredVersioning.class)) { + mock.when(IFDeferredVersioning::checkEnabled).thenReturn(true); + mock.when(() -> IFDeferredVersioning.possiblyDeferred( + any(Executor.class), + any(Supplier.class), + anyString() + )).thenCallRealMethod(); + + IFDeferredVersioning.possiblyDeferred(executorMock, actionMock, methodName); + // the executor gets called!!! + verify(executorMock, times(1)).execute(any(Runnable.class)); + } + } + + @Test + void possiblyDeferred_disabled() { + Executor executorMock = mock(Executor.class); + Supplier actionMock = mock(Supplier.class); + String methodName = "saveContent"; + + try (MockedStatic mocked = mockStatic(IFDeferredVersioning.class)) { + // Feature flag disabled + mocked.when(IFDeferredVersioning::checkEnabled).thenReturn(false); + + mocked.when(() -> IFDeferredVersioning.possiblyDeferred( + any(Executor.class), + any(Supplier.class), + anyString()) + ).thenCallRealMethod(); + + IFDeferredVersioning.possiblyDeferred(executorMock, actionMock, methodName); + + verify(actionMock, times(1)).get(); + // Executor never gets called!!! + verify(executorMock, never()).execute(any()); + } + } + +} From e28ccd1a488eb07607d60c84262ad5b13bd5ab94 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 4 Feb 2026 15:04:05 +0100 Subject: [PATCH 03/13] ESB-827: fix version gitops cache version --- .github/workflows/ga-publication.yml | 4 ++-- .github/workflows/pr.yml | 10 +++++----- .github/workflows/publication.yml | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ga-publication.yml b/.github/workflows/ga-publication.yml index 12552a7..8a5260e 100644 --- a/.github/workflows/ga-publication.yml +++ b/.github/workflows/ga-publication.yml @@ -41,7 +41,7 @@ jobs: --id "CHECKOUT FOR GA PUBLICATION" \ --lcd "$LOCAL_CLONE_DIR" - name: "Cache Maven packages" - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} @@ -63,4 +63,4 @@ jobs: env: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} \ No newline at end of file + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 6d1fcfe..ca2a292 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -95,7 +95,7 @@ jobs: java-version: 11 #~ MAVEN CACHE - name: "Cache Maven packages" - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2 @@ -103,7 +103,7 @@ jobs: #~ BUILD CACHE - name: "Cache Build Dir" id: build-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: "${{ env.LOCAL_CLONE_DIR }}/target/" key: ${{ runner.os }}-build-${{ env.BUILD_CACHE_KEY }} @@ -151,7 +151,7 @@ jobs: #~ MAVEN CACHE - name: "Cache Maven packages" id: maven-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2-matrix-${{ matrix.scan-type }} @@ -162,7 +162,7 @@ jobs: #~ BUILD CACHE - name: "Cache Build Dir" id: build-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: "${{ env.LOCAL_CLONE_DIR}}/target/" key: ${{ runner.os }}-build-${{ env.BUILD_CACHE_KEY }} @@ -188,4 +188,4 @@ jobs: export ENTANDO_OPT_TEST_TLS_KEY="${{ secrets.ENTANDO_OPT_TEST_TLS_KEY }}" ;; esac - ~/ppl-run generic "$SCAN_TYPE" mvn --id "$SCAN_TYPE" --lcd "$LOCAL_CLONE_DIR" \ No newline at end of file + ~/ppl-run generic "$SCAN_TYPE" mvn --id "$SCAN_TYPE" --lcd "$LOCAL_CLONE_DIR" diff --git a/.github/workflows/publication.yml b/.github/workflows/publication.yml index d3ba911..7834d68 100644 --- a/.github/workflows/publication.yml +++ b/.github/workflows/publication.yml @@ -56,7 +56,7 @@ jobs: java-version: 11 #~ MAVEN CACHE - name: "Cache Maven packages" - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2 @@ -64,7 +64,7 @@ jobs: #~ BUILD CACHE - name: "Cache Build Dir" id: build-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: "${{ env.LOCAL_CLONE_DIR }}/target/" key: ${{ runner.os }}-build-${{ env.BUILD_CACHE_KEY }} @@ -131,7 +131,7 @@ jobs: java-version: 11 #~ MAVEN CACHE - name: "Cache Maven packages" - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2 @@ -139,7 +139,7 @@ jobs: #~ BUILD CACHE - name: "Cache Build Dir" id: build-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: "${{ env.LOCAL_CLONE_DIR }}/target/" key: ${{ runner.os }}-build-${{ env.BUILD_CACHE_KEY }} @@ -177,7 +177,7 @@ jobs: java-version: 11 #~ MAVEN CACHE - name: "Cache Maven packages" - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ~/.m2 key: ${{ runner.os }}-m2 @@ -185,7 +185,7 @@ jobs: #~ BUILD CACHE - name: "Cache Build Dir" id: build-cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: "${{ env.LOCAL_CLONE_DIR }}/target/" key: ${{ runner.os }}-build-${{ env.BUILD_CACHE_KEY }} @@ -200,4 +200,4 @@ jobs: export ENTANDO_OPT_TEST_TLS_CRT="${{ secrets.ENTANDO_OPT_TEST_TLS_CRT }}" export ENTANDO_OPT_TEST_TLS_KEY="${{ secrets.ENTANDO_OPT_TEST_TLS_KEY }}" - ~/ppl-run generic "POST-DEP-TESTS" --id "POST_DEP_TESTS" --lcd "$LOCAL_CLONE_DIR" \ No newline at end of file + ~/ppl-run generic "POST-DEP-TESTS" --id "POST_DEP_TESTS" --lcd "$LOCAL_CLONE_DIR" From 7083ee6482f440feb8d5699e092f7a985718b086 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 4 Feb 2026 15:47:34 +0100 Subject: [PATCH 04/13] ESB-827: fix version gitops cache version --- .github/workflows/pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ca2a292..1890fa8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -109,7 +109,7 @@ jobs: key: ${{ runner.os }}-build-${{ env.BUILD_CACHE_KEY }} #~ SONAR CACHE - name: Cache SonarCloud packages - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar From 1f421bc2c03e9da4d50b693495727e88d76e06f5 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 4 Feb 2026 16:28:29 +0100 Subject: [PATCH 05/13] ESB-827: Updated POM with JACMS version --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0490719..8cb9933 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ org.entando.entando.plugins entando-plugin-jacms classes - 6.5.4 + 6.5.4-ESB-827-PR-240 org.entando.entando @@ -147,7 +147,7 @@ org.entando.entando.plugins entando-plugin-jacms test-jar - 6.5.4 + 6.5.4-ESB-827-PR-240 org.springframework From 21646a2c77e26249f55c7badb09a9f2d2e6bfcc3 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Thu, 5 Feb 2026 09:06:32 +0100 Subject: [PATCH 06/13] ESB-827: quality gate --- .../versioning/IFDeferredVersioning.java | 14 +++- .../versioning/VersioningManager.java | 70 ++++--------------- .../versioning/TestVersioningManager.java | 11 ++- .../web/content/IFDeferredVersioningTest.java | 69 ++++++++++++++++++ 4 files changed, 104 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java index be70c35..9df9654 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/IFDeferredVersioning.java @@ -17,12 +17,20 @@ static boolean checkEnabled() { return IFeatureFlag.readEnablementStatus("DEFERRED_VERSIONING"); } - static void possiblyDeferred(Executor executor, Supplier action, String method) { + static CompletableFuture possiblyDeferred(Executor executor, Supplier action, String method) { if (checkEnabled()) { ApsDeepDebug.print("deferred-versioning", "running versioning task in a separate thread: " + method); - CompletableFuture.runAsync(action::get, executor); + + return CompletableFuture.runAsync(() -> action.get(), executor); } else { - action.get(); + try { + action.get(); + return CompletableFuture.completedFuture(null); + } catch (Exception e) { + CompletableFuture failed = new CompletableFuture<>(); + failed.completeExceptionally(e); + return failed; + } } } diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index 129c440..cf9cf53 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -65,69 +65,25 @@ public void init() throws Exception { @Before("execution(* com.agiletec.plugins.jacms.aps.system.services.content.IContentManager.saveContent(..)) && args(content)") public void onSaveContent(Content content) { - try { - if (!this.hasToVersionContent(content)) { - return; - } - IFDeferredVersioning.possiblyDeferred(_executor, - () -> { - try { - saveContentVersion(content.getId()); - - } catch (EntException ex) { - _logger.error("error in (deferred) onSaveContent", ex); - } - return null; - }, "onSaveContent, " + content.getId()); - } catch (Exception e) { - _logger.error("error in onSaveContent", e); - } + this.processDeferredVersioning(content, "onSaveContent"); } @Before("execution(* com.agiletec.plugins.jacms.aps.system.services.content.IContentManager.insertOnLineContent(..)) && args(content)") public void onInsertOnLineContent(Content content) { - try { - if (!this.hasToVersionContent(content)) { - return; - } - IFDeferredVersioning.possiblyDeferred(_executor, - () -> { - try { - saveContentVersion(content.getId()); - - } catch (EntException ex) { - _logger.error("error in (deferred) onInsertOnLineContent", ex); - } - return null; - }, "onInsertOnLineContent, " + content.getId()); - } catch (Exception e) { - _logger.error("error in onInsertOnLineContent", e); - } + this.processDeferredVersioning(content, "onInsertOnLineContent"); } @Before("execution(* com.agiletec.plugins.jacms.aps.system.services.content.IContentManager.removeOnLineContent(..)) && args(content)") public void onRemoveOnLineContent(Content content) { - try { - if (!this.hasToVersionContent(content)) { - return; - } - IFDeferredVersioning.possiblyDeferred(_executor, - () -> { - try { - saveContentVersion(content.getId()); - - } catch (EntException ex) { - _logger.error("error in (deferred) onRemoveOnLineContent", ex); - } - return null; - }, "onRemoveOnLineContent, " + content.getId()); - } catch (Exception e) { - _logger.error("error in onRemoveOnLineContent", e); - } + this.processDeferredVersioning(content, "onRemoveOnLineContent"); } @Before("execution(* com.agiletec.plugins.jacms.aps.system.services.content.IContentManager.deleteContent(..)) && args(content)") public void onDeleteContent(Content content) { + this.processDeferredVersioning(content, "onDeleteContent"); + } + + private void processDeferredVersioning(Content content, String methodName) { try { if (!this.hasToVersionContent(content)) { return; @@ -136,14 +92,18 @@ public void onDeleteContent(Content content) { () -> { try { saveContentVersion(content.getId()); - } catch (EntException ex) { - _logger.error("error in (deferred) onDeleteContent", ex); + _logger.error("error in (deferred) {}", methodName, ex); } return null; - }, "onDeleteContent, " + content.getId()); + }, methodName + ", " + content.getId()) + .whenComplete((result, ex) -> { + if (ex != null) { + _logger.error("error in possiblyDeferred {}", methodName, ex); + } + }); } catch (Exception e) { - _logger.error("error in onDeleteContent", e); + _logger.error("error in {}", methodName, e); } } diff --git a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java index e443263..50f482b 100644 --- a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java +++ b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java @@ -58,6 +58,7 @@ public class TestVersioningManager extends BaseTestCase { private IVersioningManager versioningManager; private JpversioningTestHelper helper; + @org.junit.jupiter.api.Test void testGetVersions() throws Throwable { List versions = this.versioningManager.getVersions("CNG12"); assertNull(versions); @@ -66,6 +67,7 @@ void testGetVersions() throws Throwable { this.checkVersionIds(new long[]{1, 2, 3}, versions); } + @org.junit.jupiter.api.Test void testGetLastVersions() throws Throwable { List versions = this.versioningManager.getLastVersions("CNG", null); assertTrue(versions.isEmpty()); @@ -74,6 +76,7 @@ void testGetLastVersions() throws Throwable { this.checkVersionIds(new long[]{3}, versions); } + @org.junit.jupiter.api.Test void testGetVersion() throws Throwable { ContentVersion contentVersion = this.versioningManager.getVersion(10000); assertNull(contentVersion); @@ -92,6 +95,7 @@ void testGetVersion() throws Throwable { assertEquals("admin", contentVersion.getUsername()); } + @org.junit.jupiter.api.Test void testGetLastVersion() throws Throwable { ContentVersion contentVersion = this.versioningManager.getLastVersion("CNG12"); assertNull(contentVersion); @@ -109,6 +113,7 @@ void testGetLastVersion() throws Throwable { assertEquals("mainEditor", contentVersion.getUsername()); } + @org.junit.jupiter.api.Test void testSaveGetDeleteVersion() throws Throwable { ((VersioningManager) this.versioningManager).saveContentVersion("ART102"); ContentVersion contentVersion = this.versioningManager.getLastVersion("ART102"); @@ -145,11 +150,13 @@ private void checkVersionIds(long[] expected, List received) { } } + @org.junit.jupiter.api.Test void testContentVersionToIgnore_1() throws Exception { this.testContentVersionToIgnore(false, true); this.testContentVersionToIgnore(true, true); } + @org.junit.jupiter.api.Test void testContentVersionToIgnore_2() throws Exception { this.testContentVersionToIgnore(false, false); this.testContentVersionToIgnore(true, false); @@ -215,7 +222,7 @@ private void updateConfigItem(String paramKey, String paramValue) throws Excepti } @BeforeEach - private void init() throws Exception { + public void init() throws Exception { this.versioningManager = (IVersioningManager) this.getService(JpversioningSystemConstants.VERSIONING_MANAGER); this.configManager = (ConfigInterface) this.getService(SystemConstants.BASE_CONFIG_MANAGER); this.contentManager = (IContentManager) this.getService(JacmsSystemConstants.CONTENT_MANAGER); @@ -225,7 +232,7 @@ private void init() throws Exception { } @AfterEach - private void dispose() throws Exception { + public void dispose() throws Exception { this.helper.cleanContentVersions(); } diff --git a/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java b/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java index b76cb7f..4de7849 100644 --- a/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java +++ b/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java @@ -1,14 +1,22 @@ package org.entando.entando.jpversioning.web.content; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.agiletec.plugins.jpversioning.aps.system.services.versioning.IFDeferredVersioning; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.function.Supplier; import org.entando.entando.web.AbstractControllerIntegrationTest; @@ -63,4 +71,65 @@ void possiblyDeferred_disabled() { } } + @Test + void possiblyDeferred_exception_enabled() throws Exception { + Executor executorMock = mock(Executor.class); + Supplier actionMock = mock(Supplier.class); + String methodName = "saveContent"; + RuntimeException exception = new RuntimeException("Test Exception"); + + when(actionMock.get()).thenThrow(exception); + + // Simulo l'esecuzione immediata nel thread corrente quando viene chiamato l'executor + doAnswer(invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }).when(executorMock).execute(any(Runnable.class)); + + try (MockedStatic mock = mockStatic(IFDeferredVersioning.class)) { + mock.when(IFDeferredVersioning::checkEnabled).thenReturn(true); + mock.when(() -> IFDeferredVersioning.possiblyDeferred( + any(Executor.class), + any(Supplier.class), + anyString() + )).thenCallRealMethod(); + + CompletableFuture result = IFDeferredVersioning.possiblyDeferred(executorMock, actionMock, methodName); + + assertNotNull(result); + assertTrue(result.isCompletedExceptionally()); + + ExecutionException ex = assertThrows(ExecutionException.class, result::get); + assertEquals(exception, ex.getCause()); + } + } + + @Test + void possiblyDeferred_exception_disabled() throws Exception { + Executor executorMock = mock(Executor.class); + Supplier actionMock = mock(Supplier.class); + String methodName = "saveContent"; + RuntimeException exception = new RuntimeException("Test Exception"); + + when(actionMock.get()).thenThrow(exception); + + try (MockedStatic mocked = mockStatic(IFDeferredVersioning.class)) { + mocked.when(IFDeferredVersioning::checkEnabled).thenReturn(false); + mocked.when(() -> IFDeferredVersioning.possiblyDeferred( + any(Executor.class), + any(Supplier.class), + anyString()) + ).thenCallRealMethod(); + + CompletableFuture result = IFDeferredVersioning.possiblyDeferred(executorMock, actionMock, methodName); + + assertNotNull(result); + assertTrue(result.isCompletedExceptionally()); + + ExecutionException ex = assertThrows(ExecutionException.class, result::get); + assertEquals(exception, ex.getCause()); + } + } + } From 7a78a4971e06ed4f5ec01996d031c9f2fd970452 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Thu, 5 Feb 2026 10:35:17 +0100 Subject: [PATCH 07/13] ESB-827: quality gate --- .../web/content/IFDeferredVersioningTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java b/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java index 4de7849..0ef6b0f 100644 --- a/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java +++ b/src/test/java/org/entando/entando/jpversioning/web/content/IFDeferredVersioningTest.java @@ -132,4 +132,24 @@ void possiblyDeferred_exception_disabled() throws Exception { } } + @Test + void possiblyDeferred_should_propagate_exception_when_enabled() throws Exception { + Executor executor = Runnable::run; // Immediate execution + Supplier action = () -> { + throw new RuntimeException("Async Exception"); + }; + + try (MockedStatic mocked = mockStatic(IFDeferredVersioning.class)) { + mocked.when(IFDeferredVersioning::checkEnabled).thenReturn(true); + mocked.when(() -> IFDeferredVersioning.possiblyDeferred(any(), any(), anyString())).thenCallRealMethod(); + + CompletableFuture result = IFDeferredVersioning.possiblyDeferred(executor, action, "testMethod"); + + assertNotNull(result); + assertTrue(result.isCompletedExceptionally()); + ExecutionException ex = assertThrows(ExecutionException.class, result::get); + assertEquals("Async Exception", ex.getCause().getMessage()); + } + } + } From d7f0a1af6d18761d9b83f1e76caa579b71ac419f Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 11 Feb 2026 09:06:36 +0100 Subject: [PATCH 08/13] ESB-827 fixed corner case --- .../versioning/VersioningManager.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index cf9cf53..2b22e66 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -88,10 +88,13 @@ private void processDeferredVersioning(Content content, String methodName) { if (!this.hasToVersionContent(content)) { return; } + // this is kept sequential + final ContentRecordVO record = this.getContentManager().loadContentVO(content.getId()); + // the remainder of the process can be safely deferred IFDeferredVersioning.possiblyDeferred(_executor, () -> { try { - saveContentVersion(content.getId()); + saveContentVersion(record); } catch (EntException ex) { _logger.error("error in (deferred) {}", methodName, ex); } @@ -173,16 +176,8 @@ public ContentVersion getLastVersion(String contentId) throws EntException { public void saveContentVersion(String contentId) throws EntException { try { if (contentId != null) { - ContentRecordVO record = this.getContentManager().loadContentVO(contentId); - if (record != null) { - ContentVersion versionRecord = this.createContentVersion(record); - //CANCELLAZIONE VERSIONE WORK OBSOLETE - if (versionRecord.isApproved()) { - int onlineVersionsToDelete = versionRecord.getOnlineVersion() - 1; - this.deleteWorkVersions(versionRecord.getContentId(), onlineVersionsToDelete); - } - this.getVersioningDAO().addContentVersion(versionRecord); - } + final ContentRecordVO record = this.getContentManager().loadContentVO(contentId); + saveContentVersion(record); } } catch (Exception e) { _logger.error("error in Error saving version for content {}", contentId, e); @@ -190,6 +185,23 @@ public void saveContentVersion(String contentId) throws EntException { } } + public void saveContentVersion(final ContentRecordVO record) throws EntException { + try { + if (record != null) { + ContentVersion versionRecord = this.createContentVersion(record); + //CANCELLAZIONE VERSIONE WORK OBSOLETE + if (versionRecord.isApproved()) { + int onlineVersionsToDelete = versionRecord.getOnlineVersion() - 1; + this.deleteWorkVersions(versionRecord.getContentId(), onlineVersionsToDelete); + } + this.getVersioningDAO().addContentVersion(versionRecord); + } + } catch (Exception e) { + _logger.error("error in Error saving version for content {}", record.getId(), e); + throw new EntException("Error saving version for content" + record.getId()); + } + } + @Override public void deleteWorkVersions(String contentId, int onlineVersion) throws EntException { try { From 68d7ba8d84fa3d9ae28730c21c36a1a393bbfb5d Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 11 Feb 2026 10:48:17 +0100 Subject: [PATCH 09/13] ESB-827: test coverage --- .../versioning/TestVersioningManager.java | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java index 50f482b..1d7d4a1 100644 --- a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java +++ b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java @@ -32,21 +32,19 @@ import com.agiletec.aps.system.services.baseconfig.ConfigInterface; import com.agiletec.aps.system.services.baseconfig.SystemParamsUtils; import com.agiletec.aps.system.services.group.Group; -import java.util.List; - -import javax.sql.DataSource; - -import com.agiletec.plugins.jpversioning.util.JpversioningTestHelper; - import com.agiletec.aps.util.DateConverter; import com.agiletec.plugins.jacms.aps.system.JacmsSystemConstants; import com.agiletec.plugins.jacms.aps.system.services.content.IContentManager; import com.agiletec.plugins.jacms.aps.system.services.content.model.Content; import com.agiletec.plugins.jpversioning.aps.system.JpversioningSystemConstants; +import com.agiletec.plugins.jpversioning.util.JpversioningTestHelper; import java.util.HashMap; +import java.util.List; import java.util.Map; +import javax.sql.DataSource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * @author G.Cocco @@ -58,7 +56,7 @@ public class TestVersioningManager extends BaseTestCase { private IVersioningManager versioningManager; private JpversioningTestHelper helper; - @org.junit.jupiter.api.Test + @Test void testGetVersions() throws Throwable { List versions = this.versioningManager.getVersions("CNG12"); assertNull(versions); @@ -67,7 +65,7 @@ void testGetVersions() throws Throwable { this.checkVersionIds(new long[]{1, 2, 3}, versions); } - @org.junit.jupiter.api.Test + @Test void testGetLastVersions() throws Throwable { List versions = this.versioningManager.getLastVersions("CNG", null); assertTrue(versions.isEmpty()); @@ -76,7 +74,7 @@ void testGetLastVersions() throws Throwable { this.checkVersionIds(new long[]{3}, versions); } - @org.junit.jupiter.api.Test + @Test void testGetVersion() throws Throwable { ContentVersion contentVersion = this.versioningManager.getVersion(10000); assertNull(contentVersion); @@ -95,7 +93,7 @@ void testGetVersion() throws Throwable { assertEquals("admin", contentVersion.getUsername()); } - @org.junit.jupiter.api.Test + @Test void testGetLastVersion() throws Throwable { ContentVersion contentVersion = this.versioningManager.getLastVersion("CNG12"); assertNull(contentVersion); @@ -113,7 +111,7 @@ void testGetLastVersion() throws Throwable { assertEquals("mainEditor", contentVersion.getUsername()); } - @org.junit.jupiter.api.Test + @Test void testSaveGetDeleteVersion() throws Throwable { ((VersioningManager) this.versioningManager).saveContentVersion("ART102"); ContentVersion contentVersion = this.versioningManager.getLastVersion("ART102"); @@ -133,12 +131,28 @@ void testSaveGetDeleteVersion() throws Throwable { assertNull(this.versioningManager.getLastVersion("ART102")); } - public void deleteWorkVersions() throws Throwable { - List versions = this.versioningManager.getVersions("ART1"); - this.checkVersionIds(new long[]{1, 2, 3}, versions); - this.versioningManager.deleteWorkVersions("ART1", 0); - versions = this.versioningManager.getVersions("ART1"); - this.checkVersionIds(new long[]{1, 3}, versions); + @Test + void testDeleteWorkVersions() throws Throwable { + String contentId = "ART1"; + VersioningManager versioningManagerImpl = (VersioningManager) this.versioningManager; + try { + versioningManagerImpl.setDeleteMidVersions(true); + List versions = this.versioningManager.getVersions(contentId); + this.checkVersionIds(new long[]{1, 2, 3}, versions); + this.versioningManager.deleteWorkVersions(contentId, 0); + versions = this.versioningManager.getVersions(contentId); + this.checkVersionIds(new long[]{1, 3}, versions); + + this.helper.initContentVersions(); + versioningManagerImpl.setDeleteMidVersions(false); + versions = this.versioningManager.getVersions(contentId); + this.checkVersionIds(new long[]{1, 2, 3}, versions); + this.versioningManager.deleteWorkVersions(contentId, 0); + versions = this.versioningManager.getVersions(contentId); + this.checkVersionIds(new long[]{1, 2, 3}, versions); + } finally { + versioningManagerImpl.setDeleteMidVersions(true); + } } private void checkVersionIds(long[] expected, List received) { @@ -150,13 +164,13 @@ private void checkVersionIds(long[] expected, List received) { } } - @org.junit.jupiter.api.Test + @Test void testContentVersionToIgnore_1() throws Exception { this.testContentVersionToIgnore(false, true); this.testContentVersionToIgnore(true, true); } - @org.junit.jupiter.api.Test + @Test void testContentVersionToIgnore_2() throws Exception { this.testContentVersionToIgnore(false, false); this.testContentVersionToIgnore(true, false); From 79d3689c393b2c5033ada456a1e59f8c8f1881a4 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 11 Feb 2026 10:58:34 +0100 Subject: [PATCH 10/13] ESB-827: test coverage --- .../versioning/TestVersioningManager.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java index 1d7d4a1..0071a21 100644 --- a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java +++ b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -227,6 +228,33 @@ protected void testContentVersionToIgnore(boolean includeVersion, boolean isType } } + @Test + void testGetContent_Exception() { + ContentVersion contentVersion = new ContentVersion(); + contentVersion.setContentType("ART"); + contentVersion.setXml(""); + assertThrows(org.entando.entando.ent.exception.EntException.class, () -> { + this.versioningManager.getContent(contentVersion); + }); + + contentVersion.setXml(""); + contentVersion.setContentType("INVALID_TYPE"); + assertThrows(org.entando.entando.ent.exception.EntException.class, () -> { + this.versioningManager.getContent(contentVersion); + }); + } + + @Test + void testSaveContentVersion_Exception() { + // We test that it does NOT throw exception for null ID, as per implementation + try { + this.versioningManager.saveContentVersion((String) null); + } catch (Exception e) { + fail("Should not throw exception for null contentId"); + } + } + + private void updateConfigItem(String paramKey, String paramValue) throws Exception { Map params = new HashMap<>(); params.put(paramKey, paramValue); From 87474477d4460e73c3324b6d7c58e8e5099d8324 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 11 Feb 2026 11:14:18 +0100 Subject: [PATCH 11/13] ESB-827: test coverage --- .../versioning/VersioningManager.java | 8 ++--- .../versioning/TestVersioningManager.java | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index 2b22e66..f5604e8 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -168,7 +168,7 @@ public ContentVersion getLastVersion(String contentId) throws EntException { return this.getVersioningDAO().getLastVersion(contentId); } catch (Exception e) { _logger.error("Error loading last version for content {}", contentId, e); - throw new EntException("Error loading last version for content" + contentId); + throw new EntException("Error loading last version for content " + contentId); } } @@ -181,11 +181,11 @@ public void saveContentVersion(String contentId) throws EntException { } } catch (Exception e) { _logger.error("error in Error saving version for content {}", contentId, e); - throw new EntException("Error saving version for content" + contentId); + throw new EntException("Error saving version for content " + contentId); } } - public void saveContentVersion(final ContentRecordVO record) throws EntException { + protected void saveContentVersion(final ContentRecordVO record) throws EntException { try { if (record != null) { ContentVersion versionRecord = this.createContentVersion(record); @@ -198,7 +198,7 @@ public void saveContentVersion(final ContentRecordVO record) throws EntException } } catch (Exception e) { _logger.error("error in Error saving version for content {}", record.getId(), e); - throw new EntException("Error saving version for content" + record.getId()); + throw new EntException("Error saving version for content " + record.getId()); } } diff --git a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java index 0071a21..af54cde 100644 --- a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java +++ b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java @@ -26,6 +26,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.junit.jupiter.api.Assertions.fail; import com.agiletec.aps.BaseTestCase; @@ -46,6 +49,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; /** * @author G.Cocco @@ -254,6 +258,38 @@ void testSaveContentVersion_Exception() { } } + @Test + void testSaveContentVersionVO_Exception() { + com.agiletec.plugins.jacms.aps.system.services.content.model.ContentRecordVO record = new com.agiletec.plugins.jacms.aps.system.services.content.model.ContentRecordVO(); + record.setId("ART1"); + record.setVersion("invalid.version"); // This will cause NumberFormatException in createContentVersion + + assertThrows(org.entando.entando.ent.exception.EntException.class, () -> { + ((VersioningManager) this.versioningManager).saveContentVersion(record); + }); + + record.setVersion(null); // This will cause NullPointerException in createContentVersion + assertThrows(org.entando.entando.ent.exception.EntException.class, () -> { + ((VersioningManager) this.versioningManager).saveContentVersion(record); + }); + } + + @Test + void testSaveContentVersionString_Exception() throws Exception { + IContentManager originalContentManager = ((VersioningManager) this.versioningManager).getContentManager(); + IContentManager mockContentManager = mock(IContentManager.class); + when(mockContentManager.loadContentVO(anyString())).thenThrow(new RuntimeException("Test exception")); + + try { + ((VersioningManager) this.versioningManager).setContentManager(mockContentManager); + assertThrows(org.entando.entando.ent.exception.EntException.class, () -> { + this.versioningManager.saveContentVersion("ART1"); + }); + } finally { + ((VersioningManager) this.versioningManager).setContentManager(originalContentManager); + } + } + private void updateConfigItem(String paramKey, String paramValue) throws Exception { Map params = new HashMap<>(); From 536590b0207167b1005009031d8ba8210317c8b5 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 11 Feb 2026 11:17:46 +0100 Subject: [PATCH 12/13] ESB-827: code cleaning --- .../system/services/versioning/VersioningManager.java | 1 - .../services/versioning/TestVersioningManager.java | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index f5604e8..bf5a496 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -32,7 +32,6 @@ import java.io.IOException; import java.io.StringReader; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; diff --git a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java index af54cde..615a804 100644 --- a/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java +++ b/src/test/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/TestVersioningManager.java @@ -26,10 +26,10 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.junit.jupiter.api.Assertions.fail; import com.agiletec.aps.BaseTestCase; import com.agiletec.aps.system.SystemConstants; @@ -49,7 +49,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.test.util.ReflectionTestUtils; /** * @author G.Cocco @@ -118,7 +117,7 @@ void testGetLastVersion() throws Throwable { @Test void testSaveGetDeleteVersion() throws Throwable { - ((VersioningManager) this.versioningManager).saveContentVersion("ART102"); + this.versioningManager.saveContentVersion("ART102"); ContentVersion contentVersion = this.versioningManager.getLastVersion("ART102"); assertEquals(4, contentVersion.getId()); assertEquals("ART102", contentVersion.getContentId()); @@ -163,7 +162,7 @@ void testDeleteWorkVersions() throws Throwable { private void checkVersionIds(long[] expected, List received) { assertEquals(expected.length, received.size()); for (long current : expected) { - if (!received.contains(new Long(current))) { + if (!received.contains(current)) { fail("Expected " + current + " - Not found"); } } @@ -212,8 +211,6 @@ protected void testContentVersionToIgnore(boolean includeVersion, boolean isType } else { assertTrue(null == versions || versions.isEmpty()); } - } catch (Exception e) { - throw e; } finally { if (null != versions) { for (Long version : versions) { From 57ca944d319fa144649714dc5b660bfe22795d48 Mon Sep 17 00:00:00 2001 From: "Matteo E. Minnai" Date: Wed, 11 Feb 2026 11:30:07 +0100 Subject: [PATCH 13/13] ESB-827: code cleaning --- .../versioning/VersioningManager.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java index bf5a496..15989ea 100644 --- a/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java +++ b/src/main/java/com/agiletec/plugins/jpversioning/aps/system/services/versioning/VersioningManager.java @@ -88,12 +88,12 @@ private void processDeferredVersioning(Content content, String methodName) { return; } // this is kept sequential - final ContentRecordVO record = this.getContentManager().loadContentVO(content.getId()); + final ContentRecordVO contentRecordVO = this.getContentManager().loadContentVO(content.getId()); // the remainder of the process can be safely deferred IFDeferredVersioning.possiblyDeferred(_executor, () -> { try { - saveContentVersion(record); + saveContentVersion(contentRecordVO); } catch (EntException ex) { _logger.error("error in (deferred) {}", methodName, ex); } @@ -175,8 +175,8 @@ public ContentVersion getLastVersion(String contentId) throws EntException { public void saveContentVersion(String contentId) throws EntException { try { if (contentId != null) { - final ContentRecordVO record = this.getContentManager().loadContentVO(contentId); - saveContentVersion(record); + final ContentRecordVO contentRecordVO = this.getContentManager().loadContentVO(contentId); + saveContentVersion(contentRecordVO); } } catch (Exception e) { _logger.error("error in Error saving version for content {}", contentId, e); @@ -184,10 +184,10 @@ public void saveContentVersion(String contentId) throws EntException { } } - protected void saveContentVersion(final ContentRecordVO record) throws EntException { + protected void saveContentVersion(final ContentRecordVO recordVO) throws EntException { try { - if (record != null) { - ContentVersion versionRecord = this.createContentVersion(record); + if (recordVO != null) { + ContentVersion versionRecord = this.createContentVersion(recordVO); //CANCELLAZIONE VERSIONE WORK OBSOLETE if (versionRecord.isApproved()) { int onlineVersionsToDelete = versionRecord.getOnlineVersion() - 1; @@ -196,8 +196,8 @@ protected void saveContentVersion(final ContentRecordVO record) throws EntExcept this.getVersioningDAO().addContentVersion(versionRecord); } } catch (Exception e) { - _logger.error("error in Error saving version for content {}", record.getId(), e); - throw new EntException("Error saving version for content " + record.getId()); + _logger.error("error in Error saving version for content {}", recordVO.getId(), e); + throw new EntException("Error saving version for content " + recordVO.getId()); } } @@ -379,6 +379,6 @@ public void setExecutor(Executor executor) { private IContentManager _contentManager; private ICategoryManager _categoryManager; private ConfigInterface _configManager; - private Executor _executor; + private transient Executor _executor; }