From 5080d501655d73587fba50fa52074945d201e80e Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Thu, 18 Dec 2025 00:12:41 +0200 Subject: [PATCH 01/76] #2224 First steps to Entity Centre named configuration execution API. --- .../platform/share/ShareActionProducer.java | 11 +- .../platform/web/utils/EntityCentreAPI.java | 23 ++ .../ioc/IBasicWebApplicationServerModule.java | 3 + .../resources/webui/CentreResourceUtils.java | 8 +- .../web/resources/webui/CriteriaResource.java | 74 ++++- .../web/utils/EntityCentreAPIImpl.java | 300 ++++++++++++++++++ 6 files changed, 397 insertions(+), 22 deletions(-) create mode 100644 platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java create mode 100644 platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index 6e8ac63b69f..6548c86bc4e 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -12,6 +12,7 @@ import ua.com.fielden.platform.tiny.TinyHyperlinkCo; import ua.com.fielden.platform.types.Hyperlink; import ua.com.fielden.platform.web.centre.CentreContext; +import ua.com.fielden.platform.web.utils.EntityCentreAPI; import java.util.Base64; import java.util.Optional; @@ -33,13 +34,16 @@ public class ShareActionProducer extends DefaultEntityProducerWithContext { private CentreContextHolder centreContextHolder; + private final EntityCentreAPI entityCentreAPI; @Inject ShareActionProducer( final EntityFactory factory, - final ICompanionObjectFinder companionFinder) + final ICompanionObjectFinder companionFinder, + final EntityCentreAPI entityCentreAPI) { super(factory, ShareAction.class, companionFinder); + this.entityCentreAPI = entityCentreAPI; } @Override @@ -77,6 +81,11 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { }); } + final var result = entityCentreAPI.entityCentreResult(null, null, null); + result.asRight().value()._1.forEach(ent -> { + System.out.println(ent.get("details") + " " + ent.get("person") + " " + ent.get("date")); + }); + return super.provideDefaultValues(entity); } diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java new file mode 100644 index 00000000000..2458346bfdc --- /dev/null +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java @@ -0,0 +1,23 @@ +package ua.com.fielden.platform.web.utils; + +import ua.com.fielden.platform.dao.IEntityDao; +import ua.com.fielden.platform.entity.AbstractEntity; +import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; +import ua.com.fielden.platform.error.Result; +import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.types.Hyperlink; +import ua.com.fielden.platform.types.either.Either; +import ua.com.fielden.platform.types.tuples.T2; + +import java.util.List; + +public interface EntityCentreAPI { + + , M extends EnhancedCentreEntityQueryCriteria>> + Either, Hyperlink>> entityCentreResult( + Class miTypeForStandaloneCentre, + String configUuid, + User user + ); + +} diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java index 8fd90d0e69b..863019cdcb2 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java @@ -17,6 +17,8 @@ import ua.com.fielden.platform.web.test.server.TgTestWebApplicationServerIocModule; import ua.com.fielden.platform.web.uri.EntityMasterUrlProvider; import ua.com.fielden.platform.web.utils.CriteriaEntityRestorer; +import ua.com.fielden.platform.web.utils.EntityCentreAPI; +import ua.com.fielden.platform.web.utils.EntityCentreAPIImpl; import ua.com.fielden.platform.web.utils.ICriteriaEntityRestorer; import static ua.com.fielden.platform.basic.config.Workflows.deployment; @@ -61,6 +63,7 @@ default void bindWebAppResources(final IWebUiConfig webApp) { // dependent on IWebUiConfig, IUserProvider and other Web UI infrastructure bindType(ICriteriaEntityRestorer.class).to(CriteriaEntityRestorer.class); + bindType(EntityCentreAPI.class).to(EntityCentreAPIImpl.class); // bind companion object implementations that are dependent on ICriteriaEntityRestorer PlatformDomainTypes.typesDependentOnWebUI.stream().forEach(type -> bindCo(type, (co, t) -> bindType(co).to(t))); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index dce38175da5..a7e458f573a 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -127,7 +127,7 @@ public class CentreResourceUtils> extends CentreUtil private CentreResourceUtils() { } - private enum RunActions { + public enum RunActions { RUN("run"), REFRESH("refresh"), NAVIGATE("navigate"); @@ -244,7 +244,7 @@ public static boolean isAutoRunning(final Map customObject) { /// /// @param updatedPreviouslyRunCriteriaEntity criteria entity created from PREVIOUSLY_RUN surrogate centre, which was potentially updated from FRESH (in case of "running" action), but not yet actually used for running /// - static , M extends EnhancedCentreEntityQueryCriteria>> Pair, List>> createCriteriaMetaValuesCustomObjectWithResult( + public static , M extends EnhancedCentreEntityQueryCriteria>> Pair, List>> createCriteriaMetaValuesCustomObjectWithResult( final Map customObject, final M updatedPreviouslyRunCriteriaEntity) { final Map resultantCustomObject = new LinkedHashMap<>(); @@ -474,7 +474,7 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd /// /// The entity creation process uses rigorous generation of criteria type and the instance every time (based on cdtmae of concrete miType). /// - static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototype( + public static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototype( final Class> miType, final Optional saveAsName, final ICentreDomainTreeManagerAndEnhancer cdtmae, @@ -835,7 +835,7 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd /// UPDATE: resetting of the values has been enhanced (comparing to just invoking resetMetaState() on entity) with the functionality, for which the detailed comment is inside /// the method implementation. /// - private static , M extends EnhancedCentreEntityQueryCriteria>> M resetMetaStateForCriteriaValidationPrototype(final M criteriaValidationPrototype, final Class originalManagedType) { + public static , M extends EnhancedCentreEntityQueryCriteria>> M resetMetaStateForCriteriaValidationPrototype(final M criteriaValidationPrototype, final Class originalManagedType) { // standard resetting of meta-values: copies values into originalValues for all properties: criteriaValidationPrototype.resetMetaState(); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index e35cbe717c0..1508235fd95 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -211,7 +211,18 @@ public Representation get() { } else { // if there is no FRESH configuration then there are no [link, own save-as] configuration with the specified uuid; // however there can exist [base, shared] config for other user with the specified uuid; - actualSaveAsNameAndSharedIndicator = firstTimeLoadingFrom(validateUuidAndGetUpstreamConfig(configUuid.get()).orElseThrow(Result::asRuntime)); + actualSaveAsNameAndSharedIndicator = firstTimeLoadingFrom( + validateUuidAndGetUpstreamConfig(configUuid.get(), miType, device(), eccCompanion, user).orElseThrow(Result::asRuntime), + miType, + device(), + eccCompanion, + user, + webUiConfig, + mmiCompanion, + userCompanion, + sharingModel, + companionFinder + ); } actualSaveAsName = actualSaveAsNameAndSharedIndicator._1; final boolean isInheritedFromShared = actualSaveAsNameAndSharedIndicator._2; @@ -243,9 +254,15 @@ public Representation get() { /// Validates `configUuid` to ensure the configuration exists and can be shared with the current `user`. /// - private Either validateUuidAndGetUpstreamConfig(final String configUuid) { + public static Either validateUuidAndGetUpstreamConfig( + final String configUuid, + final Class> miType, + final DeviceProfile device, + final EntityCentreConfigCo eccCompanion, + final User user + ) { // we look only for owners; "owning" is indicated by presence of SAVED configuration with the specified uuid - final var savedConfigOptForOtherUser = findConfigOptByUuid(configUuid, miType, device(), SAVED_CENTRE_NAME, eccCompanion); + final var savedConfigOptForOtherUser = findConfigOptByUuid(configUuid, miType, device, SAVED_CENTRE_NAME, eccCompanion); if (savedConfigOptForOtherUser.isEmpty()) { // configuration does not exist (no SAVED surrogate centre) -- legitimate error; this can happen if configuration has been already deleted or didn't exist due to URI mistyping return left(failure(CONFIG_DOES_NOT_EXIST)); @@ -256,7 +273,7 @@ private Either validateUuidAndGetUpstreamConfig(fina // so from two categories [base, shared] we can only consider [shared]; // so, at this stage, we prohibit loading of [inherited from shared] configurations for base users -- not really practical scenario and possibly will never be required return left(failure(format(CONFIG_COULD_NOT_BE_SHARED_WITH_BASE_USER, user))); - } else if (LINK_CONFIG_TITLE.equals(obtainTitleFrom(savedConfigOptForOtherUser.get().getTitle(), SAVED_CENTRE_NAME, device()))) { + } else if (LINK_CONFIG_TITLE.equals(obtainTitleFrom(savedConfigOptForOtherUser.get().getTitle(), SAVED_CENTRE_NAME, device))) { // link-configs can not be shared anywhere neither from base user nor from base/non-base user that gave its uuid as part of sharing process return left(failure(LINK_CONFIG_COULD_NOT_BE_SHARED)); } @@ -265,10 +282,21 @@ private Either validateUuidAndGetUpstreamConfig(fina /// Implements the first-time loading of a configuration into `inherited from base / shared`. /// - private T2, Boolean> firstTimeLoadingFrom(final EntityCentreConfig upstreamConfig) { + public static T2, Boolean> firstTimeLoadingFrom( + final EntityCentreConfig upstreamConfig, + final Class> miType, + final DeviceProfile device, + final EntityCentreConfigCo eccCompanion, + final User user, + final IWebUiConfig webUiConfig, + final MainMenuItemCo mmiCompanion, + final IUser userCompanion, + final ICentreConfigSharingModel sharingModel, + final ICompanionObjectFinder companionFinder + ) { final String configUuid = upstreamConfig.getConfigUuid(); final User upstreamConfigCreator = upstreamConfig.getOwner(); - final String preliminarySaveAsName = obtainTitleFrom(upstreamConfig.getTitle(), SAVED_CENTRE_NAME, device()); + final String preliminarySaveAsName = obtainTitleFrom(upstreamConfig.getTitle(), SAVED_CENTRE_NAME, device); final Optional actualSaveAsName; if (upstreamConfigCreator.isBase() && areEqual(upstreamConfigCreator, user.getBasedOnUser() /*id-only-proxy*/)) { // we have base => basedOn relationship between current user and the creator of savedConfig; @@ -276,25 +304,25 @@ private T2, Boolean> firstTimeLoadingFrom(final EntityCentreCon // CentreUpdater.updateCentre and .updateDifferences method should take care of that process; // at least FRESH config should be prepared -- making it preferred requires existence actualSaveAsName = of(preliminarySaveAsName); - updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); return t2(actualSaveAsName, false); } else { // if current user does not have access to shared configuration then sharing process should be prevented sharingModel.isSharedWith(configUuid, user).ifFailure(Result::throwRuntime); // current user gets uuid as part of sharing process from other base/non-base user; // need to determine non-conflicting name for current user from preliminarySaveAsName - actualSaveAsName = of(determineNonConflictingName(preliminarySaveAsName, -1)); + actualSaveAsName = of(determineNonConflictingName(preliminarySaveAsName, -1, miType, device, eccCompanion, user)); final Function>>>> createInheritedFromShared = surrogateName -> runAutomatically -> newDescription -> uuid -> saveNewEntityCentreManager( upstreamConfig.getConfigBody(), miType, user, - NAME_OF.apply(surrogateName).apply(actualSaveAsName).apply(device()), + NAME_OF.apply(surrogateName).apply(actualSaveAsName).apply(device), newDescription, eccCompanion, mmiCompanion, ecc -> uuid.map(ecc::setConfigUuid).orElse(ecc).setRunAutomatically(runAutomatically) ); - final EntityCentreConfig freshConfigForCreator = findConfigOptByUuid(configUuid, upstreamConfigCreator, miType, device(), FRESH_CENTRE_NAME, eccCompanion).get(); // need to retrieve FRESH config to get 'desc' -- that's because SAVED centres haven't stored descriptions, only FRESH do; this config must be present, otherwise savedConfigForOtherUser would not exist + final EntityCentreConfig freshConfigForCreator = findConfigOptByUuid(configUuid, upstreamConfigCreator, miType, device, FRESH_CENTRE_NAME, eccCompanion).get(); // need to retrieve FRESH config to get 'desc' -- that's because SAVED centres haven't stored descriptions, only FRESH do; this config must be present, otherwise savedConfigForOtherUser would not exist createInheritedFromShared.apply(FRESH_CENTRE_NAME).apply(freshConfigForCreator.isRunAutomatically()).apply(freshConfigForCreator.getDesc()).accept(of(configUuid)); // update (FRESH only) with upstream description and configUuid during creation createInheritedFromShared.apply(SAVED_CENTRE_NAME).apply(false).apply(null).accept(empty()); return t2(actualSaveAsName, true); @@ -308,15 +336,22 @@ private T2, Boolean> firstTimeLoadingFrom(final EntityCentreCon /// If that still conflicts, numeric suffixes `(shared 1)` through `(shared 9)` are attempted. /// If no unique name can be generated, an error is thrown. /// - private String determineNonConflictingName(final String preliminaryName, final int index) { + private static String determineNonConflictingName( + final String preliminaryName, + final int index, + final Class> miType, + final DeviceProfile device, + final EntityCentreConfigCo eccCompanion, + final User user + ) { final String name; if (index > 9) { throw failure(format(COULD_NOT_LOAD_CONFLICTING_SHARED_CONFIGURATION, preliminaryName)); } else { name = preliminaryName + (index == -1 ? "" : format(CONFLICTING_TITLE_SUFFIX, index == 0 ? "" : " " + index)); } - return findConfigOpt(miType, user, NAME_OF.apply(FRESH_CENTRE_NAME).apply(of(name)).apply(device()), eccCompanion, FETCH_CONFIG) - .map(conflictingConfig -> determineNonConflictingName(preliminaryName, index + 1)) + return findConfigOpt(miType, user, NAME_OF.apply(FRESH_CENTRE_NAME).apply(of(name)).apply(device), eccCompanion, FETCH_CONFIG) + .map(conflictingConfig -> determineNonConflictingName(preliminaryName, index + 1, miType, device, eccCompanion, user)) .orElse(name); } @@ -519,7 +554,12 @@ public static CriteriaIndication createChangedCriteriaIndication(final ICentreDo return !savedCentre.getFirstTick().selectionCriteriaEquals(freshCentre.getFirstTick()) ? CHANGED : NONE; } - private Result authoriseCriteriaEntity(final EnhancedCentreEntityQueryCriteria criteriaEntity) { + public static Result authoriseCriteriaEntity( + final EnhancedCentreEntityQueryCriteria criteriaEntity, + final Class> miType, + final IAuthorisationModel authorisationModel, + final ISecurityTokenProvider securityTokenProvider + ) { final var entityAuthorisationResult = authoriseReading(getEntityType(miType).getSimpleName(), READ, authorisationModel, securityTokenProvider); return entityAuthorisationResult.isSuccessful() ? authoriseCriteria(criteriaEntity.queryProperties.get(), authorisationModel, securityTokenProvider) @@ -590,7 +630,7 @@ public Representation put(final Representation envelope) { return restUtil.rawListJsonRepresentation(freshCentreAppliedCriteriaEntity, updateResultantCustomObject(freshCentreAppliedCriteriaEntity.centreDirtyCalculator(), miType, saveAsName, updatedFreshCentre, new LinkedHashMap<>(), of(criteriaIndication))); } - final Result authorisationResult = authoriseCriteriaEntity(freshCentreAppliedCriteriaEntity); + final Result authorisationResult = authoriseCriteriaEntity(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); if (!authorisationResult.isSuccessful()) { LOGGER.debug("CRITERIA_RESOURCE: run failed (authorisation validation failed)."); final var criteriaIndication = createCriteriaIndication((String) centreContextHolder.getModifHolder().get("@@wasRun"), updatedFreshCentre, miType, saveAsName, user, companionFinder, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion); @@ -644,7 +684,7 @@ public Representation put(final Representation envelope) { // Performs criteria validation on centre refresh / navigate. // It is needed if the user changed token role association between run and refresh actions. if (!isRunning) { - final Result authorisationResult = authoriseCriteriaEntity(previouslyRunCriteriaEntity); + final Result authorisationResult = authoriseCriteriaEntity(previouslyRunCriteriaEntity, miType, authorisationModel, securityTokenProvider); if (!authorisationResult.isSuccessful()) { LOGGER.debug("CRITERIA_RESOURCE: refresh failed (authorisation validation failed)."); return restUtil.resultJSONRepresentation( @@ -672,7 +712,7 @@ public Representation put(final Representation envelope) { ) ); if (isRunning) { - final var updatedSavedCentre = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder);; + final var updatedSavedCentre = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); final var changedCriteriaIndication = createChangedCriteriaIndication(updatedFreshCentre, updatedSavedCentre); updateResultantCustomObject(previouslyRunCriteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), miType, saveAsName, previouslyRunCentre, pair.getKey(), of(changedCriteriaIndication)); } diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java new file mode 100644 index 00000000000..f655ebb8a45 --- /dev/null +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -0,0 +1,300 @@ +package ua.com.fielden.platform.web.utils; + +import com.google.inject.Inject; +import ua.com.fielden.platform.criteria.generator.ICriteriaGenerator; +import ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler; +import ua.com.fielden.platform.dao.IEntityDao; +import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager; +import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer; +import ua.com.fielden.platform.entity.AbstractEntity; +import ua.com.fielden.platform.entity.factory.EntityFactory; +import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; +import ua.com.fielden.platform.entity.functional.centre.CentreContextHolder; +import ua.com.fielden.platform.entity.meta.MetaProperty; +import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; +import ua.com.fielden.platform.error.Result; +import ua.com.fielden.platform.security.IAuthorisationModel; +import ua.com.fielden.platform.security.provider.ISecurityTokenProvider; +import ua.com.fielden.platform.security.user.IUser; +import ua.com.fielden.platform.security.user.IUserProvider; +import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.types.Hyperlink; +import ua.com.fielden.platform.types.either.Either; +import ua.com.fielden.platform.types.tuples.T2; +import ua.com.fielden.platform.ui.config.EntityCentreConfig; +import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; +import ua.com.fielden.platform.ui.config.MainMenuItem; +import ua.com.fielden.platform.ui.config.MainMenuItemCo; +import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; +import ua.com.fielden.platform.utils.CollectionUtil; +import ua.com.fielden.platform.utils.Pair; +import ua.com.fielden.platform.web.app.IWebUiConfig; +import ua.com.fielden.platform.web.centre.CentreContext; +import ua.com.fielden.platform.web.centre.EntityCentre; +import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; +import ua.com.fielden.platform.web.interfaces.DeviceProfile; +import ua.com.fielden.platform.web.interfaces.IDeviceProvider; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Optional.of; +import static java.util.Optional.ofNullable; +import static org.apache.poi.hslf.usermodel.HSLFFontInfo.FontRenderType.device; +import static ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler.CRITERIA_ENTITY_ID; +import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; +import static ua.com.fielden.platform.data.generator.IGenerator.shouldForceRegeneration; +import static ua.com.fielden.platform.streaming.ValueCollectors.toLinkedHashMap; +import static ua.com.fielden.platform.types.either.Either.left; +import static ua.com.fielden.platform.types.tuples.T2.t2; +import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; +import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.findConfigOptByUuid; +import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; +import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.getEntityCentre; +import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; +import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; +import static ua.com.fielden.platform.web.utils.EntityResourceUtils.getOriginalManagedType; +import static ua.com.fielden.platform.web.utils.EntityResourceUtils.maybeVersion; + +public class EntityCentreAPIImpl implements EntityCentreAPI { + private final ICompanionObjectFinder companionFinder; + private final IUserProvider userProvider; +// private final IDeviceProvider deviceProvider; + private final ICriteriaGenerator critGenerator; + private final IWebUiConfig webUiConfig; + private final EntityFactory entityFactory; + private final ICentreConfigSharingModel sharingModel; + private final IAuthorisationModel authorisationModel; + private final ISecurityTokenProvider securityTokenProvider; + + @Inject + public EntityCentreAPIImpl( + final ICompanionObjectFinder companionFinder, + final IUserProvider userProvider, +// final IDeviceProvider deviceProvider, + final ICriteriaGenerator critGenerator, + final IWebUiConfig webUiConfig, + final EntityFactory entityFactory, + final ICentreConfigSharingModel sharingModel, + final IAuthorisationModel authorisationModel, + final ISecurityTokenProvider securityTokenProvider + ) { + this.companionFinder = companionFinder; + this.userProvider = userProvider; +// this.deviceProvider = deviceProvider; + this.critGenerator = critGenerator; + this.webUiConfig = webUiConfig; + this.entityFactory = entityFactory; + this.sharingModel = sharingModel; + this.authorisationModel = authorisationModel; + this.securityTokenProvider = securityTokenProvider; + } + + @Override + public , M extends EnhancedCentreEntityQueryCriteria>> Either, Hyperlink>> entityCentreResult( + Class miTypeGen, + String configUuid, + User user + ) { + + final String miTypeString = "fielden.main.menu.personnel.MiLeaveRequest"; + try { + miTypeGen = Class.forName(miTypeString); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + Class> miType = (Class>) miTypeGen; + + configUuid = "48b767df-bf45-48b7-932a-d2892eec47e8"; + + final var userName = "Troy.Plecas"; +// user = new User(); +// user.setKey(userName); + final IUser coUser = companionFinder.find(User.class, true); + user = coUser.findUser(userName); + + final User currentUser = userProvider.getUser(); + + try { + + userProvider.setUser(user); + + final CentreContextHolder centreContextHolder = null; + final Map customObject = mapOf(t2("@@action", RunActions.RUN.toString())); + + final boolean isRunning = true; + final boolean isSorting = false; + + final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); + final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); + final IUser userCompanion = companionFinder.find(User.class); + + // TODO + final var device = DeviceProfile.DESKTOP; + +// final EnhancedCentreEntityQueryCriteria freshCentreAppliedCriteriaEntity = createCriteriaEntityWithoutConflicts( +// centreContextHolder.getModifHolder(), +// companionFinder, +// critGenerator, +// miType, +// saveAsName, +// user, +// DeviceProfile.DESKTOP, +// webUiConfig, +// eccCompanion, +// mmiCompanion, +// userCompanion, +// sharingModel +// ); + + + // start loading of configuration defined by concrete uuid; + // we look only through [link, own save-as, inherited from base, inherited from shared] set of configurations; + // default configurations are excluded in the lookup; + // only FRESH kind are looked for; + final Optional freshConfigOpt = findConfigOptByUuid(configUuid, user, miType, device, FRESH_CENTRE_NAME, eccCompanion); + final Optional actualSaveAsName; + if (freshConfigOpt.isPresent()) { + // for current user we already have FRESH configuration with uuid loaded; + final Optional preliminarySaveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); + // updating is required from upstream configuration; + if (!LINK_CONFIG_TITLE.equals(preliminarySaveAsName.get())) { // (but not for link configuration); + actualSaveAsName = preliminarySaveAsName; // TODO updateFromUpstream(configUuid.get(), preliminarySaveAsName); + } else { + actualSaveAsName = preliminarySaveAsName; + } + } else { + // if there is no FRESH configuration then there are no [link, own save-as] configuration with the specified uuid; + // however there can exist [base, shared] config for other user with the specified uuid; + actualSaveAsName = firstTimeLoadingFrom( + validateUuidAndGetUpstreamConfig(configUuid, miType, device, eccCompanion, user).orElseThrow(Result::asRuntime), // TODO use Either here + miType, + device, + eccCompanion, + user, + webUiConfig, + mmiCompanion, + userCompanion, + sharingModel, + companionFinder + )._1; + } + final var saveAsName = actualSaveAsName; + + // load / update fresh centre if it is not loaded yet / stale + final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + + final M validationPrototype = createCriteriaValidationPrototype(miType, saveAsName, originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + + final M freshCentreAppliedCriteriaEntity = resetMetaStateForCriteriaValidationPrototype( + validationPrototype, + getOriginalManagedType(validationPrototype.getType(), originalCdtmae) + ); + + // There is a need to validate criteria entity with the check for 'required' properties. If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. + final Result validationResult = freshCentreAppliedCriteriaEntity.isValid(); + if (!validationResult.isSuccessful()) { + return left(validationResult); + } + + final Result authorisationResult = authoriseCriteriaEntity(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); + if (!authorisationResult.isSuccessful()) { + return left(authorisationResult); + } + + final EntityCentre> centre = getEntityCentre(miType.getName(), webUiConfig); + + // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) + // then for an entity centre configuration check if a generator was provided + final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); + final boolean generationShouldOccur = isRunning && !isSorting && createdByConstraintShouldOccur; + if (generationShouldOccur) { + // obtain the type for entities to be generated + final Class> generatorEntityType = (Class>) centre.getGeneratorTypes().get().getKey(); + + // create and execute a generator instance + final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); + final Map> params = freshCentreAppliedCriteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( + (final MetaProperty mp) -> mp.getName(), + (final MetaProperty mp) -> ofNullable(mp.getValue()))); + params.putAll(freshCentreAppliedCriteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); + if (shouldForceRegeneration(customObject)) { + params.put(FORCE_REGENERATION_KEY, of(true)); + } + final Result generationResult = generator.gen(generatorEntityType, params); + // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result + // otherwise, proceed with the request handling further to actually query the data + // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' + if (!generationResult.isSuccessful()) { + return left(generationResult); + } + } + + final ICentreDomainTreeManagerAndEnhancer updatedFreshCentre = freshCentreAppliedCriteriaEntity.getCentreDomainTreeMangerAndEnhancer(); + + commitCentreWithoutConflicts(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, updatedFreshCentre, null, webUiConfig, eccCompanion, mmiCompanion, userCompanion); + + final ICentreDomainTreeManagerAndEnhancer previouslyRunCentre = updateCentre(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final EnhancedCentreEntityQueryCriteria, ?> previouslyRunCriteriaEntity = createCriteriaValidationPrototype(miType, saveAsName, previouslyRunCentre, companionFinder, critGenerator, 0L, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + + final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( + customObject, + complementCriteriaEntityBeforeRunning( // complements previouslyRunCriteriaEntity instance + previouslyRunCriteriaEntity, + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + eccCompanion, + mmiCompanion, + userCompanion, + sharingModel + ) + ); + + Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( + centre, + centre.getCustomPropertiesDefinitions(), + centre.getCustomPropertiesAsignmentHandler(), + pair.getValue().stream()); + + final List list = new ArrayList<>(); +// list.add(isRunning ? previouslyRunCriteriaEntity : null); +// list.add(pair.getKey()); + + // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence + // FIXME Need to investigate why this is the case. + processedEntities.forEach(entity -> list.add((T) entity) ); + return Either.right(t2(list, new Hyperlink("https://192.168.1.40/#/Timesheets/Leave/Leave%20Request/48b767df-bf45-48b7-932a-d2892eec47e8"))); + + // // Build dynamic properties object +// final List>, Optional, ?>>>> resPropsWithContext = getDynamicResultProperties( +// centre, +// webUiConfig, +// companionFinder, +// user, +// critGenerator, +// entityFactory, +// centreContextHolder, +// previouslyRunCriteriaEntity, +// device, +// eccCompanion, +// mmiCompanion, +// userCompanion, +// sharingModel); +// +// //Enhance entities with values defined with consumer in each dynamic property. +// processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); +// //Enhance rendering hints with styles for each dynamic column. +// processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); + + + } finally { + userProvider.setUser(currentUser); + } + } + +} From 0a5af40e2dd93f091c21a67fd1155b20334c039d Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 6 Jan 2026 16:07:50 +0200 Subject: [PATCH 02/76] #2224 Temporary changes to make testing easier. --- .../platform/share/ShareActionProducer.java | 11 ++++++----- .../platform/web/utils/EntityCentreAPI.java | 4 ++-- .../web/utils/EntityCentreAPIImpl.java | 19 ++++++------------- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index 6548c86bc4e..c05dfdd0545 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -79,12 +79,13 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(hyperlink.value, PNG, 512, 512, 12, WHITE, BLACK))); } }); - } - final var result = entityCentreAPI.entityCentreResult(null, null, null); - result.asRight().value()._1.forEach(ent -> { - System.out.println(ent.get("details") + " " + ent.get("person") + " " + ent.get("date")); - }); + final var result = entityCentreAPI.entityCentreResult("fielden.main.menu.personnel.MiLeaveRequest", "48b767df-bf45-48b7-932a-d2892eec47e8", "Troy.Plecas"); // Daniel.Truong Troy.Plecas + + result.asRight().value()._1.forEach(ent -> { + System.out.println(ent.get("details") + " " + ent.get("person") + " " + ent.get("date")); + }); + } return super.provideDefaultValues(entity); } diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java index 2458346bfdc..91f72575cb0 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java @@ -15,9 +15,9 @@ public interface EntityCentreAPI { , M extends EnhancedCentreEntityQueryCriteria>> Either, Hyperlink>> entityCentreResult( - Class miTypeForStandaloneCentre, + String miTypeForStandaloneCentreName, String configUuid, - User user + String userName ); } diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index f655ebb8a45..adaab70021c 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -93,26 +93,19 @@ public EntityCentreAPIImpl( @Override public , M extends EnhancedCentreEntityQueryCriteria>> Either, Hyperlink>> entityCentreResult( - Class miTypeGen, - String configUuid, - User user + final String miTypeNameForStandaloneCentre, + final String configUuid, + final String userName ) { - - final String miTypeString = "fielden.main.menu.personnel.MiLeaveRequest"; + final Class miTypeGen; try { - miTypeGen = Class.forName(miTypeString); + miTypeGen = Class.forName(miTypeNameForStandaloneCentre); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } Class> miType = (Class>) miTypeGen; - - configUuid = "48b767df-bf45-48b7-932a-d2892eec47e8"; - - final var userName = "Troy.Plecas"; -// user = new User(); -// user.setKey(userName); final IUser coUser = companionFinder.find(User.class, true); - user = coUser.findUser(userName); + final User user = coUser.findUser(userName); final User currentUser = userProvider.getUser(); From 2e121fd2e7de15932267c63953de9224bd53c642 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 6 Jan 2026 16:11:28 +0200 Subject: [PATCH 03/76] #2224 Use saved version of [already updated from upstream] configuration for API running of the centre. Fresh version may contain user modifications, which must be left as is; but should not be taken into account for API running. --- .../platform/web/utils/EntityCentreAPIImpl.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index adaab70021c..98788286475 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -176,22 +176,23 @@ public EntityCentreAPIImpl( final var saveAsName = actualSaveAsName; // load / update fresh centre if it is not loaded yet / stale - final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); final M validationPrototype = createCriteriaValidationPrototype(miType, saveAsName, originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); - final M freshCentreAppliedCriteriaEntity = resetMetaStateForCriteriaValidationPrototype( + final M savedCriteriaEntity = resetMetaStateForCriteriaValidationPrototype( validationPrototype, getOriginalManagedType(validationPrototype.getType(), originalCdtmae) ); // There is a need to validate criteria entity with the check for 'required' properties. If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. - final Result validationResult = freshCentreAppliedCriteriaEntity.isValid(); + final Result validationResult = savedCriteriaEntity.isValid(); if (!validationResult.isSuccessful()) { return left(validationResult); } - final Result authorisationResult = authoriseCriteriaEntity(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); + final Result authorisationResult = authoriseCriteriaEntity(savedCriteriaEntity, miType, authorisationModel, securityTokenProvider); if (!authorisationResult.isSuccessful()) { return left(authorisationResult); } @@ -208,11 +209,11 @@ public EntityCentreAPIImpl( // create and execute a generator instance final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); - final Map> params = freshCentreAppliedCriteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( + final Map> params = savedCriteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( (final MetaProperty mp) -> mp.getName(), (final MetaProperty mp) -> ofNullable(mp.getValue()))); - params.putAll(freshCentreAppliedCriteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); - if (shouldForceRegeneration(customObject)) { + params.putAll(savedCriteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); + if (shouldForceRegeneration(customObject)) { // TODO always force regeneration params.put(FORCE_REGENERATION_KEY, of(true)); } final Result generationResult = generator.gen(generatorEntityType, params); From e92667e6282955ed7a8b9574db28bf9437eab9a9 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 6 Jan 2026 16:14:37 +0200 Subject: [PATCH 04/76] #2224 Avoid PREVIOUS_RUN surrogate centre alteration during API running. Such alteration will affect UI configurations, which should be avoided. P.S. Generator-like centres will still affect result-sets. This is considered acceptable. But should be revised / tested later. --- .../platform/web/utils/EntityCentreAPIImpl.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 98788286475..79b3c7ba807 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -225,17 +225,17 @@ public EntityCentreAPIImpl( } } - final ICentreDomainTreeManagerAndEnhancer updatedFreshCentre = freshCentreAppliedCriteriaEntity.getCentreDomainTreeMangerAndEnhancer(); + //final ICentreDomainTreeManagerAndEnhancer updatedSavedCentre = savedCriteriaEntity.getCentreDomainTreeMangerAndEnhancer(); - commitCentreWithoutConflicts(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, updatedFreshCentre, null, webUiConfig, eccCompanion, mmiCompanion, userCompanion); + //commitCentreWithoutConflicts(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, updatedSavedCentre, null, webUiConfig, eccCompanion, mmiCompanion, userCompanion); - final ICentreDomainTreeManagerAndEnhancer previouslyRunCentre = updateCentre(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - final EnhancedCentreEntityQueryCriteria, ?> previouslyRunCriteriaEntity = createCriteriaValidationPrototype(miType, saveAsName, previouslyRunCentre, companionFinder, critGenerator, 0L, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + //final ICentreDomainTreeManagerAndEnhancer previouslyRunCentre = updateCentre(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + //final EnhancedCentreEntityQueryCriteria, ?> previouslyRunCriteriaEntity = createCriteriaValidationPrototype(miType, saveAsName, previouslyRunCentre, companionFinder, critGenerator, 0L, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( customObject, complementCriteriaEntityBeforeRunning( // complements previouslyRunCriteriaEntity instance - previouslyRunCriteriaEntity, + savedCriteriaEntity, webUiConfig, companionFinder, user, From d3edfcf0cdab220e6da5127b718603962a8916ef Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 6 Jan 2026 21:35:07 +0200 Subject: [PATCH 05/76] #2224 Ensure repeated API running with up-to-date configuration changes taken from upstream. 'updateFromUpstream' has side effects and these side effects are required. --- .../web/resources/webui/CriteriaResource.java | 52 +++++++++++++------ .../web/utils/EntityCentreAPIImpl.java | 2 +- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 1508235fd95..9a3cc274002 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -204,7 +204,7 @@ public Representation get() { final Optional preliminarySaveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device())); // updating is required from upstream configuration; if (!LINK_CONFIG_TITLE.equals(preliminarySaveAsName.get())) { // (but not for link configuration); - actualSaveAsNameAndSharedIndicator = updateFromUpstream(configUuid.get(), preliminarySaveAsName); + actualSaveAsNameAndSharedIndicator = updateFromUpstream(configUuid.get(), preliminarySaveAsName, miType, device(), eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder); } else { actualSaveAsNameAndSharedIndicator = t2(preliminarySaveAsName, false); } @@ -236,7 +236,7 @@ public Representation get() { final Optional preliminarySaveAsName = retrievePreferredConfigName(user, miType, device(), companionFinder, webUiConfig); // preferred configuration should be loaded resolvedConfigUuid = updateCentreConfigUuid(user, miType, preliminarySaveAsName, device(), eccCompanion); if (resolvedConfigUuid.isPresent()) { // preferred config can be inherited from base / shared (link configs can not be preferred, no need to check it here) - actualSaveAsName = updateFromUpstream(resolvedConfigUuid.get(), preliminarySaveAsName)._1; // it needs updating from upstream -- only for the configs that has configUuid aka non-default + actualSaveAsName = updateFromUpstream(resolvedConfigUuid.get(), preliminarySaveAsName, miType, device(), eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder)._1; // it needs updating from upstream -- only for the configs that has configUuid aka non-default } else { actualSaveAsName = preliminarySaveAsName; } @@ -358,9 +358,21 @@ private static String determineNonConflictingName( /// Updates a configuration already loaded by the `user` with the concrete `configUuid` /// from its upstream configuration, if it is inherited. /// - private T2, Boolean> updateFromUpstream(final String configUuid, final Optional saveAsName) { + public static T2, Boolean> updateFromUpstream( + final String configUuid, + final Optional saveAsName, + final Class> miType, + final DeviceProfile device, + final EntityCentreConfigCo eccCompanion, + final User user, + final IWebUiConfig webUiConfig, + final MainMenuItemCo mmiCompanion, + final IUser userCompanion, + final ICentreConfigSharingModel sharingModel, + final ICompanionObjectFinder companionFinder + ) { // look for config creator - final Optional savedConfigOpt = findConfigOptByUuid(configUuid, miType, device(), SAVED_CENTRE_NAME, eccCompanion); + final Optional savedConfigOpt = findConfigOptByUuid(configUuid, miType, device, SAVED_CENTRE_NAME, eccCompanion); if (savedConfigOpt.isPresent()) { // the creator is current user or other final EntityCentreConfig savedConfig = savedConfigOpt.get(); @@ -369,20 +381,20 @@ private T2, Boolean> updateFromUpstream(final String configUuid // current user didn't create this config -> it is inherited and needs updating if (savedConfigCreator.isBase() && areEqual(savedConfigCreator, user.getBasedOnUser() /*id-only-proxy*/)) { // inherited from base - if (isCentreChanged(saveAsName)) { // if there are some user changes, only SAVED surrogate must be updated; if such centre will be discarded the base user changes will be loaded immediately - removeCentres(user, miType, device(), saveAsName, eccCompanion, SAVED_CENTRE_NAME); + if (isCentreChanged(saveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, companionFinder)) { // if there are some user changes, only SAVED surrogate must be updated; if such centre will be discarded the base user changes will be loaded immediately + removeCentres(user, miType, device, saveAsName, eccCompanion, SAVED_CENTRE_NAME); } else { // otherwise base user changes will be loaded immediately after centre loading - removeCentres(user, miType, device(), saveAsName, eccCompanion, FRESH_CENTRE_NAME, SAVED_CENTRE_NAME); + removeCentres(user, miType, device, saveAsName, eccCompanion, FRESH_CENTRE_NAME, SAVED_CENTRE_NAME); } - updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); // do not leave only FRESH centre out of two (FRESH + SAVED) => update SAVED centre explicitly + updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); // do not leave only FRESH centre out of two (FRESH + SAVED) => update SAVED centre explicitly - makePreferred(user, miType, saveAsName, device(), companionFinder, webUiConfig); // inherited from base always gets preferred on loading; must leave it preferred after deletion + makePreferred(user, miType, saveAsName, device, companionFinder, webUiConfig); // inherited from base always gets preferred on loading; must leave it preferred after deletion } else { if (sharingModel.isSharedWith(configUuid, user).isSuccessful()) { // inherited from shared - updateInheritedFromShared(savedConfig, miType, device(), saveAsName, user, eccCompanion, of(() -> isCentreChanged(saveAsName))); - return t2(of(obtainTitleFrom(savedConfig.getTitle(), SAVED_CENTRE_NAME, device())), true); + updateInheritedFromShared(savedConfig, miType, device, saveAsName, user, eccCompanion, of(() -> isCentreChanged(saveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, companionFinder))); + return t2(of(obtainTitleFrom(savedConfig.getTitle(), SAVED_CENTRE_NAME, device)), true); } // already loaded inherited from shared config was made unshared; the inherited from shared configuration now acts like own save-as configuration } } // if the current user is creator then no 'updating from upstream' is needed -- it is own save-as @@ -423,10 +435,20 @@ private T2, Optional> prepareLinkConfigInfrastructure() /// Determines whether the FRESH configuration has been modified compared to the SAVED one. /// - private boolean isCentreChanged(final Optional actualSaveAsName) { + private static boolean isCentreChanged( + final Optional actualSaveAsName, + final Class> miType, + final DeviceProfile device, + final EntityCentreConfigCo eccCompanion, + final User user, + final IWebUiConfig webUiConfig, + final MainMenuItemCo mmiCompanion, + final IUser userCompanion, + final ICompanionObjectFinder companionFinder + ) { return isFreshCentreChanged( - updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder), - updateCentre(user, miType, SAVED_CENTRE_NAME, actualSaveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder) + updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder), + updateCentre(user, miType, SAVED_CENTRE_NAME, actualSaveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder) ); } diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 79b3c7ba807..1a482da5539 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -153,7 +153,7 @@ public EntityCentreAPIImpl( final Optional preliminarySaveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); // updating is required from upstream configuration; if (!LINK_CONFIG_TITLE.equals(preliminarySaveAsName.get())) { // (but not for link configuration); - actualSaveAsName = preliminarySaveAsName; // TODO updateFromUpstream(configUuid.get(), preliminarySaveAsName); + actualSaveAsName = updateFromUpstream(configUuid, preliminarySaveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder)._1; } else { actualSaveAsName = preliminarySaveAsName; } From 666c0a8a670d3bad1a77a24b97caaa6d36eb4bf6 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 6 Jan 2026 22:06:42 +0200 Subject: [PATCH 06/76] #2224 Prohibit links configs from API running. These are only a placeholders for some parameterised Entity Centre links. We only care about saved named configurations of concrete users. --- .../platform/share/ShareActionProducer.java | 16 +++++++++++----- .../platform/web/utils/EntityCentreAPIImpl.java | 4 ++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index c05dfdd0545..66ffb38c495 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -80,11 +80,17 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { } }); - final var result = entityCentreAPI.entityCentreResult("fielden.main.menu.personnel.MiLeaveRequest", "48b767df-bf45-48b7-932a-d2892eec47e8", "Troy.Plecas"); // Daniel.Truong Troy.Plecas - - result.asRight().value()._1.forEach(ent -> { - System.out.println(ent.get("details") + " " + ent.get("person") + " " + ent.get("date")); - }); + final var result = entityCentreAPI.entityCentreResult("fielden.main.menu.personnel.MiLeaveRequest", "05ca9ad5-ecb3-4ee1-b8af-a56cc5e12131", "Daniel.Truong"); // Daniel.Truong Troy.Plecas + System.out.println("----------- API Execution (...) ------------"); + if (result.isRight()) { + result.asRight().value()._1.forEach(ent -> { + System.out.println(ent.get("details") + " " + ent.get("person") + " " + ent.get("date")); + }); + } + else { + System.out.println(result.asLeft().value().getMessage()); + } + System.out.println("----------- API Execution (end) ------------"); } return super.provideDefaultValues(entity); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 1a482da5539..40a0bef6005 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -45,6 +45,7 @@ import static ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler.CRITERIA_ENTITY_ID; import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; import static ua.com.fielden.platform.data.generator.IGenerator.shouldForceRegeneration; +import static ua.com.fielden.platform.error.Result.failure; import static ua.com.fielden.platform.streaming.ValueCollectors.toLinkedHashMap; import static ua.com.fielden.platform.types.either.Either.left; import static ua.com.fielden.platform.types.tuples.T2.t2; @@ -173,6 +174,9 @@ public EntityCentreAPIImpl( companionFinder )._1; } + if (!actualSaveAsName.isPresent() || LINK_CONFIG_TITLE.equals(actualSaveAsName.get())) { + return left(failure("Default / Link configs are not available for API running (%s).".formatted(actualSaveAsName))); + } final var saveAsName = actualSaveAsName; // load / update fresh centre if it is not loaded yet / stale From f9bb5470babf0b1caed6fa34033b2622e302b4f4 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 2 Feb 2026 18:50:29 +0200 Subject: [PATCH 07/76] #2224 Adjust as per simplified issue requirements. a. Reduce API - 1) only for owner; 2) no special hyperlinks. b. Always run fresh configuration, i.e. configuration with unsaved changes. --- .../platform/share/ShareActionProducer.java | 4 +- .../platform/web/utils/EntityCentreAPI.java | 5 +- .../web/utils/EntityCentreAPIImpl.java | 69 ++++++++----------- 3 files changed, 31 insertions(+), 47 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index 66ffb38c495..485d90a2527 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -80,10 +80,10 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { } }); - final var result = entityCentreAPI.entityCentreResult("fielden.main.menu.personnel.MiLeaveRequest", "05ca9ad5-ecb3-4ee1-b8af-a56cc5e12131", "Daniel.Truong"); // Daniel.Truong Troy.Plecas + final var result = entityCentreAPI.entityCentreResult("fielden.main.menu.personnel.MiLeaveRequest", "48b767df-bf45-48b7-932a-d2892eec47e8"); // Daniel.Truong Troy.Plecas System.out.println("----------- API Execution (...) ------------"); if (result.isRight()) { - result.asRight().value()._1.forEach(ent -> { + result.asRight().value().forEach(ent -> { System.out.println(ent.get("details") + " " + ent.get("person") + " " + ent.get("date")); }); } diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java index 91f72575cb0..8b26adb9ea3 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java @@ -14,10 +14,9 @@ public interface EntityCentreAPI { , M extends EnhancedCentreEntityQueryCriteria>> - Either, Hyperlink>> entityCentreResult( + Either> entityCentreResult( String miTypeForStandaloneCentreName, - String configUuid, - String userName + String configUuid ); } diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 40a0bef6005..610a38fa016 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -93,10 +93,9 @@ public EntityCentreAPIImpl( } @Override - public , M extends EnhancedCentreEntityQueryCriteria>> Either, Hyperlink>> entityCentreResult( + public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( final String miTypeNameForStandaloneCentre, - final String configUuid, - final String userName + final String configUuid ) { final Class miTypeGen; try { @@ -106,7 +105,14 @@ public EntityCentreAPIImpl( } Class> miType = (Class>) miTypeGen; final IUser coUser = companionFinder.find(User.class, true); - final User user = coUser.findUser(userName); + final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); + // TODO + final var device = DeviceProfile.DESKTOP; + final Optional freshConfigOpt = findConfigOptByUuid(configUuid, miType, device, FRESH_CENTRE_NAME, eccCompanion); + if (!freshConfigOpt.isPresent()) { + return left(Result.failure("Config with uuid %s does not exist.".formatted(configUuid))); + } + final User user = coUser.findUser(freshConfigOpt.get().getOwner().getKey()); final User currentUser = userProvider.getUser(); @@ -120,12 +126,9 @@ public EntityCentreAPIImpl( final boolean isRunning = true; final boolean isSorting = false; - final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); final IUser userCompanion = companionFinder.find(User.class); - // TODO - final var device = DeviceProfile.DESKTOP; // final EnhancedCentreEntityQueryCriteria freshCentreAppliedCriteriaEntity = createCriteriaEntityWithoutConflicts( // centreContextHolder.getModifHolder(), @@ -147,56 +150,38 @@ public EntityCentreAPIImpl( // we look only through [link, own save-as, inherited from base, inherited from shared] set of configurations; // default configurations are excluded in the lookup; // only FRESH kind are looked for; - final Optional freshConfigOpt = findConfigOptByUuid(configUuid, user, miType, device, FRESH_CENTRE_NAME, eccCompanion); final Optional actualSaveAsName; - if (freshConfigOpt.isPresent()) { - // for current user we already have FRESH configuration with uuid loaded; - final Optional preliminarySaveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); - // updating is required from upstream configuration; - if (!LINK_CONFIG_TITLE.equals(preliminarySaveAsName.get())) { // (but not for link configuration); - actualSaveAsName = updateFromUpstream(configUuid, preliminarySaveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder)._1; - } else { - actualSaveAsName = preliminarySaveAsName; - } - } else { - // if there is no FRESH configuration then there are no [link, own save-as] configuration with the specified uuid; - // however there can exist [base, shared] config for other user with the specified uuid; - actualSaveAsName = firstTimeLoadingFrom( - validateUuidAndGetUpstreamConfig(configUuid, miType, device, eccCompanion, user).orElseThrow(Result::asRuntime), // TODO use Either here - miType, - device, - eccCompanion, - user, - webUiConfig, - mmiCompanion, - userCompanion, - sharingModel, - companionFinder - )._1; - } + // for current user we already have FRESH configuration with uuid loaded; + final Optional preliminarySaveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); +// // updating is required from upstream configuration; +// if (!LINK_CONFIG_TITLE.equals(preliminarySaveAsName.get())) { // (but not for link configuration); +// actualSaveAsName = updateFromUpstream(configUuid, preliminarySaveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder)._1; +// } else { + actualSaveAsName = preliminarySaveAsName; +// } if (!actualSaveAsName.isPresent() || LINK_CONFIG_TITLE.equals(actualSaveAsName.get())) { return left(failure("Default / Link configs are not available for API running (%s).".formatted(actualSaveAsName))); } final var saveAsName = actualSaveAsName; // load / update fresh centre if it is not loaded yet / stale - updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + //final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); final M validationPrototype = createCriteriaValidationPrototype(miType, saveAsName, originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); - final M savedCriteriaEntity = resetMetaStateForCriteriaValidationPrototype( + final M freshCriteriaEntity = resetMetaStateForCriteriaValidationPrototype( validationPrototype, getOriginalManagedType(validationPrototype.getType(), originalCdtmae) ); // There is a need to validate criteria entity with the check for 'required' properties. If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. - final Result validationResult = savedCriteriaEntity.isValid(); + final Result validationResult = freshCriteriaEntity.isValid(); if (!validationResult.isSuccessful()) { return left(validationResult); } - final Result authorisationResult = authoriseCriteriaEntity(savedCriteriaEntity, miType, authorisationModel, securityTokenProvider); + final Result authorisationResult = authoriseCriteriaEntity(freshCriteriaEntity, miType, authorisationModel, securityTokenProvider); if (!authorisationResult.isSuccessful()) { return left(authorisationResult); } @@ -213,10 +198,10 @@ public EntityCentreAPIImpl( // create and execute a generator instance final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); - final Map> params = savedCriteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( + final Map> params = freshCriteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( (final MetaProperty mp) -> mp.getName(), (final MetaProperty mp) -> ofNullable(mp.getValue()))); - params.putAll(savedCriteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); + params.putAll(freshCriteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); if (shouldForceRegeneration(customObject)) { // TODO always force regeneration params.put(FORCE_REGENERATION_KEY, of(true)); } @@ -239,7 +224,7 @@ public EntityCentreAPIImpl( final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( customObject, complementCriteriaEntityBeforeRunning( // complements previouslyRunCriteriaEntity instance - savedCriteriaEntity, + freshCriteriaEntity, webUiConfig, companionFinder, user, @@ -266,7 +251,7 @@ public EntityCentreAPIImpl( // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence // FIXME Need to investigate why this is the case. processedEntities.forEach(entity -> list.add((T) entity) ); - return Either.right(t2(list, new Hyperlink("https://192.168.1.40/#/Timesheets/Leave/Leave%20Request/48b767df-bf45-48b7-932a-d2892eec47e8"))); + return Either.right(list); // // Build dynamic properties object // final List>, Optional, ?>>>> resPropsWithContext = getDynamicResultProperties( From fe8cb6f1b30fa6d563c1b9f6b5e2535af537bbc3 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 2 Feb 2026 23:34:28 +0200 Subject: [PATCH 08/76] #2224 Preliminary change to find owner's fresh centre for API running. --- .../platform/web/utils/EntityCentreAPIImpl.java | 13 ++++++++++++- .../fielden/platform/web/centre/CentreUpdater.java | 4 ++-- .../platform/web/centre/CentreUpdaterUtils.java | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 610a38fa016..8497c4da550 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -108,7 +108,18 @@ public EntityCentreAPIImpl( final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); // TODO final var device = DeviceProfile.DESKTOP; - final Optional freshConfigOpt = findConfigOptByUuid(configUuid, miType, device, FRESH_CENTRE_NAME, eccCompanion); + final Optional freshConfigOpt = findConfigOptByUuid( + centreConfigQueryFor(miType, device, FRESH_CENTRE_NAME) + .and().exists( + centreConfigQueryFor(miType, device, SAVED_CENTRE_NAME) + .and().prop("configUuid").eq().val(configUuid) + .and().prop("owner").eq().extProp("e.owner") + .model() + ), + configUuid, + eccCompanion + ); + //findConfigOptByUuid(configUuid, miType, device, FRESH_CENTRE_NAME, eccCompanion); if (!freshConfigOpt.isPresent()) { return left(Result.failure("Config with uuid %s does not exist.".formatted(configUuid))); } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java index 736864060df..4bb0bffa95b 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java @@ -779,8 +779,8 @@ private static String obtainTitleFrom(final String title, final String surrogate * @param surrogateName -- surrogate name of the centre (fresh, previouslyRun etc.) * @return */ - static ICompoundCondition0 centreConfigQueryFor(final Class> miType, final DeviceProfile device, final String surrogateName) { - return select(EntityCentreConfig.class).where() + public static ICompoundCondition0 centreConfigQueryFor(final Class> miType, final DeviceProfile device, final String surrogateName) { + return select(EntityCentreConfig.class).as("e").where() .prop("title").like().val(deviceSpecific(surrogateName, device) + "[%") .and().prop("title").notLike().val(deviceSpecific(surrogateName, opposite(device)) + "[%") .and().prop("menuItem.key").eq().val(miType.getName()); diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java index 16606598a5f..cd9a4b28257 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java @@ -228,7 +228,7 @@ public static Optional findConfigOpt(final Class miType, /** * Finds optional configuration for {@code model} and {@code uuid} with predefined fetch model, sufficient for most situations. */ - private static Optional findConfigOptByUuid(final ICompoundCondition0 model, final String uuid, final EntityCentreConfigCo eccCompanion) { + public static Optional findConfigOptByUuid(final ICompoundCondition0 model, final String uuid, final EntityCentreConfigCo eccCompanion) { return eccCompanion.getEntityOptional(from(model .and().prop("configUuid").eq().val(uuid).model() ).with(fetchWithKeyAndDesc(EntityCentreConfig.class, true).with("preferred").with("configUuid").with("owner.base").with("configBody").with("runAutomatically").fetchModel()).model()); From df5c0183f7c4ebbc4d50611a1c7fc93a89fc2824 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 3 Feb 2026 18:29:07 +0200 Subject: [PATCH 09/76] #2224 Automatically determine miType from config UUID to reduce API complexity. * Reuse utility functions as much as possible. * Reduce implementation complexity by separating EQL model code from actual querying. * Minor simplifications around fetching. --- .../platform/share/ShareActionProducer.java | 2 +- .../platform/web/utils/EntityCentreAPI.java | 1 - .../web/utils/EntityCentreAPIImpl.java | 50 ++++++++----------- .../platform/web/centre/CentreUpdater.java | 49 +++++++++++++----- .../web/centre/CentreUpdaterUtils.java | 16 +++--- 5 files changed, 69 insertions(+), 49 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index 485d90a2527..cf77516bb81 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -80,7 +80,7 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { } }); - final var result = entityCentreAPI.entityCentreResult("fielden.main.menu.personnel.MiLeaveRequest", "48b767df-bf45-48b7-932a-d2892eec47e8"); // Daniel.Truong Troy.Plecas + final var result = entityCentreAPI.entityCentreResult("48b767df-bf45-48b7-932a-d2892eec47e8"); // Daniel.Truong Troy.Plecas System.out.println("----------- API Execution (...) ------------"); if (result.isRight()) { result.asRight().value().forEach(ent -> { diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java index 8b26adb9ea3..7f60ae91202 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java @@ -15,7 +15,6 @@ public interface EntityCentreAPI { , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( - String miTypeForStandaloneCentreName, String configUuid ); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 8497c4da550..d082d84f72d 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -2,9 +2,7 @@ import com.google.inject.Inject; import ua.com.fielden.platform.criteria.generator.ICriteriaGenerator; -import ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler; import ua.com.fielden.platform.dao.IEntityDao; -import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager; import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.entity.factory.EntityFactory; @@ -18,22 +16,17 @@ import ua.com.fielden.platform.security.user.IUser; import ua.com.fielden.platform.security.user.IUserProvider; import ua.com.fielden.platform.security.user.User; -import ua.com.fielden.platform.types.Hyperlink; import ua.com.fielden.platform.types.either.Either; -import ua.com.fielden.platform.types.tuples.T2; import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; import ua.com.fielden.platform.ui.config.MainMenuItem; import ua.com.fielden.platform.ui.config.MainMenuItemCo; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; -import ua.com.fielden.platform.utils.CollectionUtil; import ua.com.fielden.platform.utils.Pair; import ua.com.fielden.platform.web.app.IWebUiConfig; -import ua.com.fielden.platform.web.centre.CentreContext; import ua.com.fielden.platform.web.centre.EntityCentre; import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; import ua.com.fielden.platform.web.interfaces.DeviceProfile; -import ua.com.fielden.platform.web.interfaces.IDeviceProvider; import java.util.*; import java.util.stream.Collectors; @@ -41,22 +34,22 @@ import static java.util.Optional.of; import static java.util.Optional.ofNullable; -import static org.apache.poi.hslf.usermodel.HSLFFontInfo.FontRenderType.device; import static ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler.CRITERIA_ENTITY_ID; import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; import static ua.com.fielden.platform.data.generator.IGenerator.shouldForceRegeneration; +import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.from; import static ua.com.fielden.platform.error.Result.failure; import static ua.com.fielden.platform.streaming.ValueCollectors.toLinkedHashMap; import static ua.com.fielden.platform.types.either.Either.left; import static ua.com.fielden.platform.types.tuples.T2.t2; import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; +import static ua.com.fielden.platform.utils.EntityUtils.fetchWithKeyAndDesc; import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.findConfigOptByUuid; import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.getEntityCentre; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; import static ua.com.fielden.platform.web.utils.EntityResourceUtils.getOriginalManagedType; -import static ua.com.fielden.platform.web.utils.EntityResourceUtils.maybeVersion; public class EntityCentreAPIImpl implements EntityCentreAPI { private final ICompanionObjectFinder companionFinder; @@ -94,36 +87,37 @@ public EntityCentreAPIImpl( @Override public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( - final String miTypeNameForStandaloneCentre, final String configUuid ) { - final Class miTypeGen; - try { - miTypeGen = Class.forName(miTypeNameForStandaloneCentre); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - Class> miType = (Class>) miTypeGen; final IUser coUser = companionFinder.find(User.class, true); final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); // TODO final var device = DeviceProfile.DESKTOP; - final Optional freshConfigOpt = findConfigOptByUuid( - centreConfigQueryFor(miType, device, FRESH_CENTRE_NAME) - .and().exists( - centreConfigQueryFor(miType, device, SAVED_CENTRE_NAME) - .and().prop("configUuid").eq().val(configUuid) - .and().prop("owner").eq().extProp("e.owner") - .model() - ), - configUuid, - eccCompanion + final Optional freshConfigOpt = eccCompanion.getEntityOptional( + from(centreConfigQueryFor(configUuid, device, FRESH_CENTRE_NAME) + .and().exists( + centreConfigQueryFor(configUuid, device, SAVED_CENTRE_NAME) + .and().prop("owner").eq().extProp("e.owner") + .model() + ).model()) + .with( + fetchWithKeyAndDesc(EntityCentreConfig.class, true) + .with("owner", "menuItem", "title") + .fetchModel() + ).model() ); - //findConfigOptByUuid(configUuid, miType, device, FRESH_CENTRE_NAME, eccCompanion); + if (!freshConfigOpt.isPresent()) { return left(Result.failure("Config with uuid %s does not exist.".formatted(configUuid))); } final User user = coUser.findUser(freshConfigOpt.get().getOwner().getKey()); + final Class miTypeGen; + try { + miTypeGen = Class.forName(freshConfigOpt.get().getMenuItem().getKey()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + Class> miType = (Class>) miTypeGen; final User currentUser = userProvider.getUser(); diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java index 4bb0bffa95b..56c83dee87c 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java @@ -10,6 +10,7 @@ import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompoundCondition0; import ua.com.fielden.platform.entity.query.fluent.fetch; +import ua.com.fielden.platform.entity.query.model.ConditionModel; import ua.com.fielden.platform.entity.query.model.EntityResultQueryModel; import ua.com.fielden.platform.entity_centre.mnemonics.DateRangePrefixEnum; import ua.com.fielden.platform.entity_centre.mnemonics.MnemonicEnum; @@ -768,7 +769,27 @@ private static String obtainTitleFrom(final String title, final String surrogate final String surrogateWithSuffix = title.replaceFirst(surrogateNamePrefix, ""); return surrogateWithSuffix.substring(1, surrogateWithSuffix.lastIndexOf("]")); } - + + private static ICompoundCondition0 centreConfigQueryFor(final DeviceProfile device, final String surrogateName) { + return select(EntityCentreConfig.class).as("e") + .where().prop("title").like().val(deviceSpecific(surrogateName, device) + "[%") + .and().prop("title").notLike().val(deviceSpecific(surrogateName, opposite(device)) + "[%"); + } + + public static ICompoundCondition0 centreConfigQueryFor(final String uuid, final DeviceProfile device, final String surrogateName) { + return centreConfigQueryFor(device, surrogateName) + .and().condition(centreConfigCondFor(uuid)); + } + + public static ICompoundCondition0 centreConfigQueryFor(final String uuid, final Class> miType, final DeviceProfile device, final String surrogateName) { + return centreConfigQueryFor(uuid, device, surrogateName) + .and().condition(centreConfigCondFor(miType)); + } + + static ConditionModel centreConfigCondFor(final String uuid) { + return cond().prop("configUuid").eq().val(uuid).model(); + } + /** * Creates a function that returns a query to find centre configurations persisted. *

@@ -779,13 +800,15 @@ private static String obtainTitleFrom(final String title, final String surrogate * @param surrogateName -- surrogate name of the centre (fresh, previouslyRun etc.) * @return */ - public static ICompoundCondition0 centreConfigQueryFor(final Class> miType, final DeviceProfile device, final String surrogateName) { - return select(EntityCentreConfig.class).as("e").where() - .prop("title").like().val(deviceSpecific(surrogateName, device) + "[%") - .and().prop("title").notLike().val(deviceSpecific(surrogateName, opposite(device)) + "[%") - .and().prop("menuItem.key").eq().val(miType.getName()); + static ICompoundCondition0 centreConfigQueryFor(final Class> miType, final DeviceProfile device, final String surrogateName) { + return centreConfigQueryFor(device, surrogateName) + .and().condition(centreConfigCondFor(miType)) ; } - + + private static ConditionModel centreConfigCondFor(Class> miType) { + return cond().prop("menuItem.key").eq().val(miType.getName()).model(); + } + /** * Creates a function that returns a query to find centre configurations persisted for user. *

@@ -799,15 +822,17 @@ public static ICompoundCondition0 centreConfigQueryFor(final */ static ICompoundCondition0 centreConfigQueryFor(final User user, final Class> miType, final DeviceProfile device, final String surrogateName) { return centreConfigQueryFor(miType, device, surrogateName) - .and().prop("owner").eq().val(user); + .and().condition(centreConfigCondFor(user)); + } + + static ConditionModel centreConfigCondFor(final User user) { + return cond().prop("owner").eq().val(user).model(); } /** * Loads centre through the following chain: 'default centre' + 'differences' := 'centre'. * - * @param user * @param miType - * @param saveAsName -- user-defined title of 'saveAs' centre configuration or empty {@link Optional} for unnamed centre * @param updatedDiff -- updated differences * @param companionFinder * @@ -832,7 +857,6 @@ private static ICentreDomainTreeManagerAndEnhancer loadCentreFromDefaultAndDiff( * Please note that 'default' centre is specific to the user on current thread ({@link IUserProvider}). All injector-based default values will be user-specific * if they are defined as user-specific in domain logic. * - * @param gdtm * @param miType * @return */ @@ -843,7 +867,7 @@ public static ICentreDomainTreeManagerAndEnhancer getDefaultCentre(final Class - * Centres defined as {@code runAutomatically} not only runs automatically on loading; criteria for such centres will be cleared before auto-running (see {@link CriteriaResource#put} for more details). + * Centres defined as {@code runAutomatically} not only runs automatically on loading; criteria for such centres will be cleared before auto-running. * * @param miType * @param webUiConfig @@ -1221,7 +1245,6 @@ private static void processValue(final Map diff, final String va * Applies the differences from 'differences centre' on top of 'target centre'. * * @param targetCentre - * @param differencesCentre * @param root * @param companionFinder -- to process crit-only single entity-typed values * @return diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java index cd9a4b28257..df27b690698 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java @@ -228,24 +228,28 @@ public static Optional findConfigOpt(final Class miType, /** * Finds optional configuration for {@code model} and {@code uuid} with predefined fetch model, sufficient for most situations. */ - public static Optional findConfigOptByUuid(final ICompoundCondition0 model, final String uuid, final EntityCentreConfigCo eccCompanion) { - return eccCompanion.getEntityOptional(from(model - .and().prop("configUuid").eq().val(uuid).model() - ).with(fetchWithKeyAndDesc(EntityCentreConfig.class, true).with("preferred").with("configUuid").with("owner.base").with("configBody").with("runAutomatically").fetchModel()).model()); + private static Optional findConfigOptByModel(final ICompoundCondition0 model, final EntityCentreConfigCo eccCompanion) { + return eccCompanion.getEntityOptional( + from(model.model()) + .with(fetchWithKeyAndDesc(EntityCentreConfig.class, true) + .with("preferred", "configUuid", "owner.base", "configBody", "runAutomatically") + .fetchModel() + ).model() + ); } /** * Finds optional configuration for {@code uuid}, {@code miType}, {@code device} and {@code surrogateName} with predefined fetch model, sufficient for most situations. */ public static Optional findConfigOptByUuid(final String uuid, final Class> miType, final DeviceProfile device, final String surrogateName, final EntityCentreConfigCo eccCompanion) { - return findConfigOptByUuid(centreConfigQueryFor(miType, device, surrogateName), uuid, eccCompanion); + return findConfigOptByModel(centreConfigQueryFor(uuid, miType, device, surrogateName), eccCompanion); } /** * Finds optional configuration for {@code uuid}, {@code user}, {@code miType}, {@code device} and {@code surrogateName} with predefined fetch model, sufficient for most situations. */ public static Optional findConfigOptByUuid(final String uuid, final User user, final Class> miType, final DeviceProfile device, final String surrogateName, final EntityCentreConfigCo eccCompanion) { - return findConfigOptByUuid(centreConfigQueryFor(user, miType, device, surrogateName), uuid, eccCompanion); + return findConfigOptByModel(centreConfigQueryFor(uuid, miType, device, surrogateName).and().condition(centreConfigCondFor(user)), eccCompanion); } /** From bf6500823c2459633fd6d375effce8d81c4258a7 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 3 Feb 2026 21:31:09 +0200 Subject: [PATCH 10/76] #2224 Automatically determine device from config UUID, i.e. allow running of mobile configurations exactly as desktop ones. --- .../web/utils/EntityCentreAPIImpl.java | 31 ++++++++++++++----- .../platform/web/centre/CentreUpdater.java | 11 ++++--- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index d082d84f72d..70387a94565 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -9,6 +9,7 @@ import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; import ua.com.fielden.platform.entity.functional.centre.CentreContextHolder; import ua.com.fielden.platform.entity.meta.MetaProperty; +import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompoundCondition0; import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; import ua.com.fielden.platform.error.Result; import ua.com.fielden.platform.security.IAuthorisationModel; @@ -26,27 +27,29 @@ import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.centre.EntityCentre; import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; -import ua.com.fielden.platform.web.interfaces.DeviceProfile; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Optional.of; -import static java.util.Optional.ofNullable; +import static java.util.Optional.*; import static ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler.CRITERIA_ENTITY_ID; import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; import static ua.com.fielden.platform.data.generator.IGenerator.shouldForceRegeneration; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.from; +import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select; import static ua.com.fielden.platform.error.Result.failure; import static ua.com.fielden.platform.streaming.ValueCollectors.toLinkedHashMap; import static ua.com.fielden.platform.types.either.Either.left; import static ua.com.fielden.platform.types.tuples.T2.t2; import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; import static ua.com.fielden.platform.utils.EntityUtils.fetchWithKeyAndDesc; +import static ua.com.fielden.platform.web.centre.CentreUpdater.PREFIX_OF; import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.findConfigOptByUuid; import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.getEntityCentre; +import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; +import static ua.com.fielden.platform.web.interfaces.DeviceProfile.MOBILE; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; import static ua.com.fielden.platform.web.utils.EntityResourceUtils.getOriginalManagedType; @@ -85,6 +88,20 @@ public EntityCentreAPIImpl( this.securityTokenProvider = securityTokenProvider; } + private static ICompoundCondition0 centreConfigQueryFor(final String surrogateName, final Optional maybeAlias) { + final var selectStart = select(EntityCentreConfig.class); + return maybeAlias.map(selectStart::as).orElse(selectStart) + .where().begin() + .prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(DESKTOP)) + .or().prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(MOBILE)) + .end(); + } + + private static ICompoundCondition0 centreConfigQueryFor(final String uuid, final String surrogateName, final Optional maybeAlias) { + return centreConfigQueryFor(surrogateName, maybeAlias) + .and().condition(centreConfigCondFor(uuid)); + } + @Override public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( final String configUuid @@ -92,12 +109,11 @@ public EntityCentreAPIImpl( final IUser coUser = companionFinder.find(User.class, true); final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); // TODO - final var device = DeviceProfile.DESKTOP; final Optional freshConfigOpt = eccCompanion.getEntityOptional( - from(centreConfigQueryFor(configUuid, device, FRESH_CENTRE_NAME) + from(centreConfigQueryFor(configUuid, FRESH_CENTRE_NAME, of("ecc")) .and().exists( - centreConfigQueryFor(configUuid, device, SAVED_CENTRE_NAME) - .and().prop("owner").eq().extProp("e.owner") + centreConfigQueryFor(configUuid, SAVED_CENTRE_NAME, empty()) + .and().prop("owner").eq().extProp("ecc.owner") .model() ).model()) .with( @@ -118,6 +134,7 @@ public EntityCentreAPIImpl( throw new RuntimeException(e); } Class> miType = (Class>) miTypeGen; + final var device = freshConfigOpt.get().getTitle().startsWith(MOBILE.name()) ? MOBILE : DESKTOP; final User currentUser = userProvider.getUser(); diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java index 56c83dee87c..a8b5f095911 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java @@ -177,7 +177,8 @@ enum MetaValueType { * Function to get title of surrogate configuration from surrogate name, save-as name and device. */ public static final Function, Function>> NAME_OF = surrogateName -> saveAs -> device -> deviceSpecific(saveAsSpecific(surrogateName, saveAs), device) + DIFFERENCES_SUFFIX; - + public static final Function> PREFIX_OF = surrogateName -> device -> deviceSpecific(surrogateName, device) + "[%"; + /** Protected default constructor to prevent instantiation. */ protected CentreUpdater() { } @@ -771,9 +772,9 @@ private static String obtainTitleFrom(final String title, final String surrogate } private static ICompoundCondition0 centreConfigQueryFor(final DeviceProfile device, final String surrogateName) { - return select(EntityCentreConfig.class).as("e") - .where().prop("title").like().val(deviceSpecific(surrogateName, device) + "[%") - .and().prop("title").notLike().val(deviceSpecific(surrogateName, opposite(device)) + "[%"); + return select(EntityCentreConfig.class) + .where().prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(device)) + .and().prop("title").notLike().val(PREFIX_OF.apply(surrogateName).apply(opposite(device))); } public static ICompoundCondition0 centreConfigQueryFor(final String uuid, final DeviceProfile device, final String surrogateName) { @@ -786,7 +787,7 @@ public static ICompoundCondition0 centreConfigQueryFor(final .and().condition(centreConfigCondFor(miType)); } - static ConditionModel centreConfigCondFor(final String uuid) { + public static ConditionModel centreConfigCondFor(final String uuid) { return cond().prop("configUuid").eq().val(uuid).model(); } From f59b5e5af27883345c17ee004592c78576852aff Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 3 Feb 2026 21:44:24 +0200 Subject: [PATCH 11/76] #2224 Reduce unnecessary method visibility. --- .../com/fielden/platform/web/utils/EntityCentreAPIImpl.java | 1 - .../ua/com/fielden/platform/web/centre/CentreUpdater.java | 4 ++-- .../com/fielden/platform/web/centre/CentreUpdaterUtils.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 70387a94565..c70f2f1b366 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -108,7 +108,6 @@ private static ICompoundCondition0 centreConfigQueryFor(fina ) { final IUser coUser = companionFinder.find(User.class, true); final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); - // TODO final Optional freshConfigOpt = eccCompanion.getEntityOptional( from(centreConfigQueryFor(configUuid, FRESH_CENTRE_NAME, of("ecc")) .and().exists( diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java index a8b5f095911..db905372838 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java @@ -777,12 +777,12 @@ private static ICompoundCondition0 centreConfigQueryFor(fina .and().prop("title").notLike().val(PREFIX_OF.apply(surrogateName).apply(opposite(device))); } - public static ICompoundCondition0 centreConfigQueryFor(final String uuid, final DeviceProfile device, final String surrogateName) { + private static ICompoundCondition0 centreConfigQueryFor(final String uuid, final DeviceProfile device, final String surrogateName) { return centreConfigQueryFor(device, surrogateName) .and().condition(centreConfigCondFor(uuid)); } - public static ICompoundCondition0 centreConfigQueryFor(final String uuid, final Class> miType, final DeviceProfile device, final String surrogateName) { + static ICompoundCondition0 centreConfigQueryFor(final String uuid, final Class> miType, final DeviceProfile device, final String surrogateName) { return centreConfigQueryFor(uuid, device, surrogateName) .and().condition(centreConfigCondFor(miType)); } diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java index df27b690698..022ec377129 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java @@ -226,7 +226,7 @@ public static Optional findConfigOpt(final Class miType, } /** - * Finds optional configuration for {@code model} and {@code uuid} with predefined fetch model, sufficient for most situations. + * Finds optional configuration for {@code model} with predefined fetch model, sufficient for most situations. */ private static Optional findConfigOptByModel(final ICompoundCondition0 model, final EntityCentreConfigCo eccCompanion) { return eccCompanion.getEntityOptional( From 273bc70cae0eede2095a2042b6e76aa216cda3f9 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 3 Feb 2026 22:18:50 +0200 Subject: [PATCH 12/76] #2224 Preliminary changes to include dynamic columns into API result-set. --- .../web/utils/EntityCentreAPIImpl.java | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index c70f2f1b366..884f16e5e29 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -25,6 +25,7 @@ import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; import ua.com.fielden.platform.utils.Pair; import ua.com.fielden.platform.web.app.IWebUiConfig; +import ua.com.fielden.platform.web.centre.CentreContext; import ua.com.fielden.platform.web.centre.EntityCentre; import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; @@ -265,37 +266,36 @@ private static ICompoundCondition0 centreConfigQueryFor(fina centre.getCustomPropertiesAsignmentHandler(), pair.getValue().stream()); + // Build dynamic properties object + final var resPropsWithContext = getDynamicResultProperties( + centre, + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + (EnhancedCentreEntityQueryCriteria, ?>) freshCriteriaEntity, + device, + eccCompanion, + mmiCompanion, + userCompanion, + sharingModel); + + //Enhance entities with values defined with consumer in each dynamic property. + processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); +// //Enhance rendering hints with styles for each dynamic column. +// processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); + final List list = new ArrayList<>(); -// list.add(isRunning ? previouslyRunCriteriaEntity : null); -// list.add(pair.getKey()); + // list.add(isRunning ? previouslyRunCriteriaEntity : null); + // list.add(pair.getKey()); // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence // FIXME Need to investigate why this is the case. processedEntities.forEach(entity -> list.add((T) entity) ); return Either.right(list); - // // Build dynamic properties object -// final List>, Optional, ?>>>> resPropsWithContext = getDynamicResultProperties( -// centre, -// webUiConfig, -// companionFinder, -// user, -// critGenerator, -// entityFactory, -// centreContextHolder, -// previouslyRunCriteriaEntity, -// device, -// eccCompanion, -// mmiCompanion, -// userCompanion, -// sharingModel); -// -// //Enhance entities with values defined with consumer in each dynamic property. -// processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); -// //Enhance rendering hints with styles for each dynamic column. -// processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); - - } finally { userProvider.setUser(currentUser); } From a5043ec0212274a345e5f38d8779e7706ca91412 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 11 Feb 2026 12:18:03 +0200 Subject: [PATCH 13/76] #2224 Unify before-run criteria validation. The impact includes sending of invalid criteria entity wrapped into unsuccessful validation result instead of 'Result.successful'. This is now exactly like for authorisation results or invalid IGenerator results. --- .../web/resources/webui/CriteriaResource.java | 31 +++++++++++++------ .../web/utils/EntityCentreAPIImpl.java | 8 +---- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 9a3cc274002..c5aafd703f4 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -588,6 +588,24 @@ public static Result authoriseCriteriaEntity( : entityAuthorisationResult; } + public static Result validateCriteriaBeforeRunning( + final EnhancedCentreEntityQueryCriteria freshCentreAppliedCriteriaEntity, + final Class> miType, + final IAuthorisationModel authorisationModel, + final ISecurityTokenProvider securityTokenProvider + ) { + final Result validationResult = freshCentreAppliedCriteriaEntity.isValid(); + if (!validationResult.isSuccessful()) { + return validationResult; + } + + final Result authorisationResult = authoriseCriteriaEntity(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); + if (!authorisationResult.isSuccessful()) { + return authorisationResult; + } + return Result.successful(); + } + /// Handles `PUT` requests triggered by the `tg-selection-criteria.run()` method. /// @SuppressWarnings("unchecked") @@ -645,19 +663,12 @@ public Representation put(final Representation envelope) { } // There is a need to validate criteria entity with the check for 'required' properties. If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. - final Result validationResult = freshCentreAppliedCriteriaEntity.isValid(); + final Result validationResult = validateCriteriaBeforeRunning(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); if (!validationResult.isSuccessful()) { - LOGGER.debug("CRITERIA_RESOURCE: run finished (validation failed)."); - final var criteriaIndication = createCriteriaIndication((String) centreContextHolder.getModifHolder().get("@@wasRun"), updatedFreshCentre, miType, saveAsName, user, companionFinder, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion); - return restUtil.rawListJsonRepresentation(freshCentreAppliedCriteriaEntity, updateResultantCustomObject(freshCentreAppliedCriteriaEntity.centreDirtyCalculator(), miType, saveAsName, updatedFreshCentre, new LinkedHashMap<>(), of(criteriaIndication))); - } - - final Result authorisationResult = authoriseCriteriaEntity(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); - if (!authorisationResult.isSuccessful()) { - LOGGER.debug("CRITERIA_RESOURCE: run failed (authorisation validation failed)."); + LOGGER.debug("CRITERIA_RESOURCE: run failed (validation failed)."); final var criteriaIndication = createCriteriaIndication((String) centreContextHolder.getModifHolder().get("@@wasRun"), updatedFreshCentre, miType, saveAsName, user, companionFinder, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion); return restUtil.resultJSONRepresentation( - authorisationResult.copyWith(List.of( + validationResult.copyWith(List.of( freshCentreAppliedCriteriaEntity, updateResultantCustomObject(freshCentreAppliedCriteriaEntity.centreDirtyCalculator(), miType, saveAsName, updatedFreshCentre, new LinkedHashMap<>(), of(criteriaIndication)) )) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 884f16e5e29..441ba696995 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -197,17 +197,11 @@ private static ICompoundCondition0 centreConfigQueryFor(fina getOriginalManagedType(validationPrototype.getType(), originalCdtmae) ); - // There is a need to validate criteria entity with the check for 'required' properties. If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. - final Result validationResult = freshCriteriaEntity.isValid(); + final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, miType, authorisationModel, securityTokenProvider); if (!validationResult.isSuccessful()) { return left(validationResult); } - final Result authorisationResult = authoriseCriteriaEntity(freshCriteriaEntity, miType, authorisationModel, securityTokenProvider); - if (!authorisationResult.isSuccessful()) { - return left(authorisationResult); - } - final EntityCentre> centre = getEntityCentre(miType.getName(), webUiConfig); // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) From a6fde3df550e86b66e435ce5eba3a5c54b60a1a1 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 11 Feb 2026 12:30:17 +0200 Subject: [PATCH 14/76] #2224 #2517 Simplify property authorisation by removing unused token provider. --- .../review/criteria/EntityQueryCriteriaUtils.java | 4 ++-- .../ua/com/fielden/platform/security/tokens/TokenUtils.java | 6 +++--- .../platform/web/resources/webui/CriteriaResource.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java index 822c00f53f7..2cb3ac343eb 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java @@ -68,7 +68,7 @@ public static boolean isPropertyAuthorised(final Class root, final String pro // Root property (aka "entity itself") is always authorised. return property.isEmpty() // Non-root property access is governed by *_CanRead_property_* tokens, where the `property` part is never empty. - || authorisePropertyReading(root, property, authorisationModel, securityTokenProvider).orElseGet(Result::successful).isSuccessful(); + || authorisePropertyReading(root, property, authorisationModel).orElseGet(Result::successful).isSuccessful(); } /// Creates an Entity Centre query for a list of [QueryProperty] and other parameters. @@ -91,7 +91,7 @@ public static > EntityQueryProgressiveInterfaces.ICo final IDates dates) { authoriseReading(type.getSimpleName(), READ, authorisationModel, securityTokenProvider).ifFailure(Result::throwRuntime); - authoriseCriteria(queryProperties, authorisationModel, securityTokenProvider).ifFailure(Result::throwRuntime); + authoriseCriteria(queryProperties, authorisationModel).ifFailure(Result::throwRuntime); if (createdByUserConstraint.isPresent()) { final QueryProperty queryProperty = EntityQueryCriteriaUtils.createNotInitialisedQueryProperty(managedType, "createdBy"); final List createdByCriteria = new ArrayList<>(); diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java index a81dabf0173..7fadb542be0 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java @@ -61,14 +61,14 @@ public static Result authoriseReading(final String entityTypeSimpleName, final T /// Validates the criteria values to determine whether they can be applied under the authorisation model. /// - public static Result authoriseCriteria(final List queryProperties, final IAuthorisationModel authorisation, final ISecurityTokenProvider securityTokenProvider) { + public static Result authoriseCriteria(final List queryProperties, final IAuthorisationModel authorisation) { return queryProperties.stream() .map(queryProperty -> { // Criterion without any conditions is authorised. // Root property (aka "entity itself") is always authorised. if (!queryProperty.isEmptyWithoutMnemonics() && !"".equals(queryProperty.getPropertyName())) { final var originalType = getOriginalType(stripIfNeeded(queryProperty.getEntityClass())); - return authorisePropertyReading(originalType, queryProperty.getPropertyName(), authorisation, securityTokenProvider); + return authorisePropertyReading(originalType, queryProperty.getPropertyName(), authorisation); } return Optional.empty(); }) @@ -78,7 +78,7 @@ public static Result authoriseCriteria(final List queryProperties .orElseGet(Result::successful); } - public static Optional authorisePropertyReading(final Class entityType, final String propertyName, final IAuthorisationModel authorisation, final ISecurityTokenProvider securityTokenProvider) { + public static Optional authorisePropertyReading(final Class entityType, final String propertyName, final IAuthorisationModel authorisation) { return getPropertyAnnotationOptionally(Authorise.class, entityType, propertyName) .map(annot -> (Class) annot.value()) .map(authorisation::authorise); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index c5aafd703f4..44266099790 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -584,7 +584,7 @@ public static Result authoriseCriteriaEntity( ) { final var entityAuthorisationResult = authoriseReading(getEntityType(miType).getSimpleName(), READ, authorisationModel, securityTokenProvider); return entityAuthorisationResult.isSuccessful() - ? authoriseCriteria(criteriaEntity.queryProperties.get(), authorisationModel, securityTokenProvider) + ? authoriseCriteria(criteriaEntity.queryProperties.get(), authorisationModel) : entityAuthorisationResult; } From c2f2bbc66a1a119cf66c1d9029f6783685e79bc9 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 20 Feb 2026 00:54:25 +0200 Subject: [PATCH 15/76] #2224 Reuse generation logic in API and standard centre running. --- .../web/resources/webui/CriteriaResource.java | 74 +++++++++++-------- .../web/utils/EntityCentreAPIImpl.java | 31 ++------ 2 files changed, 49 insertions(+), 56 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 44266099790..def2956df1c 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -66,6 +66,7 @@ import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; import static ua.com.fielden.platform.data.generator.IGenerator.shouldForceRegeneration; import static ua.com.fielden.platform.error.Result.failure; +import static ua.com.fielden.platform.error.Result.successful; import static ua.com.fielden.platform.security.tokens.Template.READ; import static ua.com.fielden.platform.security.tokens.TokenUtils.authoriseCriteria; import static ua.com.fielden.platform.security.tokens.TokenUtils.authoriseReading; @@ -589,21 +590,50 @@ public static Result authoriseCriteriaEntity( } public static Result validateCriteriaBeforeRunning( - final EnhancedCentreEntityQueryCriteria freshCentreAppliedCriteriaEntity, + final EnhancedCentreEntityQueryCriteria criteriaEntity, final Class> miType, final IAuthorisationModel authorisationModel, final ISecurityTokenProvider securityTokenProvider ) { - final Result validationResult = freshCentreAppliedCriteriaEntity.isValid(); + final Result validationResult = criteriaEntity.isValid(); if (!validationResult.isSuccessful()) { return validationResult; } - final Result authorisationResult = authoriseCriteriaEntity(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); + final Result authorisationResult = authoriseCriteriaEntity(criteriaEntity, miType, authorisationModel, securityTokenProvider); if (!authorisationResult.isSuccessful()) { return authorisationResult; } - return Result.successful(); + return successful(); + } + + public static Result generateDataIfNeeded( + final EnhancedCentreEntityQueryCriteria criteriaEntity, + final EntityCentre> centre, + final boolean isRunning, + final boolean isSorting, + final Map customObject + ) { + // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) + // then for an entity centre configuration check if a generator was provided + final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); + final boolean generationShouldOccur = isRunning && !isSorting && createdByConstraintShouldOccur; + if (generationShouldOccur) { + // obtain the type for entities to be generated + final Class> generatorEntityType = (Class>) centre.getGeneratorTypes().get().getKey(); + + // create and execute a generator instance + final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); + final Map> params = criteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( + (final MetaProperty mp) -> mp.getName(), + (final MetaProperty mp) -> ofNullable(mp.getValue()))); + params.putAll(criteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); + if (shouldForceRegeneration(customObject)) { + params.put(FORCE_REGENERATION_KEY, of(true)); + } + return generator.gen(generatorEntityType, params); + } + return successful(); } /// Handles `PUT` requests triggered by the `tg-selection-criteria.run()` method. @@ -679,33 +709,15 @@ public Representation put(final Representation envelope) { freshCentreAppliedCriteriaEntity = null; } - // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) - // then for an entity centre configuration check if a generator was provided - final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); - final boolean generationShouldOccur = isRunning && !isSorting && createdByConstraintShouldOccur; - if (generationShouldOccur) { - // obtain the type for entities to be generated - final Class> generatorEntityType = (Class>) centre.getGeneratorTypes().get().getKey(); - - // create and execute a generator instance - final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); - final Map> params = freshCentreAppliedCriteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( - (final MetaProperty mp) -> mp.getName(), - (final MetaProperty mp) -> ofNullable(mp.getValue()))); - params.putAll(freshCentreAppliedCriteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); - if (shouldForceRegeneration(customObject)) { - params.put(FORCE_REGENERATION_KEY, of(true)); - } - final Result generationResult = generator.gen(generatorEntityType, params); - // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result - // otherwise, proceed with the request handling further to actually query the data - // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' - if (!generationResult.isSuccessful()) { - LOGGER.debug("CRITERIA_RESOURCE: run finished (generation failed)."); - final var criteriaIndication = createCriteriaIndication((String) centreContextHolder.getModifHolder().get("@@wasRun"), updatedFreshCentre, miType, saveAsName, user, companionFinder, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion); - final Result result = generationResult.copyWith(List.of(freshCentreAppliedCriteriaEntity, updateResultantCustomObject(freshCentreAppliedCriteriaEntity.centreDirtyCalculator(), miType, saveAsName, updatedFreshCentre, new LinkedHashMap<>(), of(criteriaIndication)))); - return restUtil.resultJSONRepresentation(result); - } + final var generationResult = generateDataIfNeeded(freshCentreAppliedCriteriaEntity, centre, isRunning, isSorting, customObject); + // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result + // otherwise, proceed with the request handling further to actually query the data + // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' + if (!generationResult.isSuccessful()) { + LOGGER.debug("CRITERIA_RESOURCE: run finished (generation failed)."); + final var criteriaIndication = createCriteriaIndication((String) centreContextHolder.getModifHolder().get("@@wasRun"), updatedFreshCentre, miType, saveAsName, user, companionFinder, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion); + final Result result = generationResult.copyWith(List.of(freshCentreAppliedCriteriaEntity, updateResultantCustomObject(freshCentreAppliedCriteriaEntity.centreDirtyCalculator(), miType, saveAsName, updatedFreshCentre, new LinkedHashMap<>(), of(criteriaIndication)))); + return restUtil.resultJSONRepresentation(result); } if (isRunning) { diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 441ba696995..defb46feabd 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -203,31 +203,12 @@ private static ICompoundCondition0 centreConfigQueryFor(fina } final EntityCentre> centre = getEntityCentre(miType.getName(), webUiConfig); - - // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) - // then for an entity centre configuration check if a generator was provided - final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); - final boolean generationShouldOccur = isRunning && !isSorting && createdByConstraintShouldOccur; - if (generationShouldOccur) { - // obtain the type for entities to be generated - final Class> generatorEntityType = (Class>) centre.getGeneratorTypes().get().getKey(); - - // create and execute a generator instance - final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); - final Map> params = freshCriteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( - (final MetaProperty mp) -> mp.getName(), - (final MetaProperty mp) -> ofNullable(mp.getValue()))); - params.putAll(freshCriteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); - if (shouldForceRegeneration(customObject)) { // TODO always force regeneration - params.put(FORCE_REGENERATION_KEY, of(true)); - } - final Result generationResult = generator.gen(generatorEntityType, params); - // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result - // otherwise, proceed with the request handling further to actually query the data - // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' - if (!generationResult.isSuccessful()) { - return left(generationResult); - } + final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, centre, isRunning, isSorting, customObject); + // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result + // otherwise, proceed with the request handling further to actually query the data + // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' + if (!generationResult.isSuccessful()) { + return left(generationResult); } //final ICentreDomainTreeManagerAndEnhancer updatedSavedCentre = savedCriteriaEntity.getCentreDomainTreeMangerAndEnhancer(); From 44f95326e0608bd5d70f72ce683a2d0aa886cdf7 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 21 Feb 2026 23:08:51 +0200 Subject: [PATCH 16/76] #2224 Added basic unit test for Entity Centre API execution. The test resides in -web-resources module to take into account UI configurations (DSL) being executed by the API. Additional API extension is possible (through ICentreDomainTreeManagerAndEnhancer) to provide ability to define custom values for criteria, sorting and other Entity Centre settings in unit tests. The same approach is possible in end applications for unit tests in -web-server module (see WebUiConfigTest for more). --- .../DataPopulationConfigForWebTests.java | 46 ++++++++++ .../TgTestWebApplicationServerIocModule.java | 11 ++- ...DomainDrivenTestCaseRunnerForWebTests.java | 36 ++++++++ .../web/utils/EntityCentreAPITest.java | 85 +++++++++++++++++++ 4 files changed, 172 insertions(+), 6 deletions(-) create mode 100644 platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java create mode 100644 platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java create mode 100644 platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java new file mode 100644 index 00000000000..23502c5c223 --- /dev/null +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java @@ -0,0 +1,46 @@ +package ua.com.fielden.platform.web.test.server; + +import com.google.inject.Injector; +import ua.com.fielden.platform.ioc.ApplicationInjectorFactory; +import ua.com.fielden.platform.ioc.NewUserEmailNotifierTestIocModule; +import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; +import ua.com.fielden.platform.web.test.config.ApplicationDomain; + +import java.util.Properties; + +/// Provides Web UI Testing Server specific implementation of [IDomainDrivenTestCaseConfiguration] to be used for web unit tests. +/// +public final class DataPopulationConfigForWebTests implements IDomainDrivenTestCaseConfiguration { + private final Injector injector; + + public DataPopulationConfigForWebTests(final Properties props) { + // instantiate all the factories and Hibernate utility + try { + // application properties + props.setProperty("app.name", "TG Test App"); + props.setProperty("reports.path", ""); + props.setProperty("domain.path", "../platform-pojo-bl/target/classes"); + props.setProperty("domain.package", "ua.com.fielden.platform.sample.domain"); + props.setProperty("tokens.path", "../platform-pojo-bl/target/classes"); + props.setProperty("tokens.package", "ua.com.fielden.platform.security.tokens"); + props.setProperty("workflow", "development"); + props.setProperty("email.smtp", "localhost"); + props.setProperty("email.fromAddress", "tg@localhost"); + props.setProperty("audit.path", "../platform-pojo-bl/target/classes"); + + final ApplicationDomain appDomain = new ApplicationDomain(); + injector = new ApplicationInjectorFactory() + .add(new TgTestWebApplicationServerIocModule(appDomain, appDomain.domainTypes(), props)) + .add(new NewUserEmailNotifierTestIocModule()) + .getInjector(); + } catch (final Exception e) { + throw new IllegalStateException("Could not create data population configuration.", e); + } + } + + @Override + public T getInstance(final Class type) { + return injector.getInstance(type); + } + +} diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java index cf2cd1a9b43..141d47b5f92 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java @@ -8,16 +8,14 @@ import ua.com.fielden.platform.entity.validation.CanBuildReferenceHierarchyForEveryEntityValidator; import ua.com.fielden.platform.entity.validation.ICanBuildReferenceHierarchyForEntityValidator; import ua.com.fielden.platform.web.ioc.IBasicWebApplicationServerModule; +import ua.com.fielden.platform.web.utils.EntityCentreAPI; +import ua.com.fielden.platform.web.utils.EntityCentreAPIImpl; import java.util.List; import java.util.Properties; -/** - * Guice injector module for TG Testing Server (WebApp-enabled). - * - * @author TG Team - * - */ +/// Guice injector module for TG Testing Server (WebApp-enabled). +/// public class TgTestWebApplicationServerIocModule extends TgTestApplicationServerIocModule implements IBasicWebApplicationServerModule, IModuleWithInjector { private final Properties props; @@ -36,6 +34,7 @@ protected void configure() { super.configure(); bind(ICanBuildReferenceHierarchyForEntityValidator.class).to(CanBuildReferenceHierarchyForEveryEntityValidator.class); bindWebAppResources(new WebUiConfig(props)); + bind(EntityCentreAPI.class).to(EntityCentreAPIImpl.class); } @Override diff --git a/platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java b/platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java new file mode 100644 index 00000000000..138d1a7dfd7 --- /dev/null +++ b/platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java @@ -0,0 +1,36 @@ +package test_config; + +import ua.com.fielden.platform.test.AbstractDomainDrivenTestCase; +import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; +import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner; +import ua.com.fielden.platform.web.test.server.DataPopulationConfig; +import ua.com.fielden.platform.web.test.server.DataPopulationConfigForWebTests; + +import java.util.Properties; + +/// A test case runner for domain-driven unit tests extending [AbstractDomainDrivenTestCase]. +/// +/// Used for web-server-driven tests in *-web-resources module. +/// +public class SqlServerDomainDrivenTestCaseRunnerForWebTests extends SqlServerDomainDrivenTestCaseRunner { + + public SqlServerDomainDrivenTestCaseRunnerForWebTests(final Class klass) throws Exception { + super(klass); + } + + public SqlServerDomainDrivenTestCaseRunnerForWebTests(final Class klass, final IDomainDrivenTestCaseConfiguration testConfig) throws Exception { + super(klass, testConfig); + } + + @Override + protected Properties mkDbProps(final String dbUri) { + final Properties props = super.mkDbProps(dbUri); + props.setProperty("config.domain", DataPopulationConfigForWebTests.class.getName()); + props.setProperty("attachments.location", "../platform-web-resources/src/test/resources/attachments"); + props.setProperty("web.domain", "unit-test.com"); + props.setProperty("web.path", "/"); + props.setProperty("port", "1234"); + return props; + } + +} \ No newline at end of file diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java new file mode 100644 index 00000000000..733c67f79fd --- /dev/null +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java @@ -0,0 +1,85 @@ +package ua.com.fielden.platform.web.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; +import org.junit.runner.RunWith; +import test_config.SqlServerDomainDrivenTestCaseRunnerForWebTests; +import ua.com.fielden.platform.sample.domain.compound.TgCompoundEntity; +import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.test_config.AbstractDaoTestCase; +import ua.com.fielden.platform.ui.config.EntityCentreConfig; +import ua.com.fielden.platform.ui.config.MainMenuItem; + +import java.util.Map; + +import static org.junit.Assert.*; +import static ua.com.fielden.platform.types.tuples.T2.t2; +import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; + +/// Tests for {@link EntityCentreAPI}. +/// These should serve as an example of how end-application tests can be implemented. +/// +@RunWith(SqlServerDomainDrivenTestCaseRunnerForWebTests.class) +public class EntityCentreAPITest extends AbstractDaoTestCase { + + /// Creates Entity Centre persisted configuration for concrete surrogate version of it (FRESH or SAVED). + /// + private void createConfig( + final String surrogateName, + final MainMenuItem mainMenuItem, + final Map diffObject, + final String uuid + ) { + try { + save(new_(EntityCentreConfig.class) + .setOwner(getUser()) + .setMenuItem(mainMenuItem) + .setTitle("__________%s[saveAs]__________DIFFERENCES".formatted(surrogateName)) + .setConfigBody(new ObjectMapper().writeValueAsBytes(diffObject)) + .setConfigUuid(uuid) + ); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Test + public void executing_entityCentreResult_method_returns_entities_for_named_configuration_for_no_additional_criteria() { + setupUser(User.system_users.UNIT_TEST_USER, "example.tg.test"); + + // Initialise data for `MiTgCompoundEntity` Entity Centre execution. + + // Create entity instance first. + save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); + // Define uuid for named configuration. + final var uuid = "c95ec1ae-09e8-439f-92e7-880261fce023"; + // Create MainMenuItem instance conforming to MiTgCompoundEntity. + final var mainMenuItem = (MainMenuItem) save(new_(MainMenuItem.class).setKey("fielden.test_app.main.menu.compound.MiTgCompoundEntity")); + // Create empty diff object for Entity Centre configuration, meaning it will behave exactly as defined in `TgCompoundEntityWebUiConfig`. + final Map diffObject = mapOf( + t2("PROPERTIES", mapOf()) + ); + + // Create "fresh" config, that would get executed through the API. + createConfig("FRESH", mainMenuItem, diffObject, uuid); + // Create "saved" config for current user too, which indicates that the user own it. + createConfig("SAVED", mainMenuItem, diffObject, uuid); + + + final var entityCentreApi = getInstance(EntityCentreAPI.class); + final var result = entityCentreApi.entityCentreResult(uuid); + + assertNotNull(result); + assertTrue(result.isRight()); + assertNotNull(result.asRight().value()); + assertEquals(1, result.asRight().value().size()); + assertEquals("KEY1", result.asRight().value().getFirst().getKey()); + } + + @Override + protected void populateDomain() { + // do nothing to save time + } + +} \ No newline at end of file From 797d0899c2ce38d0058dc1b28f3f93297321dbdc Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 21 Feb 2026 23:12:31 +0200 Subject: [PATCH 17/76] #2224 Temporarily comment out data-based default values in Entity Centre configurations to make EntityCentreAPITest happy. Web test becomes broken though. centre/test/tg-entity-centre.html entity centre running does not occur for invalid criteria --- .../platform/web/test/server/WebUiConfig.java | 16 ++++++++-------- .../platform/web/utils/EntityCentreAPITest.java | 3 +++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java index f08038db246..0b38d0adadd 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java @@ -258,13 +258,13 @@ public void initConfiguration() { .addTopAction(CentreConfigActions.CUSTOMISE_COLUMNS_ACTION.mkAction()).also() .addTopAction(StandardActions.EXPORT_ACTION.mkAction(TgFetchProviderTestEntity.class)) .addCrit("property").asMulti().autocompleter(TgPersistentEntityWithProperties.class).setDefaultValue(multi().string().setValues("KE*").value()).also() - .addCrit("propForValidation").asSingle().autocompleter(TgPersistentEntityWithProperties.class) - .setDefaultValue( - single() - .entity(TgPersistentEntityWithProperties.class) - .setValue(injector().getInstance(ITgPersistentEntityWithProperties.class).findByKey("KEY8")) - .value() - ). + .addCrit("propForValidation").asSingle().autocompleter(TgPersistentEntityWithProperties.class). +// .setDefaultValue( +// single() +// .entity(TgPersistentEntityWithProperties.class) +// .setValue(injector().getInstance(ITgPersistentEntityWithProperties.class).findByKey("KEY8")) +// .value() +// ). setLayoutFor(Device.DESKTOP, Optional.empty(), LayoutComposer.mkGridForCentre(1, 2)) .addProp("property") @@ -1565,7 +1565,7 @@ public JsCode build() { .addCrit("critOnlyEntityProp").asSingle().autocompleter(TgPersistentEntityWithProperties.class) .withMatcher(CritOnlySingleEntityPropValueMatcherForCentre.class, context().withSelectedEntities()./*withMasterEntity().*/build()) .lightDesc() - /* */.setDefaultValue(single().entity(TgPersistentEntityWithProperties.class)./* TODO not applicable on query generation level not().*/setValue(injector().getInstance(ITgPersistentEntityWithProperties.class).findByKey("KEY8"))./* TODO not applicable on query generation level canHaveNoValue(). */value()) +// /* */.setDefaultValue(single().entity(TgPersistentEntityWithProperties.class).setValue(injector().getInstance(ITgPersistentEntityWithProperties.class).findByKey("KEY8")).value()) .also() .addCrit("userParam").asSingle().autocompleter(User.class) .withProps(pair("base", false), pair("basedOnUser", false)) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java index 733c67f79fd..87948dabda9 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java @@ -50,6 +50,9 @@ public void executing_entityCentreResult_method_returns_entities_for_named_confi // Initialise data for `MiTgCompoundEntity` Entity Centre execution. + // This instance is required for some default values in WebUiConfig-registered centres. + // TODO save(new_(TgPersistentEntityWithProperties.class, "KEY8").setDateProp(new DateTime(3609999L).toDate()).setDesc("Description for entity with key 8.").setRequiredValidatedProp(30)); + // Create entity instance first. save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); // Define uuid for named configuration. From 01b66466a6527dffb860832cf951f0026f37c686 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 00:16:24 +0200 Subject: [PATCH 18/76] #2224 Remove data-based default values in Entity Centre configurations as not needed. Adjust web test. --- .../com/fielden/platform/web/test/server/WebUiConfig.java | 7 ------- .../fielden/platform/web/utils/EntityCentreAPITest.java | 8 ++++---- .../platform/web/centre/test/tg-entity-centre.html | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java index 0b38d0adadd..2a9340f6239 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/WebUiConfig.java @@ -259,12 +259,6 @@ public void initConfiguration() { .addTopAction(StandardActions.EXPORT_ACTION.mkAction(TgFetchProviderTestEntity.class)) .addCrit("property").asMulti().autocompleter(TgPersistentEntityWithProperties.class).setDefaultValue(multi().string().setValues("KE*").value()).also() .addCrit("propForValidation").asSingle().autocompleter(TgPersistentEntityWithProperties.class). -// .setDefaultValue( -// single() -// .entity(TgPersistentEntityWithProperties.class) -// .setValue(injector().getInstance(ITgPersistentEntityWithProperties.class).findByKey("KEY8")) -// .value() -// ). setLayoutFor(Device.DESKTOP, Optional.empty(), LayoutComposer.mkGridForCentre(1, 2)) .addProp("property") @@ -1565,7 +1559,6 @@ public JsCode build() { .addCrit("critOnlyEntityProp").asSingle().autocompleter(TgPersistentEntityWithProperties.class) .withMatcher(CritOnlySingleEntityPropValueMatcherForCentre.class, context().withSelectedEntities()./*withMasterEntity().*/build()) .lightDesc() -// /* */.setDefaultValue(single().entity(TgPersistentEntityWithProperties.class).setValue(injector().getInstance(ITgPersistentEntityWithProperties.class).findByKey("KEY8")).value()) .also() .addCrit("userParam").asSingle().autocompleter(User.class) .withProps(pair("base", false), pair("basedOnUser", false)) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java index 87948dabda9..8203c92eb79 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java @@ -48,10 +48,7 @@ private void createConfig( public void executing_entityCentreResult_method_returns_entities_for_named_configuration_for_no_additional_criteria() { setupUser(User.system_users.UNIT_TEST_USER, "example.tg.test"); - // Initialise data for `MiTgCompoundEntity` Entity Centre execution. - - // This instance is required for some default values in WebUiConfig-registered centres. - // TODO save(new_(TgPersistentEntityWithProperties.class, "KEY8").setDateProp(new DateTime(3609999L).toDate()).setDesc("Description for entity with key 8.").setRequiredValidatedProp(30)); + //////// Initialise data for `MiTgCompoundEntity` Entity Centre execution. //////// // Create entity instance first. save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); @@ -69,10 +66,13 @@ public void executing_entityCentreResult_method_returns_entities_for_named_confi // Create "saved" config for current user too, which indicates that the user own it. createConfig("SAVED", mainMenuItem, diffObject, uuid); + //////// Run Entity Centre through the API //////// final var entityCentreApi = getInstance(EntityCentreAPI.class); final var result = entityCentreApi.entityCentreResult(uuid); + //////// Check the result //////// + assertNotNull(result); assertTrue(result.isRight()); assertNotNull(result.asRight().value()); diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html index 794fb9b5219..cf9933ad1e5 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/test/tg-entity-centre.html @@ -63,7 +63,7 @@ oldPostRetrieved(entity, bindingEntity, customObject); centre.postRetrieved = oldPostRetrieved; - assert.strictEqual(bindingEntity.get('tgFetchProviderTestEntity_propForValidation'), 'KEY8'); + assert.strictEqual(bindingEntity.get('tgFetchProviderTestEntity_propForValidation'), null); assert.strictEqual(centre.$.selection_criteria._centreDirty, true, '_centreDirty flag should be defined and should be equal to true.'); // this is because we use default configuration which is always dirty assert.ok(entity, 'Criteria entity should arrive from the server.'); assert.strictEqual(entity.isValid(), true, 'Criteria entity should be valid.'); From b02a87cb53817a76ec6d16ae5844781c5e78a1e1 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 01:46:59 +0200 Subject: [PATCH 19/76] #2224 Enhance test to support all databases (not only SQL Server). --- ...rPostgreSqlOrSqlServerContextSelector.java | 33 ++++----- ...DomainDrivenTestCaseRunnerForWebTests.java | 36 --------- ...OrSqlServerContextSelectorForWebTests.java | 73 +++++++++++++++++++ .../web/utils/EntityCentreAPITest.java | 4 +- 4 files changed, 91 insertions(+), 55 deletions(-) delete mode 100644 platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java create mode 100644 platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java diff --git a/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java b/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java index c5663392e57..e5a1833ffa6 100644 --- a/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java +++ b/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java @@ -1,30 +1,29 @@ package ua.com.fielden.platform.test_config; -import static org.apache.commons.lang3.StringUtils.isEmpty; - -import java.util.Optional; -import java.util.Properties; - import ua.com.fielden.platform.test.DbCreator; import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; import ua.com.fielden.platform.test.db_creators.H2DbCreator; import ua.com.fielden.platform.test.runners.AbstractDomainDrivenTestCaseRunner; -import ua.com.fielden.platform.test.runners.H2DomainDrivenTestCaseRunner; +import ua.com.fielden.platform.test.runners.H2DomainDrivenTestCaseRunner.H2TestContext; import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner; +import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner.PostgresqlTestContext; import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner; +import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner.SqlServerTestContext; + +import java.util.Optional; +import java.util.Properties; + +import static org.apache.commons.lang3.StringUtils.isEmpty; -/** - * A test runner that selects a test configuration {@link ITestContext} from either {@link H2DomainDrivenTestCaseRunner} or {@link PostgresqlDomainDrivenTestCaseRunner} for running unit test. - * The criteria for selecting the appropriate test runner is based on runtime settings. - * - * @author TG Team - */ +/// A test runner that selects a test configuration [ITestContext] from [SqlServerDomainDrivenTestCaseRunner] or [PostgresqlDomainDrivenTestCaseRunner] for running unit test. +/// The criteria for selecting the appropriate test runner is based on runtime settings. +/// public class H2OrPostgreSqlOrSqlServerContextSelector extends AbstractDomainDrivenTestCaseRunner { // Note: This assumes PostgreSQL is listening on port 5432 (the default). // There is not much else in the URI that would uniquely identify that we are connecting to a PostgreSQL database. - private static final boolean POSTGRESQL = !isEmpty(System.getProperty("databaseUri")) && System.getProperty("databaseUri").contains("5432"); - private static final boolean SQL_SERVER = !isEmpty(System.getProperty("databaseUri")) && System.getProperty("databaseUri").contains("database"); + protected static final boolean POSTGRESQL = !isEmpty(System.getProperty("databaseUri")) && System.getProperty("databaseUri").contains("5432"); + protected static final boolean SQL_SERVER = !isEmpty(System.getProperty("databaseUri")) && System.getProperty("databaseUri").contains("database"); public H2OrPostgreSqlOrSqlServerContextSelector(final Class klass) throws Exception { super(klass, POSTGRESQL ? PostgresqlDbCreator.class : (SQL_SERVER ? SqlServerDbCreator.class : H2DbCreator.class), Optional.empty()); @@ -45,10 +44,10 @@ public void dbCleanUp() { selectRunnerConfig().dbCleanUp(); } - private ITestContext selectRunnerConfig() { + protected ITestContext selectRunnerConfig() { return POSTGRESQL - ? new PostgresqlDomainDrivenTestCaseRunner.PostgresqlTestContext() - : (SQL_SERVER ? new SqlServerDomainDrivenTestCaseRunner.SqlServerTestContext() : new H2DomainDrivenTestCaseRunner.H2TestContext(this)); + ? new PostgresqlTestContext() + : (SQL_SERVER ? new SqlServerTestContext() : new H2TestContext(this)); } } \ No newline at end of file diff --git a/platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java b/platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java deleted file mode 100644 index 138d1a7dfd7..00000000000 --- a/platform-web-resources/src/test/java/test_config/SqlServerDomainDrivenTestCaseRunnerForWebTests.java +++ /dev/null @@ -1,36 +0,0 @@ -package test_config; - -import ua.com.fielden.platform.test.AbstractDomainDrivenTestCase; -import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; -import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner; -import ua.com.fielden.platform.web.test.server.DataPopulationConfig; -import ua.com.fielden.platform.web.test.server.DataPopulationConfigForWebTests; - -import java.util.Properties; - -/// A test case runner for domain-driven unit tests extending [AbstractDomainDrivenTestCase]. -/// -/// Used for web-server-driven tests in *-web-resources module. -/// -public class SqlServerDomainDrivenTestCaseRunnerForWebTests extends SqlServerDomainDrivenTestCaseRunner { - - public SqlServerDomainDrivenTestCaseRunnerForWebTests(final Class klass) throws Exception { - super(klass); - } - - public SqlServerDomainDrivenTestCaseRunnerForWebTests(final Class klass, final IDomainDrivenTestCaseConfiguration testConfig) throws Exception { - super(klass, testConfig); - } - - @Override - protected Properties mkDbProps(final String dbUri) { - final Properties props = super.mkDbProps(dbUri); - props.setProperty("config.domain", DataPopulationConfigForWebTests.class.getName()); - props.setProperty("attachments.location", "../platform-web-resources/src/test/resources/attachments"); - props.setProperty("web.domain", "unit-test.com"); - props.setProperty("web.path", "/"); - props.setProperty("port", "1234"); - return props; - } - -} \ No newline at end of file diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java new file mode 100644 index 00000000000..daf85eccdbf --- /dev/null +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java @@ -0,0 +1,73 @@ +package ua.com.fielden.platform.test_config; + +import ua.com.fielden.platform.test.DbCreator; +import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; +import ua.com.fielden.platform.test.runners.AbstractDomainDrivenTestCaseRunner; +import ua.com.fielden.platform.test.runners.H2DomainDrivenTestCaseRunner; +import ua.com.fielden.platform.test.runners.H2DomainDrivenTestCaseRunner.H2TestContext; +import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner; +import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner.PostgresqlTestContext; +import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner; +import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner.SqlServerTestContext; +import ua.com.fielden.platform.web.app.IWebUiConfig; +import ua.com.fielden.platform.web.test.server.DataPopulationConfigForWebTests; + +import java.util.Optional; +import java.util.Properties; + +/// A test runner that selects a test configuration [ITestContext] from [SqlServerDomainDrivenTestCaseRunner] or [PostgresqlDomainDrivenTestCaseRunner] for running unit test. +/// The criteria for selecting the appropriate test runner is based on runtime settings. +/// +/// This test runner is capable for running tests with Web UI infrastructure such as [IWebUiConfig]. +/// Used for web-server-driven tests in *-web-resources module. +/// +public class H2OrPostgreSqlOrSqlServerContextSelectorForWebTests extends H2OrPostgreSqlOrSqlServerContextSelector { + + public H2OrPostgreSqlOrSqlServerContextSelectorForWebTests(Class klass) throws Exception { + super(klass); + } + + public H2OrPostgreSqlOrSqlServerContextSelectorForWebTests(Class klass, Class dbCreatorType, Optional testConfig) throws Exception { + super(klass, dbCreatorType, testConfig); + } + + private static class PostgresqlTestContextForWebTests extends PostgresqlTestContext { + @Override + public Properties mkDbProps(String dbUri) { + return mkPropsWebCapable(super.mkDbProps(dbUri)); + } + } + + private static class SqlServerTestContextForWebTests extends SqlServerTestContext { + @Override + public Properties mkDbProps(String dbUri) { + return mkPropsWebCapable(super.mkDbProps(dbUri)); + } + } + + private static class H2TestContextForWebTests extends H2TestContext { + public H2TestContextForWebTests(AbstractDomainDrivenTestCaseRunner runner) { + super(runner); + } + @Override + public Properties mkDbProps(String dbUri) { + return mkPropsWebCapable(super.mkDbProps(dbUri)); + } + } + + private static Properties mkPropsWebCapable(Properties props) { + props.setProperty("config.domain", DataPopulationConfigForWebTests.class.getName()); + props.setProperty("attachments.location", "../platform-web-resources/src/test/resources/attachments"); + props.setProperty("web.domain", "unit-test.com"); + props.setProperty("web.path", "/"); + props.setProperty("port", "1234"); + return props; + } + + protected ITestContext selectRunnerConfig() { + return POSTGRESQL + ? new PostgresqlTestContextForWebTests() + : (SQL_SERVER ? new SqlServerTestContextForWebTests() : new H2TestContextForWebTests(this)); + } + +} diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java index 8203c92eb79..00045f09b15 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java @@ -4,10 +4,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; import org.junit.runner.RunWith; -import test_config.SqlServerDomainDrivenTestCaseRunnerForWebTests; import ua.com.fielden.platform.sample.domain.compound.TgCompoundEntity; import ua.com.fielden.platform.security.user.User; import ua.com.fielden.platform.test_config.AbstractDaoTestCase; +import ua.com.fielden.platform.test_config.H2OrPostgreSqlOrSqlServerContextSelectorForWebTests; import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.config.MainMenuItem; @@ -20,7 +20,7 @@ /// Tests for {@link EntityCentreAPI}. /// These should serve as an example of how end-application tests can be implemented. /// -@RunWith(SqlServerDomainDrivenTestCaseRunnerForWebTests.class) +@RunWith(H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.class) public class EntityCentreAPITest extends AbstractDaoTestCase { /// Creates Entity Centre persisted configuration for concrete surrogate version of it (FRESH or SAVED). From cee610affdb0f49e1372748b4f2aa8eab1abe522 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 02:12:02 +0200 Subject: [PATCH 20/76] #2224 Minor simplifications. --- .../web/utils/EntityCentreAPIImpl.java | 54 +++---------------- 1 file changed, 6 insertions(+), 48 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index defb46feabd..cc19172ef54 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -33,6 +33,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.lang.Class.forName; import static java.util.Optional.*; import static ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler.CRITERIA_ENTITY_ID; import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; @@ -58,7 +59,6 @@ public class EntityCentreAPIImpl implements EntityCentreAPI { private final ICompanionObjectFinder companionFinder; private final IUserProvider userProvider; -// private final IDeviceProvider deviceProvider; private final ICriteriaGenerator critGenerator; private final IWebUiConfig webUiConfig; private final EntityFactory entityFactory; @@ -70,7 +70,6 @@ public class EntityCentreAPIImpl implements EntityCentreAPI { public EntityCentreAPIImpl( final ICompanionObjectFinder companionFinder, final IUserProvider userProvider, -// final IDeviceProvider deviceProvider, final ICriteriaGenerator critGenerator, final IWebUiConfig webUiConfig, final EntityFactory entityFactory, @@ -80,7 +79,6 @@ public EntityCentreAPIImpl( ) { this.companionFinder = companionFinder; this.userProvider = userProvider; -// this.deviceProvider = deviceProvider; this.critGenerator = critGenerator; this.webUiConfig = webUiConfig; this.entityFactory = entityFactory; @@ -123,13 +121,13 @@ private static ICompoundCondition0 centreConfigQueryFor(fina ).model() ); - if (!freshConfigOpt.isPresent()) { + if (freshConfigOpt.isEmpty()) { return left(Result.failure("Config with uuid %s does not exist.".formatted(configUuid))); } final User user = coUser.findUser(freshConfigOpt.get().getOwner().getKey()); final Class miTypeGen; try { - miTypeGen = Class.forName(freshConfigOpt.get().getMenuItem().getKey()); + miTypeGen = forName(freshConfigOpt.get().getMenuItem().getKey()); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } @@ -150,41 +148,10 @@ private static ICompoundCondition0 centreConfigQueryFor(fina final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); final IUser userCompanion = companionFinder.find(User.class); - - -// final EnhancedCentreEntityQueryCriteria freshCentreAppliedCriteriaEntity = createCriteriaEntityWithoutConflicts( -// centreContextHolder.getModifHolder(), -// companionFinder, -// critGenerator, -// miType, -// saveAsName, -// user, -// DeviceProfile.DESKTOP, -// webUiConfig, -// eccCompanion, -// mmiCompanion, -// userCompanion, -// sharingModel -// ); - - - // start loading of configuration defined by concrete uuid; - // we look only through [link, own save-as, inherited from base, inherited from shared] set of configurations; - // default configurations are excluded in the lookup; - // only FRESH kind are looked for; - final Optional actualSaveAsName; - // for current user we already have FRESH configuration with uuid loaded; - final Optional preliminarySaveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); -// // updating is required from upstream configuration; -// if (!LINK_CONFIG_TITLE.equals(preliminarySaveAsName.get())) { // (but not for link configuration); -// actualSaveAsName = updateFromUpstream(configUuid, preliminarySaveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder)._1; -// } else { - actualSaveAsName = preliminarySaveAsName; -// } - if (!actualSaveAsName.isPresent() || LINK_CONFIG_TITLE.equals(actualSaveAsName.get())) { - return left(failure("Default / Link configs are not available for API running (%s).".formatted(actualSaveAsName))); + final Optional saveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); + if (LINK_CONFIG_TITLE.equals(saveAsName.get())) { + return left(failure("Default / Link configs are not available for API running (%s).".formatted(saveAsName))); } - final var saveAsName = actualSaveAsName; // load / update fresh centre if it is not loaded yet / stale final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); @@ -211,13 +178,6 @@ private static ICompoundCondition0 centreConfigQueryFor(fina return left(generationResult); } - //final ICentreDomainTreeManagerAndEnhancer updatedSavedCentre = savedCriteriaEntity.getCentreDomainTreeMangerAndEnhancer(); - - //commitCentreWithoutConflicts(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, updatedSavedCentre, null, webUiConfig, eccCompanion, mmiCompanion, userCompanion); - - //final ICentreDomainTreeManagerAndEnhancer previouslyRunCentre = updateCentre(user, miType, PREVIOUSLY_RUN_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - //final EnhancedCentreEntityQueryCriteria, ?> previouslyRunCriteriaEntity = createCriteriaValidationPrototype(miType, saveAsName, previouslyRunCentre, companionFinder, critGenerator, 0L, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); - final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( customObject, complementCriteriaEntityBeforeRunning( // complements previouslyRunCriteriaEntity instance @@ -259,8 +219,6 @@ private static ICompoundCondition0 centreConfigQueryFor(fina //Enhance entities with values defined with consumer in each dynamic property. processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); -// //Enhance rendering hints with styles for each dynamic column. -// processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); final List list = new ArrayList<>(); // list.add(isRunning ? previouslyRunCriteriaEntity : null); From 91ce98a1c4f661d525f7d720b07a43d90594d0de Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 03:07:18 +0200 Subject: [PATCH 21/76] #2224 Abstract out centre running logic with the option of simplified running in case of API. --- .../web/resources/webui/CriteriaResource.java | 182 ++++++++++++------ .../web/utils/EntityCentreAPIImpl.java | 66 ++----- 2 files changed, 142 insertions(+), 106 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index def2956df1c..eed1b6d9f9d 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -740,84 +740,144 @@ public Representation put(final Representation envelope) { ); } } - final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( + + final var resultList = run( + of(t2(updatedFreshCentre, previouslyRunCentre)), + isRunning, + customObject, - complementCriteriaEntityBeforeRunning( // complements previouslyRunCriteriaEntity instance - previouslyRunCriteriaEntity, - webUiConfig, - companionFinder, - user, - critGenerator, - entityFactory, - centreContextHolder, - eccCompanion, - mmiCompanion, - userCompanion, - sharingModel - ) + previouslyRunCriteriaEntity, + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + eccCompanion, + mmiCompanion, + userCompanion, + sharingModel, + + miType, + saveAsName, + device(), + centre ); - if (isRunning) { - final var updatedSavedCentre = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - final var changedCriteriaIndication = createChangedCriteriaIndication(updatedFreshCentre, updatedSavedCentre); - updateResultantCustomObject(previouslyRunCriteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), miType, saveAsName, previouslyRunCentre, pair.getKey(), of(changedCriteriaIndication)); + + // NOTE: the following line can be the example how 'criteria running' server errors manifest to the client application + // throw new IllegalStateException("Illegal state during criteria running."); + LOGGER.debug("CRITERIA_RESOURCE: run finished."); + return restUtil.rawListJsonRepresentation(resultList.toArray()); + } finally { + if (lockAcquired) { + // it is necessary to unlock Lock in finally block (exceptions will be handled properly then) + lock.unlock(); } + } + }, restUtil); + } - // Running the rendering customiser for result set of entities. - pair.getKey().put("renderingHints", createRenderingHints(pair.getValue())); + public static List run( + final Optional> updatedFreshCentreAndPreviouslyRunCentre, + final boolean isRunning, - // Apply primary and secondary action selectors - pair.getKey().putAll(linkedMapOf(createPrimaryActionIndicesForCentre(pair.getValue(), centre))); - pair.getKey().putAll(linkedMapOf(createSecondaryActionIndicesForCentre(pair.getValue(), centre))); - pair.getKey().putAll(linkedMapOf(createPropertyActionIndicesForCentre(pair.getValue(), centre))); + final Map customObject, + final EnhancedCentreEntityQueryCriteria, ?> criteriaEntity, + final IWebUiConfig webUiConfig, + final ICompanionObjectFinder companionFinder, + final User user, + final ICriteriaGenerator critGenerator, + final EntityFactory entityFactory, + final CentreContextHolder centreContextHolder, + final EntityCentreConfigCo eccCompanion, + final MainMenuItemCo mmiCompanion, + final IUser userCompanion, + final ICentreConfigSharingModel sharingModel, - // Build dynamic properties object - final List>, Optional, ?>>>> resPropsWithContext = getDynamicResultProperties( - centre, + final Class> miType, + final Optional saveAsName, + final DeviceProfile device, + final EntityCentre> centre + ) { + final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( + customObject, + complementCriteriaEntityBeforeRunning( // complements criteriaEntity instance + criteriaEntity, webUiConfig, companionFinder, user, critGenerator, entityFactory, centreContextHolder, - previouslyRunCriteriaEntity, - device(), eccCompanion, mmiCompanion, userCompanion, - sharingModel); + sharingModel + ) + ); + final var skipCustomObjectCalculations = updatedFreshCentreAndPreviouslyRunCentre.isEmpty(); + if (!skipCustomObjectCalculations) { + if (isRunning) { + final ICentreDomainTreeManagerAndEnhancer updatedFreshCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._1; + final ICentreDomainTreeManagerAndEnhancer previouslyRunCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._2; + final var updatedSavedCentre = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final var changedCriteriaIndication = createChangedCriteriaIndication(updatedFreshCentre, updatedSavedCentre); + updateResultantCustomObject(criteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), miType, saveAsName, previouslyRunCentre, pair.getKey(), of(changedCriteriaIndication)); + } - pair.getKey().put("dynamicColumns", createDynamicProperties(resPropsWithContext)); + // Running the rendering customiser for result set of entities. + pair.getKey().put("renderingHints", createRenderingHints(pair.getValue(), centre)); - Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( - centre, - centre.getCustomPropertiesDefinitions(), - centre.getCustomPropertiesAsignmentHandler(), - pair.getValue().stream()); + // Apply primary and secondary action selectors + pair.getKey().putAll(linkedMapOf(createPrimaryActionIndicesForCentre(pair.getValue(), centre))); + pair.getKey().putAll(linkedMapOf(createSecondaryActionIndicesForCentre(pair.getValue(), centre))); + pair.getKey().putAll(linkedMapOf(createPropertyActionIndicesForCentre(pair.getValue(), centre))); + } - //Enhance entities with values defined with consumer in each dynamic property. - processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); - //Enhance rendering hints with styles for each dynamic column. - processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); + // Build dynamic properties object + final List>, Optional, ?>>>> resPropsWithContext = getDynamicResultProperties( + centre, + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + criteriaEntity, + device, + eccCompanion, + mmiCompanion, + userCompanion, + sharingModel); - final var list = new ArrayList(); - list.add(isRunning ? previouslyRunCriteriaEntity : null); - list.add(pair.getKey()); + if (!skipCustomObjectCalculations) { + pair.getKey().put("dynamicColumns", createDynamicProperties(resPropsWithContext, centre)); + } - // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence - // FIXME Need to investigate why this is the case. - processedEntities.forEach(list::add); + Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( + centre, + centre.getCustomPropertiesDefinitions(), + centre.getCustomPropertiesAsignmentHandler(), + pair.getValue().stream()); + + //Enhance entities with values defined with consumer in each dynamic property. + processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); + if (!skipCustomObjectCalculations) { + //Enhance rendering hints with styles for each dynamic column. + processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); + } - // NOTE: the following line can be the example how 'criteria running' server errors manifest to the client application - // throw new IllegalStateException("Illegal state during criteria running."); - LOGGER.debug("CRITERIA_RESOURCE: run finished."); - return restUtil.rawListJsonRepresentation(list.toArray()); - } finally { - if (lockAcquired) { - // it is necessary to unlock Lock in finally block (exceptions will be handled properly then) - lock.unlock(); - } - } - }, restUtil); + final var list = new ArrayList(); + if (!skipCustomObjectCalculations) { + list.add(isRunning ? criteriaEntity : null); + list.add(pair.getKey()); + } + + // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence + // FIXME Need to investigate why this is the case. + processedEntities.forEach(list::add); + + return list; } /// A method to try acquiring a lock for running a centre. @@ -842,7 +902,7 @@ private boolean tryLocking(final Lock lock) { /// Calculates rendering hints for the given `entities`. /// - private List createRenderingHints(final List> entities) { + private static List createRenderingHints(final List> entities, final EntityCentre> centre) { final Optional> renderingCustomiser = centre.getRenderingCustomiser(); if (renderingCustomiser.isPresent()) { final IRenderingCustomiser renderer = renderingCustomiser.get(); @@ -871,7 +931,7 @@ public static Stream> enhanceResultEntitiesWithDynamicProperty }); } - private Stream> enhanceResultEntitiesWithDynamicPropertyRenderingHints( + private static Stream> enhanceResultEntitiesWithDynamicPropertyRenderingHints( final Stream> stream, List>, Optional, ?>>>> resPropsWithContext, final List renderingHints) { @@ -937,7 +997,7 @@ private Stream> enhanceResultEntitiesWithDynamicPropertyRender return resList; } - private Map>> createDynamicProperties(final List>, Optional, ?>>>> resPropsWithContext) { + private static Map>> createDynamicProperties(final List>, Optional, ?>>>> resPropsWithContext, final EntityCentre> centre) { final Map>> dynamicColumns = new LinkedHashMap<>(); resPropsWithContext.forEach(resPropWithContext -> { centre.getDynamicColumnBuilderFor(resPropWithContext.getKey()) @@ -990,7 +1050,7 @@ private static Map updateResultantCustomObject( final IUser userCompanion, final ICentreConfigSharingModel sharingModel) { if (queryEnhancerConfig.isPresent()) { - return Optional.of(new Pair<>( + return of(new Pair<>( queryEnhancerConfig.get().getKey(), CentreResourceUtils.createCentreContext( true, // full context, fully-fledged restoration. This means that IQueryEnhancer descendants (centre query enhancers) could use IContextDecomposer for context decomposition on deep levels. diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index cc19172ef54..1085ad69897 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -178,55 +178,31 @@ private static ICompoundCondition0 centreConfigQueryFor(fina return left(generationResult); } - final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( + final var resultList = run( + empty(), + isRunning, + customObject, - complementCriteriaEntityBeforeRunning( // complements previouslyRunCriteriaEntity instance - freshCriteriaEntity, - webUiConfig, - companionFinder, - user, - critGenerator, - entityFactory, - centreContextHolder, - eccCompanion, - mmiCompanion, - userCompanion, - sharingModel - ) + (EnhancedCentreEntityQueryCriteria, ?>) freshCriteriaEntity, + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + eccCompanion, + mmiCompanion, + userCompanion, + sharingModel, + + miType, + saveAsName, + device, + centre ); - Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( - centre, - centre.getCustomPropertiesDefinitions(), - centre.getCustomPropertiesAsignmentHandler(), - pair.getValue().stream()); - - // Build dynamic properties object - final var resPropsWithContext = getDynamicResultProperties( - centre, - webUiConfig, - companionFinder, - user, - critGenerator, - entityFactory, - centreContextHolder, - (EnhancedCentreEntityQueryCriteria, ?>) freshCriteriaEntity, - device, - eccCompanion, - mmiCompanion, - userCompanion, - sharingModel); - - //Enhance entities with values defined with consumer in each dynamic property. - processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); - final List list = new ArrayList<>(); - // list.add(isRunning ? previouslyRunCriteriaEntity : null); - // list.add(pair.getKey()); - - // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence - // FIXME Need to investigate why this is the case. - processedEntities.forEach(entity -> list.add((T) entity) ); + resultList.forEach(entity -> list.add((T) entity) ); return Either.right(list); } finally { From 4101f36606401fce5a339c448361b1f0f8fa343e Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 15:49:23 +0200 Subject: [PATCH 22/76] #2224 Reduce exposure of internal methods that are not needed anymore. --- .../resources/webui/CentreResourceUtils.java | 2 +- .../web/resources/webui/CriteriaResource.java | 113 +++++------------- 2 files changed, 29 insertions(+), 86 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index fb121b281c1..e99288e8187 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -245,7 +245,7 @@ public static boolean isAutoRunning(final Map customObject) { /// /// @param updatedPreviouslyRunCriteriaEntity criteria entity created from PREVIOUSLY_RUN surrogate centre, which was potentially updated from FRESH (in case of "running" action), but not yet actually used for running /// - public static , M extends EnhancedCentreEntityQueryCriteria>> Pair, List>> createCriteriaMetaValuesCustomObjectWithResult( + static , M extends EnhancedCentreEntityQueryCriteria>> Pair, List>> createCriteriaMetaValuesCustomObjectWithResult( final Map customObject, final M updatedPreviouslyRunCriteriaEntity) { final Map resultantCustomObject = new LinkedHashMap<>(); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index eed1b6d9f9d..c66256721a7 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -205,25 +205,14 @@ public Representation get() { final Optional preliminarySaveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device())); // updating is required from upstream configuration; if (!LINK_CONFIG_TITLE.equals(preliminarySaveAsName.get())) { // (but not for link configuration); - actualSaveAsNameAndSharedIndicator = updateFromUpstream(configUuid.get(), preliminarySaveAsName, miType, device(), eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder); + actualSaveAsNameAndSharedIndicator = updateFromUpstream(configUuid.get(), preliminarySaveAsName); } else { actualSaveAsNameAndSharedIndicator = t2(preliminarySaveAsName, false); } } else { // if there is no FRESH configuration then there are no [link, own save-as] configuration with the specified uuid; // however there can exist [base, shared] config for other user with the specified uuid; - actualSaveAsNameAndSharedIndicator = firstTimeLoadingFrom( - validateUuidAndGetUpstreamConfig(configUuid.get(), miType, device(), eccCompanion, user).orElseThrow(Result::asRuntime), - miType, - device(), - eccCompanion, - user, - webUiConfig, - mmiCompanion, - userCompanion, - sharingModel, - companionFinder - ); + actualSaveAsNameAndSharedIndicator = firstTimeLoadingFrom(validateUuidAndGetUpstreamConfig(configUuid.get()).orElseThrow(Result::asRuntime)); } actualSaveAsName = actualSaveAsNameAndSharedIndicator._1; final boolean isInheritedFromShared = actualSaveAsNameAndSharedIndicator._2; @@ -237,7 +226,7 @@ public Representation get() { final Optional preliminarySaveAsName = retrievePreferredConfigName(user, miType, device(), companionFinder, webUiConfig); // preferred configuration should be loaded resolvedConfigUuid = updateCentreConfigUuid(user, miType, preliminarySaveAsName, device(), eccCompanion); if (resolvedConfigUuid.isPresent()) { // preferred config can be inherited from base / shared (link configs can not be preferred, no need to check it here) - actualSaveAsName = updateFromUpstream(resolvedConfigUuid.get(), preliminarySaveAsName, miType, device(), eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, sharingModel, companionFinder)._1; // it needs updating from upstream -- only for the configs that has configUuid aka non-default + actualSaveAsName = updateFromUpstream(resolvedConfigUuid.get(), preliminarySaveAsName)._1; // it needs updating from upstream -- only for the configs that has configUuid aka non-default } else { actualSaveAsName = preliminarySaveAsName; } @@ -255,15 +244,9 @@ public Representation get() { /// Validates `configUuid` to ensure the configuration exists and can be shared with the current `user`. /// - public static Either validateUuidAndGetUpstreamConfig( - final String configUuid, - final Class> miType, - final DeviceProfile device, - final EntityCentreConfigCo eccCompanion, - final User user - ) { + private Either validateUuidAndGetUpstreamConfig(final String configUuid) { // we look only for owners; "owning" is indicated by presence of SAVED configuration with the specified uuid - final var savedConfigOptForOtherUser = findConfigOptByUuid(configUuid, miType, device, SAVED_CENTRE_NAME, eccCompanion); + final var savedConfigOptForOtherUser = findConfigOptByUuid(configUuid, miType, device(), SAVED_CENTRE_NAME, eccCompanion); if (savedConfigOptForOtherUser.isEmpty()) { // configuration does not exist (no SAVED surrogate centre) -- legitimate error; this can happen if configuration has been already deleted or didn't exist due to URI mistyping return left(failure(CONFIG_DOES_NOT_EXIST)); @@ -274,7 +257,7 @@ public static Either validateUuidAndGetUpstreamConfi // so from two categories [base, shared] we can only consider [shared]; // so, at this stage, we prohibit loading of [inherited from shared] configurations for base users -- not really practical scenario and possibly will never be required return left(failure(format(CONFIG_COULD_NOT_BE_SHARED_WITH_BASE_USER, user))); - } else if (LINK_CONFIG_TITLE.equals(obtainTitleFrom(savedConfigOptForOtherUser.get().getTitle(), SAVED_CENTRE_NAME, device))) { + } else if (LINK_CONFIG_TITLE.equals(obtainTitleFrom(savedConfigOptForOtherUser.get().getTitle(), SAVED_CENTRE_NAME, device()))) { // link-configs can not be shared anywhere neither from base user nor from base/non-base user that gave its uuid as part of sharing process return left(failure(LINK_CONFIG_COULD_NOT_BE_SHARED)); } @@ -283,21 +266,10 @@ public static Either validateUuidAndGetUpstreamConfi /// Implements the first-time loading of a configuration into `inherited from base / shared`. /// - public static T2, Boolean> firstTimeLoadingFrom( - final EntityCentreConfig upstreamConfig, - final Class> miType, - final DeviceProfile device, - final EntityCentreConfigCo eccCompanion, - final User user, - final IWebUiConfig webUiConfig, - final MainMenuItemCo mmiCompanion, - final IUser userCompanion, - final ICentreConfigSharingModel sharingModel, - final ICompanionObjectFinder companionFinder - ) { + private T2, Boolean> firstTimeLoadingFrom(final EntityCentreConfig upstreamConfig) { final String configUuid = upstreamConfig.getConfigUuid(); final User upstreamConfigCreator = upstreamConfig.getOwner(); - final String preliminarySaveAsName = obtainTitleFrom(upstreamConfig.getTitle(), SAVED_CENTRE_NAME, device); + final String preliminarySaveAsName = obtainTitleFrom(upstreamConfig.getTitle(), SAVED_CENTRE_NAME, device()); final Optional actualSaveAsName; if (upstreamConfigCreator.isBase() && areEqual(upstreamConfigCreator, user.getBasedOnUser() /*id-only-proxy*/)) { // we have base => basedOn relationship between current user and the creator of savedConfig; @@ -305,25 +277,25 @@ public static T2, Boolean> firstTimeLoadingFrom( // CentreUpdater.updateCentre and .updateDifferences method should take care of that process; // at least FRESH config should be prepared -- making it preferred requires existence actualSaveAsName = of(preliminarySaveAsName); - updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); return t2(actualSaveAsName, false); } else { // if current user does not have access to shared configuration then sharing process should be prevented sharingModel.isSharedWith(configUuid, user).ifFailure(Result::throwRuntime); // current user gets uuid as part of sharing process from other base/non-base user; // need to determine non-conflicting name for current user from preliminarySaveAsName - actualSaveAsName = of(determineNonConflictingName(preliminarySaveAsName, -1, miType, device, eccCompanion, user)); + actualSaveAsName = of(determineNonConflictingName(preliminarySaveAsName, -1)); final Function>>>> createInheritedFromShared = surrogateName -> runAutomatically -> newDescription -> uuid -> saveNewEntityCentreManager( upstreamConfig.getConfigBody(), miType, user, - NAME_OF.apply(surrogateName).apply(actualSaveAsName).apply(device), + NAME_OF.apply(surrogateName).apply(actualSaveAsName).apply(device()), newDescription, eccCompanion, mmiCompanion, ecc -> uuid.map(ecc::setConfigUuid).orElse(ecc).setRunAutomatically(runAutomatically) ); - final EntityCentreConfig freshConfigForCreator = findConfigOptByUuid(configUuid, upstreamConfigCreator, miType, device, FRESH_CENTRE_NAME, eccCompanion).get(); // need to retrieve FRESH config to get 'desc' -- that's because SAVED centres haven't stored descriptions, only FRESH do; this config must be present, otherwise savedConfigForOtherUser would not exist + final EntityCentreConfig freshConfigForCreator = findConfigOptByUuid(configUuid, upstreamConfigCreator, miType, device(), FRESH_CENTRE_NAME, eccCompanion).get(); // need to retrieve FRESH config to get 'desc' -- that's because SAVED centres haven't stored descriptions, only FRESH do; this config must be present, otherwise savedConfigForOtherUser would not exist createInheritedFromShared.apply(FRESH_CENTRE_NAME).apply(freshConfigForCreator.isRunAutomatically()).apply(freshConfigForCreator.getDesc()).accept(of(configUuid)); // update (FRESH only) with upstream description and configUuid during creation createInheritedFromShared.apply(SAVED_CENTRE_NAME).apply(false).apply(null).accept(empty()); return t2(actualSaveAsName, true); @@ -337,43 +309,24 @@ public static T2, Boolean> firstTimeLoadingFrom( /// If that still conflicts, numeric suffixes `(shared 1)` through `(shared 9)` are attempted. /// If no unique name can be generated, an error is thrown. /// - private static String determineNonConflictingName( - final String preliminaryName, - final int index, - final Class> miType, - final DeviceProfile device, - final EntityCentreConfigCo eccCompanion, - final User user - ) { + private String determineNonConflictingName(final String preliminaryName, final int index) { final String name; if (index > 9) { throw failure(format(COULD_NOT_LOAD_CONFLICTING_SHARED_CONFIGURATION, preliminaryName)); } else { name = preliminaryName + (index == -1 ? "" : format(CONFLICTING_TITLE_SUFFIX, index == 0 ? "" : " " + index)); } - return findConfigOpt(miType, user, NAME_OF.apply(FRESH_CENTRE_NAME).apply(of(name)).apply(device), eccCompanion, FETCH_CONFIG) - .map(conflictingConfig -> determineNonConflictingName(preliminaryName, index + 1, miType, device, eccCompanion, user)) + return findConfigOpt(miType, user, NAME_OF.apply(FRESH_CENTRE_NAME).apply(of(name)).apply(device()), eccCompanion, FETCH_CONFIG) + .map(conflictingConfig -> determineNonConflictingName(preliminaryName, index + 1)) .orElse(name); } /// Updates a configuration already loaded by the `user` with the concrete `configUuid` /// from its upstream configuration, if it is inherited. /// - public static T2, Boolean> updateFromUpstream( - final String configUuid, - final Optional saveAsName, - final Class> miType, - final DeviceProfile device, - final EntityCentreConfigCo eccCompanion, - final User user, - final IWebUiConfig webUiConfig, - final MainMenuItemCo mmiCompanion, - final IUser userCompanion, - final ICentreConfigSharingModel sharingModel, - final ICompanionObjectFinder companionFinder - ) { + private T2, Boolean> updateFromUpstream(final String configUuid, final Optional saveAsName) { // look for config creator - final Optional savedConfigOpt = findConfigOptByUuid(configUuid, miType, device, SAVED_CENTRE_NAME, eccCompanion); + final Optional savedConfigOpt = findConfigOptByUuid(configUuid, miType, device(), SAVED_CENTRE_NAME, eccCompanion); if (savedConfigOpt.isPresent()) { // the creator is current user or other final EntityCentreConfig savedConfig = savedConfigOpt.get(); @@ -382,20 +335,20 @@ public static T2, Boolean> updateFromUpstream( // current user didn't create this config -> it is inherited and needs updating if (savedConfigCreator.isBase() && areEqual(savedConfigCreator, user.getBasedOnUser() /*id-only-proxy*/)) { // inherited from base - if (isCentreChanged(saveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, companionFinder)) { // if there are some user changes, only SAVED surrogate must be updated; if such centre will be discarded the base user changes will be loaded immediately - removeCentres(user, miType, device, saveAsName, eccCompanion, SAVED_CENTRE_NAME); + if (isCentreChanged(saveAsName)) { // if there are some user changes, only SAVED surrogate must be updated; if such centre will be discarded the base user changes will be loaded immediately + removeCentres(user, miType, device(), saveAsName, eccCompanion, SAVED_CENTRE_NAME); } else { // otherwise base user changes will be loaded immediately after centre loading - removeCentres(user, miType, device, saveAsName, eccCompanion, FRESH_CENTRE_NAME, SAVED_CENTRE_NAME); + removeCentres(user, miType, device(), saveAsName, eccCompanion, FRESH_CENTRE_NAME, SAVED_CENTRE_NAME); } - updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); // do not leave only FRESH centre out of two (FRESH + SAVED) => update SAVED centre explicitly + updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); // do not leave only FRESH centre out of two (FRESH + SAVED) => update SAVED centre explicitly - makePreferred(user, miType, saveAsName, device, companionFinder, webUiConfig); // inherited from base always gets preferred on loading; must leave it preferred after deletion + makePreferred(user, miType, saveAsName, device(), companionFinder, webUiConfig); // inherited from base always gets preferred on loading; must leave it preferred after deletion } else { if (sharingModel.isSharedWith(configUuid, user).isSuccessful()) { // inherited from shared - updateInheritedFromShared(savedConfig, miType, device, saveAsName, user, eccCompanion, of(() -> isCentreChanged(saveAsName, miType, device, eccCompanion, user, webUiConfig, mmiCompanion, userCompanion, companionFinder))); - return t2(of(obtainTitleFrom(savedConfig.getTitle(), SAVED_CENTRE_NAME, device)), true); + updateInheritedFromShared(savedConfig, miType, device(), saveAsName, user, eccCompanion, of(() -> isCentreChanged(saveAsName))); + return t2(of(obtainTitleFrom(savedConfig.getTitle(), SAVED_CENTRE_NAME, device())), true); } // already loaded inherited from shared config was made unshared; the inherited from shared configuration now acts like own save-as configuration } } // if the current user is creator then no 'updating from upstream' is needed -- it is own save-as @@ -436,20 +389,10 @@ private T2, Optional> prepareLinkConfigInfrastructure() /// Determines whether the FRESH configuration has been modified compared to the SAVED one. /// - private static boolean isCentreChanged( - final Optional actualSaveAsName, - final Class> miType, - final DeviceProfile device, - final EntityCentreConfigCo eccCompanion, - final User user, - final IWebUiConfig webUiConfig, - final MainMenuItemCo mmiCompanion, - final IUser userCompanion, - final ICompanionObjectFinder companionFinder - ) { + private boolean isCentreChanged(final Optional actualSaveAsName) { return isFreshCentreChanged( - updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder), - updateCentre(user, miType, SAVED_CENTRE_NAME, actualSaveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder) + updateCentre(user, miType, FRESH_CENTRE_NAME, actualSaveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder), + updateCentre(user, miType, SAVED_CENTRE_NAME, actualSaveAsName, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder) ); } From 658c81fd2d1c4f40674df0659c16b6f1ddc6a542 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 16:57:38 +0200 Subject: [PATCH 23/76] #2224 Refactor further to improve comprehension. --- .../resources/webui/CentreResourceUtils.java | 35 ++++++++ .../web/utils/EntityCentreAPIImpl.java | 86 +++++++++++-------- 2 files changed, 83 insertions(+), 38 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index e99288e8187..fb291e3e396 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -471,6 +471,41 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd } } + /// + /// + /// @param name -- surrogate name of the centre (fresh, previouslyRun etc.); + /// + public static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototypeForAPI( + final String name, + final Class> miType, + final Optional saveAsName, + final ICompanionObjectFinder companionFinder, + final ICriteriaGenerator critGenerator, + final User user, + final DeviceProfile device, + final IWebUiConfig webUiConfig, + final EntityCentreConfigCo eccCompanion, + final MainMenuItemCo mmiCompanion, + final IUser userCompanion, + final ICentreConfigSharingModel sharingModel) { + + // load / update fresh centre if it is not loaded yet / stale + final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre( + user, miType, name, saveAsName, + device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder + ); + + final M validationPrototype = createCriteriaValidationPrototype( + miType, saveAsName, originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, + user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel + ); + + return resetMetaStateForCriteriaValidationPrototype( + validationPrototype, + getOriginalManagedType(validationPrototype.getType(), originalCdtmae) + ); + } + /// Creates the validation prototype for criteria entity of concrete `miType`. /// /// The entity creation process uses rigorous generation of criteria type and the instance every time (based on cdtmae of concrete miType). diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 1085ad69897..eafd3c3f042 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -3,12 +3,10 @@ import com.google.inject.Inject; import ua.com.fielden.platform.criteria.generator.ICriteriaGenerator; import ua.com.fielden.platform.dao.IEntityDao; -import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.entity.factory.EntityFactory; import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; import ua.com.fielden.platform.entity.functional.centre.CentreContextHolder; -import ua.com.fielden.platform.entity.meta.MetaProperty; import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompoundCondition0; import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; import ua.com.fielden.platform.error.Result; @@ -23,38 +21,34 @@ import ua.com.fielden.platform.ui.config.MainMenuItem; import ua.com.fielden.platform.ui.config.MainMenuItemCo; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; -import ua.com.fielden.platform.utils.Pair; import ua.com.fielden.platform.web.app.IWebUiConfig; -import ua.com.fielden.platform.web.centre.CentreContext; import ua.com.fielden.platform.web.centre.EntityCentre; import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; +import ua.com.fielden.platform.web.interfaces.DeviceProfile; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import static java.lang.Class.forName; -import static java.util.Optional.*; -import static ua.com.fielden.platform.criteria.generator.impl.SynchroniseCriteriaWithModelHandler.CRITERIA_ENTITY_ID; -import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; -import static ua.com.fielden.platform.data.generator.IGenerator.shouldForceRegeneration; +import static java.util.Optional.empty; +import static java.util.Optional.of; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.from; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select; import static ua.com.fielden.platform.error.Result.failure; -import static ua.com.fielden.platform.streaming.ValueCollectors.toLinkedHashMap; import static ua.com.fielden.platform.types.either.Either.left; +import static ua.com.fielden.platform.types.either.Either.right; import static ua.com.fielden.platform.types.tuples.T2.t2; import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; import static ua.com.fielden.platform.utils.EntityUtils.fetchWithKeyAndDesc; import static ua.com.fielden.platform.web.centre.CentreUpdater.PREFIX_OF; -import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.findConfigOptByUuid; import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.getEntityCentre; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.MOBILE; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; -import static ua.com.fielden.platform.web.utils.EntityResourceUtils.getOriginalManagedType; public class EntityCentreAPIImpl implements EntityCentreAPI { private final ICompanionObjectFinder companionFinder; @@ -101,10 +95,14 @@ private static ICompoundCondition0 centreConfigQueryFor(fina .and().condition(centreConfigCondFor(uuid)); } - @Override - public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( - final String configUuid - ) { + public record ConfigSettings ( + Optional saveAsName, + User owner, + DeviceProfile device, + Class> miType + ) {} + + private static Either findConfigSettings(final String configUuid, final ICompanionObjectFinder companionFinder) { final IUser coUser = companionFinder.find(User.class, true); final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); final Optional freshConfigOpt = eccCompanion.getEntityOptional( @@ -134,11 +132,30 @@ private static ICompoundCondition0 centreConfigQueryFor(fina Class> miType = (Class>) miTypeGen; final var device = freshConfigOpt.get().getTitle().startsWith(MOBILE.name()) ? MOBILE : DESKTOP; + final Optional saveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); + if (LINK_CONFIG_TITLE.equals(saveAsName.get())) { + return left(failure("Default / Link configs are not available for API running (%s).".formatted(saveAsName))); + } + + return right(new ConfigSettings(saveAsName, user, device, miType)); + } + + @Override + public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( + final String configUuid + ) { + final var resultOrConfigSettings = findConfigSettings(configUuid, companionFinder); + if (resultOrConfigSettings.isLeft()) { + return left(resultOrConfigSettings.asLeft().value()); + } + + final var configSettings = resultOrConfigSettings.asRight().value(); + final User currentUser = userProvider.getUser(); try { - userProvider.setUser(user); + userProvider.setUser(configSettings.owner); final CentreContextHolder centreContextHolder = null; final Map customObject = mapOf(t2("@@action", RunActions.RUN.toString())); @@ -148,28 +165,21 @@ private static ICompoundCondition0 centreConfigQueryFor(fina final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); final IUser userCompanion = companionFinder.find(User.class); - final Optional saveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); - if (LINK_CONFIG_TITLE.equals(saveAsName.get())) { - return left(failure("Default / Link configs are not available for API running (%s).".formatted(saveAsName))); - } - - // load / update fresh centre if it is not loaded yet / stale - final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, FRESH_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - //final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); - final M validationPrototype = createCriteriaValidationPrototype(miType, saveAsName, originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); - final M freshCriteriaEntity = resetMetaStateForCriteriaValidationPrototype( - validationPrototype, - getOriginalManagedType(validationPrototype.getType(), originalCdtmae) + final M freshCriteriaEntity = createCriteriaValidationPrototypeForAPI( + FRESH_CENTRE_NAME, + configSettings.miType, configSettings.saveAsName, companionFinder, critGenerator, + configSettings.owner, configSettings.device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel ); - final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, miType, authorisationModel, securityTokenProvider); + final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, configSettings.miType, authorisationModel, securityTokenProvider); if (!validationResult.isSuccessful()) { return left(validationResult); } - final EntityCentre> centre = getEntityCentre(miType.getName(), webUiConfig); + final EntityCentre> centre = getEntityCentre(configSettings.miType.getName(), webUiConfig); final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, centre, isRunning, isSorting, customObject); // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result // otherwise, proceed with the request handling further to actually query the data @@ -186,7 +196,7 @@ private static ICompoundCondition0 centreConfigQueryFor(fina (EnhancedCentreEntityQueryCriteria, ?>) freshCriteriaEntity, webUiConfig, companionFinder, - user, + configSettings.owner, critGenerator, entityFactory, centreContextHolder, @@ -195,15 +205,15 @@ private static ICompoundCondition0 centreConfigQueryFor(fina userCompanion, sharingModel, - miType, - saveAsName, - device, + configSettings.miType, + configSettings.saveAsName, + configSettings.device, centre ); final List list = new ArrayList<>(); resultList.forEach(entity -> list.add((T) entity) ); - return Either.right(list); + return right(list); } finally { userProvider.setUser(currentUser); From 5133bea4fea3d7088c0af86e1bec31f7c02073bb Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 17:07:55 +0200 Subject: [PATCH 24/76] #2224 Reduce miType complexity. --- .../platform/web/resources/webui/CriteriaResource.java | 10 ++++------ .../platform/web/utils/EntityCentreAPIImpl.java | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index c66256721a7..6ce9b74817a 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -522,11 +522,10 @@ public static CriteriaIndication createChangedCriteriaIndication(final ICentreDo public static Result authoriseCriteriaEntity( final EnhancedCentreEntityQueryCriteria criteriaEntity, - final Class> miType, final IAuthorisationModel authorisationModel, final ISecurityTokenProvider securityTokenProvider ) { - final var entityAuthorisationResult = authoriseReading(getEntityType(miType).getSimpleName(), READ, authorisationModel, securityTokenProvider); + final var entityAuthorisationResult = authoriseReading(getEntityType(criteriaEntity.miType()).getSimpleName(), READ, authorisationModel, securityTokenProvider); return entityAuthorisationResult.isSuccessful() ? authoriseCriteria(criteriaEntity.queryProperties.get(), authorisationModel) : entityAuthorisationResult; @@ -534,7 +533,6 @@ public static Result authoriseCriteriaEntity( public static Result validateCriteriaBeforeRunning( final EnhancedCentreEntityQueryCriteria criteriaEntity, - final Class> miType, final IAuthorisationModel authorisationModel, final ISecurityTokenProvider securityTokenProvider ) { @@ -543,7 +541,7 @@ public static Result validateCriteriaBeforeRunning( return validationResult; } - final Result authorisationResult = authoriseCriteriaEntity(criteriaEntity, miType, authorisationModel, securityTokenProvider); + final Result authorisationResult = authoriseCriteriaEntity(criteriaEntity, authorisationModel, securityTokenProvider); if (!authorisationResult.isSuccessful()) { return authorisationResult; } @@ -636,7 +634,7 @@ public Representation put(final Representation envelope) { } // There is a need to validate criteria entity with the check for 'required' properties. If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. - final Result validationResult = validateCriteriaBeforeRunning(freshCentreAppliedCriteriaEntity, miType, authorisationModel, securityTokenProvider); + final Result validationResult = validateCriteriaBeforeRunning(freshCentreAppliedCriteriaEntity, authorisationModel, securityTokenProvider); if (!validationResult.isSuccessful()) { LOGGER.debug("CRITERIA_RESOURCE: run failed (validation failed)."); final var criteriaIndication = createCriteriaIndication((String) centreContextHolder.getModifHolder().get("@@wasRun"), updatedFreshCentre, miType, saveAsName, user, companionFinder, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion); @@ -672,7 +670,7 @@ public Representation put(final Representation envelope) { // Performs criteria validation on centre refresh / navigate. // It is needed if the user changed token role association between run and refresh actions. if (!isRunning) { - final Result authorisationResult = authoriseCriteriaEntity(previouslyRunCriteriaEntity, miType, authorisationModel, securityTokenProvider); + final Result authorisationResult = authoriseCriteriaEntity(previouslyRunCriteriaEntity, authorisationModel, securityTokenProvider); if (!authorisationResult.isSuccessful()) { LOGGER.debug("CRITERIA_RESOURCE: refresh failed (authorisation validation failed)."); return restUtil.resultJSONRepresentation( diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index eafd3c3f042..42243d49897 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -174,7 +174,7 @@ private static Either findConfigSettings(final String co configSettings.owner, configSettings.device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel ); - final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, configSettings.miType, authorisationModel, securityTokenProvider); + final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, authorisationModel, securityTokenProvider); if (!validationResult.isSuccessful()) { return left(validationResult); } From 4cdeb40b76e0c2850816b382e239f56ffbee3892 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 17:15:15 +0200 Subject: [PATCH 25/76] #2224 Reduce centre config determination complexity. --- .../web/resources/webui/CriteriaResource.java | 15 +++++++-------- .../platform/web/utils/EntityCentreAPIImpl.java | 6 ++---- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 6ce9b74817a..d41106f0680 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -82,8 +82,7 @@ import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.*; import static ua.com.fielden.platform.web.centre.CentreUtils.isFreshCentreChanged; import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; -import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.extractSaveAsName; -import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.wasLoadedPreviouslyAndConfigUuid; +import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.*; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; import static ua.com.fielden.platform.web.resources.webui.CriteriaIndication.*; import static ua.com.fielden.platform.web.resources.webui.EntityValidationResource.VALIDATION_COUNTER; @@ -550,11 +549,12 @@ public static Result validateCriteriaBeforeRunning( public static Result generateDataIfNeeded( final EnhancedCentreEntityQueryCriteria criteriaEntity, - final EntityCentre> centre, + final IWebUiConfig webUiConfig, final boolean isRunning, final boolean isSorting, final Map customObject ) { + final EntityCentre> centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) // then for an entity centre configuration check if a generator was provided final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); @@ -650,7 +650,7 @@ public Representation put(final Representation envelope) { freshCentreAppliedCriteriaEntity = null; } - final var generationResult = generateDataIfNeeded(freshCentreAppliedCriteriaEntity, centre, isRunning, isSorting, customObject); + final var generationResult = generateDataIfNeeded(freshCentreAppliedCriteriaEntity, webUiConfig, isRunning, isSorting, customObject); // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result // otherwise, proceed with the request handling further to actually query the data // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' @@ -701,8 +701,7 @@ public Representation put(final Representation envelope) { miType, saveAsName, - device(), - centre + device() ); // NOTE: the following line can be the example how 'criteria running' server errors manifest to the client application @@ -737,9 +736,9 @@ public static List run( final Class> miType, final Optional saveAsName, - final DeviceProfile device, - final EntityCentre> centre + final DeviceProfile device ) { + final EntityCentre> centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( customObject, complementCriteriaEntityBeforeRunning( // complements criteriaEntity instance diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 42243d49897..9a7bcfda0ef 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -179,8 +179,7 @@ private static Either findConfigSettings(final String co return left(validationResult); } - final EntityCentre> centre = getEntityCentre(configSettings.miType.getName(), webUiConfig); - final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, centre, isRunning, isSorting, customObject); + final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, webUiConfig, isRunning, isSorting, customObject); // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result // otherwise, proceed with the request handling further to actually query the data // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' @@ -207,8 +206,7 @@ private static Either findConfigSettings(final String co configSettings.miType, configSettings.saveAsName, - configSettings.device, - centre + configSettings.device ); final List list = new ArrayList<>(); From 807ce998e60099aecdcd811b06f1aae8d9c98c18 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sun, 22 Feb 2026 17:33:31 +0200 Subject: [PATCH 26/76] #2224 Further reduce complexity by using ConfigSettings more. --- .../resources/webui/CentreResourceUtils.java | 13 ++++------ .../web/resources/webui/ConfigSettings.java | 15 +++++++++++ .../web/resources/webui/CriteriaResource.java | 26 +++++++------------ .../web/utils/EntityCentreAPIImpl.java | 25 ++++-------------- 4 files changed, 34 insertions(+), 45 deletions(-) create mode 100644 platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index fb291e3e396..8ff181b7e36 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -477,12 +477,9 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd /// public static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototypeForAPI( final String name, - final Class> miType, - final Optional saveAsName, + final ConfigSettings configSettings, final ICompanionObjectFinder companionFinder, final ICriteriaGenerator critGenerator, - final User user, - final DeviceProfile device, final IWebUiConfig webUiConfig, final EntityCentreConfigCo eccCompanion, final MainMenuItemCo mmiCompanion, @@ -491,13 +488,13 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd // load / update fresh centre if it is not loaded yet / stale final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre( - user, miType, name, saveAsName, - device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder + configSettings.owner(), configSettings.miType(), name, configSettings.saveAsName(), + configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder ); final M validationPrototype = createCriteriaValidationPrototype( - miType, saveAsName, originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, - user, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel + configSettings.miType(), configSettings.saveAsName(), originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, + configSettings.owner(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel ); return resetMetaStateForCriteriaValidationPrototype( diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java new file mode 100644 index 00000000000..3719d7689d9 --- /dev/null +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java @@ -0,0 +1,15 @@ +package ua.com.fielden.platform.web.resources.webui; + +import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; +import ua.com.fielden.platform.web.interfaces.DeviceProfile; + +import java.util.Optional; + +public record ConfigSettings( + Optional saveAsName, + User owner, + DeviceProfile device, + Class> miType +) { +} diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index d41106f0680..8bb0d8ea789 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -683,6 +683,7 @@ public Representation put(final Representation envelope) { } final var resultList = run( + new ConfigSettings(saveAsName, user, device(), miType), of(t2(updatedFreshCentre, previouslyRunCentre)), isRunning, @@ -690,18 +691,13 @@ public Representation put(final Representation envelope) { previouslyRunCriteriaEntity, webUiConfig, companionFinder, - user, critGenerator, entityFactory, centreContextHolder, eccCompanion, mmiCompanion, userCompanion, - sharingModel, - - miType, - saveAsName, - device() + sharingModel ); // NOTE: the following line can be the example how 'criteria running' server errors manifest to the client application @@ -718,6 +714,7 @@ public Representation put(final Representation envelope) { } public static List run( + final ConfigSettings configSettings, final Optional> updatedFreshCentreAndPreviouslyRunCentre, final boolean isRunning, @@ -725,18 +722,13 @@ public static List run( final EnhancedCentreEntityQueryCriteria, ?> criteriaEntity, final IWebUiConfig webUiConfig, final ICompanionObjectFinder companionFinder, - final User user, final ICriteriaGenerator critGenerator, final EntityFactory entityFactory, final CentreContextHolder centreContextHolder, final EntityCentreConfigCo eccCompanion, final MainMenuItemCo mmiCompanion, final IUser userCompanion, - final ICentreConfigSharingModel sharingModel, - - final Class> miType, - final Optional saveAsName, - final DeviceProfile device + final ICentreConfigSharingModel sharingModel ) { final EntityCentre> centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( @@ -745,7 +737,7 @@ public static List run( criteriaEntity, webUiConfig, companionFinder, - user, + configSettings.owner(), critGenerator, entityFactory, centreContextHolder, @@ -760,9 +752,9 @@ public static List run( if (isRunning) { final ICentreDomainTreeManagerAndEnhancer updatedFreshCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._1; final ICentreDomainTreeManagerAndEnhancer previouslyRunCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._2; - final var updatedSavedCentre = updateCentre(user, miType, SAVED_CENTRE_NAME, saveAsName, device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final var updatedSavedCentre = updateCentre(configSettings.owner(), configSettings.miType(), SAVED_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); final var changedCriteriaIndication = createChangedCriteriaIndication(updatedFreshCentre, updatedSavedCentre); - updateResultantCustomObject(criteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), miType, saveAsName, previouslyRunCentre, pair.getKey(), of(changedCriteriaIndication)); + updateResultantCustomObject(criteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), configSettings.miType(), configSettings.saveAsName(), previouslyRunCentre, pair.getKey(), of(changedCriteriaIndication)); } // Running the rendering customiser for result set of entities. @@ -779,12 +771,12 @@ public static List run( centre, webUiConfig, companionFinder, - user, + configSettings.owner(), critGenerator, entityFactory, centreContextHolder, criteriaEntity, - device, + configSettings.device(), eccCompanion, mmiCompanion, userCompanion, diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 9a7bcfda0ef..d04fd0272f5 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -22,9 +22,8 @@ import ua.com.fielden.platform.ui.config.MainMenuItemCo; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; import ua.com.fielden.platform.web.app.IWebUiConfig; -import ua.com.fielden.platform.web.centre.EntityCentre; import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; -import ua.com.fielden.platform.web.interfaces.DeviceProfile; +import ua.com.fielden.platform.web.resources.webui.ConfigSettings; import java.util.ArrayList; import java.util.List; @@ -44,7 +43,6 @@ import static ua.com.fielden.platform.utils.EntityUtils.fetchWithKeyAndDesc; import static ua.com.fielden.platform.web.centre.CentreUpdater.PREFIX_OF; import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; -import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.getEntityCentre; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.MOBILE; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; @@ -95,13 +93,6 @@ private static ICompoundCondition0 centreConfigQueryFor(fina .and().condition(centreConfigCondFor(uuid)); } - public record ConfigSettings ( - Optional saveAsName, - User owner, - DeviceProfile device, - Class> miType - ) {} - private static Either findConfigSettings(final String configUuid, final ICompanionObjectFinder companionFinder) { final IUser coUser = companionFinder.find(User.class, true); final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); @@ -155,7 +146,7 @@ private static Either findConfigSettings(final String co try { - userProvider.setUser(configSettings.owner); + userProvider.setUser(configSettings.owner()); final CentreContextHolder centreContextHolder = null; final Map customObject = mapOf(t2("@@action", RunActions.RUN.toString())); @@ -169,9 +160,7 @@ private static Either findConfigSettings(final String co final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); final M freshCriteriaEntity = createCriteriaValidationPrototypeForAPI( - FRESH_CENTRE_NAME, - configSettings.miType, configSettings.saveAsName, companionFinder, critGenerator, - configSettings.owner, configSettings.device, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel + FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel ); final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, authorisationModel, securityTokenProvider); @@ -188,6 +177,7 @@ private static Either findConfigSettings(final String co } final var resultList = run( + configSettings, empty(), isRunning, @@ -195,18 +185,13 @@ private static Either findConfigSettings(final String co (EnhancedCentreEntityQueryCriteria, ?>) freshCriteriaEntity, webUiConfig, companionFinder, - configSettings.owner, critGenerator, entityFactory, centreContextHolder, eccCompanion, mmiCompanion, userCompanion, - sharingModel, - - configSettings.miType, - configSettings.saveAsName, - configSettings.device + sharingModel ); final List list = new ArrayList<>(); From 371612d1c3b52b4eb5dcc45710f5f52362b6ca85 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 23 Feb 2026 20:38:34 +0200 Subject: [PATCH 27/76] #2224 Reduce methods visibility back. Minor re-factoring and docs. --- .../resources/webui/CentreResourceUtils.java | 51 ++++++++----------- .../web/utils/EntityCentreAPIImpl.java | 2 +- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index 8ff181b7e36..5fcc2334406 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -471,43 +471,34 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd } } + /// Creates criteria validation prototype for surrogate `name` and [ConfigSettings]. /// + /// @param name surrogate name of the centre ([CentreUpdater#FRESH_CENTRE_NAME], [CentreUpdater#PREVIOUSLY_RUN_CENTRE_NAME] etc.); /// - /// @param name -- surrogate name of the centre (fresh, previouslyRun etc.); - /// - public static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototypeForAPI( - final String name, - final ConfigSettings configSettings, - final ICompanionObjectFinder companionFinder, - final ICriteriaGenerator critGenerator, - final IWebUiConfig webUiConfig, - final EntityCentreConfigCo eccCompanion, - final MainMenuItemCo mmiCompanion, - final IUser userCompanion, - final ICentreConfigSharingModel sharingModel) { - - // load / update fresh centre if it is not loaded yet / stale - final ICentreDomainTreeManagerAndEnhancer originalCdtmae = updateCentre( - configSettings.owner(), configSettings.miType(), name, configSettings.saveAsName(), - configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder - ); - - final M validationPrototype = createCriteriaValidationPrototype( - configSettings.miType(), configSettings.saveAsName(), originalCdtmae, companionFinder, critGenerator, CRITERIA_ENTITY_ID /* TODO prevVersion + 1 */, - configSettings.owner(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel - ); - - return resetMetaStateForCriteriaValidationPrototype( - validationPrototype, - getOriginalManagedType(validationPrototype.getType(), originalCdtmae) - ); + public static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototype( + final String name, + final ConfigSettings configSettings, + final ICompanionObjectFinder companionFinder, + final ICriteriaGenerator critGenerator, + final IWebUiConfig webUiConfig, + final EntityCentreConfigCo eccCompanion, + final MainMenuItemCo mmiCompanion, + final IUser userCompanion, + final ICentreConfigSharingModel sharingModel + ) { + // Load / update centre manager instance from persistence storage. + final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), name, configSettings.saveAsName(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + // Construct criteria validation prototype. + final M validationPrototype = createCriteriaValidationPrototype(configSettings.miType(), configSettings.saveAsName(), centreManager, companionFinder, critGenerator, -1L, configSettings.owner(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + // Apply meta-state resetting. + return resetMetaStateForCriteriaValidationPrototype(validationPrototype, getOriginalManagedType(validationPrototype.getType(), centreManager)); } /// Creates the validation prototype for criteria entity of concrete `miType`. /// /// The entity creation process uses rigorous generation of criteria type and the instance every time (based on cdtmae of concrete miType). /// - public static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototype( + static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototype( final Class> miType, final Optional saveAsName, final ICentreDomainTreeManagerAndEnhancer cdtmae, @@ -868,7 +859,7 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd /// UPDATE: resetting of the values has been enhanced (comparing to just invoking resetMetaState() on entity) with the functionality, for which the detailed comment is inside /// the method implementation. /// - public static , M extends EnhancedCentreEntityQueryCriteria>> M resetMetaStateForCriteriaValidationPrototype(final M criteriaValidationPrototype, final Class originalManagedType) { + static , M extends EnhancedCentreEntityQueryCriteria>> M resetMetaStateForCriteriaValidationPrototype(final M criteriaValidationPrototype, final Class originalManagedType) { // standard resetting of meta-values: copies values into originalValues for all properties: criteriaValidationPrototype.resetMetaState(); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index d04fd0272f5..fdfaea6ffa6 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -159,7 +159,7 @@ private static Either findConfigSettings(final String co final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); - final M freshCriteriaEntity = createCriteriaValidationPrototypeForAPI( + final M freshCriteriaEntity = createCriteriaValidationPrototype( FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel ); From aa7370fbc1ef870fa6c52b5bbcc7076207192c94 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 23 Feb 2026 21:50:50 +0200 Subject: [PATCH 28/76] #2224 More docs, re-factoring and minor improvements. --- .../resources/webui/CentreResourceUtils.java | 10 +-- .../web/resources/webui/ConfigSettings.java | 18 ++-- .../web/utils/EntityCentreAPIImpl.java | 82 ++++++++++++------- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index 5fcc2334406..5a39782d730 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -471,12 +471,12 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd } } - /// Creates criteria validation prototype for surrogate `name` and [ConfigSettings]. + /// Creates criteria validation prototype for `surrogateName` and [ConfigSettings]. /// - /// @param name surrogate name of the centre ([CentreUpdater#FRESH_CENTRE_NAME], [CentreUpdater#PREVIOUSLY_RUN_CENTRE_NAME] etc.); + /// @param surrogateName surrogate name of the centre ([CentreUpdater#FRESH_CENTRE_NAME], [CentreUpdater#PREVIOUSLY_RUN_CENTRE_NAME] etc.); /// public static , M extends EnhancedCentreEntityQueryCriteria>> M createCriteriaValidationPrototype( - final String name, + final String surrogateName, final ConfigSettings configSettings, final ICompanionObjectFinder companionFinder, final ICriteriaGenerator critGenerator, @@ -487,7 +487,7 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd final ICentreConfigSharingModel sharingModel ) { // Load / update centre manager instance from persistence storage. - final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), name, configSettings.saveAsName(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), surrogateName, configSettings.saveAsName(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); // Construct criteria validation prototype. final M validationPrototype = createCriteriaValidationPrototype(configSettings.miType(), configSettings.saveAsName(), centreManager, companionFinder, critGenerator, -1L, configSettings.owner(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); // Apply meta-state resetting. @@ -859,7 +859,7 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd /// UPDATE: resetting of the values has been enhanced (comparing to just invoking resetMetaState() on entity) with the functionality, for which the detailed comment is inside /// the method implementation. /// - static , M extends EnhancedCentreEntityQueryCriteria>> M resetMetaStateForCriteriaValidationPrototype(final M criteriaValidationPrototype, final Class originalManagedType) { + private static , M extends EnhancedCentreEntityQueryCriteria>> M resetMetaStateForCriteriaValidationPrototype(final M criteriaValidationPrototype, final Class originalManagedType) { // standard resetting of meta-values: copies values into originalValues for all properties: criteriaValidationPrototype.resetMetaState(); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java index 3719d7689d9..b63130e83d5 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java @@ -6,10 +6,16 @@ import java.util.Optional; +/// Convenient record holding Entity Centre configuration settings. +/// +/// @param saveAsName optional "save-as" name for named configuration or empty [Optional] for default one +/// @param owner a [User] that created the configuration (or own it through inheritance process) +/// @param device indicates whether the configuration belongs to [DeviceProfile#DESKTOP] namespace or [DeviceProfile#MOBILE] +/// @param miType menu item type for the configuration +/// public record ConfigSettings( - Optional saveAsName, - User owner, - DeviceProfile device, - Class> miType -) { -} + Optional saveAsName, + User owner, + DeviceProfile device, + Class> miType +) {} diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index fdfaea6ffa6..0989157edb0 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -8,6 +8,7 @@ import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; import ua.com.fielden.platform.entity.functional.centre.CentreContextHolder; import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompoundCondition0; +import ua.com.fielden.platform.entity_centre.exceptions.EntityCentreExecutionException; import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; import ua.com.fielden.platform.error.Result; import ua.com.fielden.platform.security.IAuthorisationModel; @@ -23,6 +24,7 @@ import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; +import ua.com.fielden.platform.web.interfaces.DeviceProfile; import ua.com.fielden.platform.web.resources.webui.ConfigSettings; import java.util.ArrayList; @@ -33,6 +35,7 @@ import static java.lang.Class.forName; import static java.util.Optional.empty; import static java.util.Optional.of; +import static org.apache.tika.utils.StringUtils.isBlank; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.from; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select; import static ua.com.fielden.platform.error.Result.failure; @@ -79,63 +82,80 @@ public EntityCentreAPIImpl( this.securityTokenProvider = securityTokenProvider; } - private static ICompoundCondition0 centreConfigQueryFor(final String surrogateName, final Optional maybeAlias) { + /// Creates centre configuration query for config `uuid` and `surrogateName` regardless of the device profile where it was created. + /// + /// @param maybeAlias alias for [EntityCentreConfig] which can be used for some outer query + /// + private static ICompoundCondition0 centreConfigQueryFor(final String uuid, final String surrogateName, final Optional maybeAlias) { final var selectStart = select(EntityCentreConfig.class); return maybeAlias.map(selectStart::as).orElse(selectStart) .where().begin() .prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(DESKTOP)) .or().prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(MOBILE)) - .end(); - } - - private static ICompoundCondition0 centreConfigQueryFor(final String uuid, final String surrogateName, final Optional maybeAlias) { - return centreConfigQueryFor(surrogateName, maybeAlias) + .end() .and().condition(centreConfigCondFor(uuid)); } - private static Either findConfigSettings(final String configUuid, final ICompanionObjectFinder companionFinder) { - final IUser coUser = companionFinder.find(User.class, true); + /// Determines [ConfigSettings] for a configuration, defined by `configUuid`. + /// These include who owns the configuration, it's "save-as" name and [DeviceProfile], where it was created. + /// + private static Either determineConfigurationSettings(final String configUuid, final ICompanionObjectFinder companionFinder) { + // Blank uuid does not represent any centre configuration. + if (isBlank(configUuid)) { + return left(failure("Configuration UUID [%s] is blank.".formatted(configUuid))); + } + + // Find "fresh" persisted configuration instance for which there is a corresponding "saved" instance for the same owner. final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); final Optional freshConfigOpt = eccCompanion.getEntityOptional( - from(centreConfigQueryFor(configUuid, FRESH_CENTRE_NAME, of("ecc")) - .and().exists( - centreConfigQueryFor(configUuid, SAVED_CENTRE_NAME, empty()) - .and().prop("owner").eq().extProp("ecc.owner") - .model() - ).model()) - .with( - fetchWithKeyAndDesc(EntityCentreConfig.class, true) - .with("owner", "menuItem", "title") - .fetchModel() - ).model() + from(centreConfigQueryFor(configUuid, FRESH_CENTRE_NAME, of("ecc")) + .and().exists(centreConfigQueryFor(configUuid, SAVED_CENTRE_NAME, empty()) + .and().prop("owner").eq().extProp("ecc.owner") + .model() + ).model() + ) + .with(fetchWithKeyAndDesc(EntityCentreConfig.class, true) + .with("owner", "menuItem", "title") + .fetchModel() + ).model() ); + // If there is no such configuration, return invalid `Result`. if (freshConfigOpt.isEmpty()) { - return left(Result.failure("Config with uuid %s does not exist.".formatted(configUuid))); + return left(failure("Configuration with [%s] UUID does not exist.".formatted(configUuid))); } - final User user = coUser.findUser(freshConfigOpt.get().getOwner().getKey()); - final Class miTypeGen; + + final var freshConfig = freshConfigOpt.get(); + // Determine owner. + final IUser coUser = companionFinder.find(User.class, true); + final User owner = coUser.findUser(freshConfig.getOwner().getKey()); + + // Determine menu item type. + final var miTypeName = freshConfig.getMenuItem().getKey(); + final Class> miType; try { - miTypeGen = forName(freshConfigOpt.get().getMenuItem().getKey()); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); + miType = (Class>) forName(miTypeName); + } catch (final ClassNotFoundException notFoundException) { + return left(failure(new EntityCentreExecutionException("Configuration's menu item type [%s] can not be found.".formatted(miTypeName), notFoundException))); } - Class> miType = (Class>) miTypeGen; - final var device = freshConfigOpt.get().getTitle().startsWith(MOBILE.name()) ? MOBILE : DESKTOP; - final Optional saveAsName = of(obtainTitleFrom(freshConfigOpt.get().getTitle(), FRESH_CENTRE_NAME, device)); + // Determine device. + final var device = freshConfig.getTitle().startsWith(MOBILE.name()) ? MOBILE : DESKTOP; + + // Determine "save-as" name. + final Optional saveAsName = of(obtainTitleFrom(freshConfig.getTitle(), FRESH_CENTRE_NAME, device)); if (LINK_CONFIG_TITLE.equals(saveAsName.get())) { - return left(failure("Default / Link configs are not available for API running (%s).".formatted(saveAsName))); + return left(failure("Link configuration [%s] is not available for API running.".formatted(saveAsName))); } - return right(new ConfigSettings(saveAsName, user, device, miType)); + return right(new ConfigSettings(saveAsName, owner, device, miType)); } @Override public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( final String configUuid ) { - final var resultOrConfigSettings = findConfigSettings(configUuid, companionFinder); + final var resultOrConfigSettings = determineConfigurationSettings(configUuid, companionFinder); if (resultOrConfigSettings.isLeft()) { return left(resultOrConfigSettings.asLeft().value()); } From 887199949f5d27aecca5476e2bf43fb359496561 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 23 Feb 2026 23:08:29 +0200 Subject: [PATCH 29/76] #2224 Further docs, re-factoring. Add generic handler of exceptions during execution. Extract constants. --- .../resources/webui/CentreResourceUtils.java | 12 +++-- .../web/resources/webui/CriteriaResource.java | 13 +++-- .../web/utils/EntityCentreAPIImpl.java | 47 ++++++++++--------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index 5a39782d730..ddc6d821f96 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -81,6 +81,7 @@ import static ua.com.fielden.platform.web.centre.CentreConfigUtils.*; import static ua.com.fielden.platform.web.centre.CentreContext.INSTANCEBASEDCONTINUATION_PROPERTY_NAME; import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.*; +import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.RunActions.*; import static ua.com.fielden.platform.web.resources.webui.CriteriaIndication.CHANGED; import static ua.com.fielden.platform.web.resources.webui.CriteriaIndication.NONE; import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; @@ -123,6 +124,9 @@ public class CentreResourceUtils> extends CentreUtil /// The key for customObject's value containing the config share validation error. static final String SHARE_ERROR = "shareError"; + /// The key for [RunActions]. + public static final String RUN_ACTION_KEY = "@@action"; + /// Private default constructor to prevent instantiation. /// private CentreResourceUtils() { @@ -217,7 +221,7 @@ static Map createCriteriaMetaValuesCustomObject(final Map customObject) { - return RunActions.RUN.toString().equals(customObject.get("@@action")); + return RUN.toString().equals(customObject.get(RUN_ACTION_KEY)); } /// A predicate to determine whether `customObject` represents action with `retrieveAll` option. @@ -227,7 +231,7 @@ public static boolean isRetrieveAll(final Map customObject) { /// A predicate to determine whether `customObject` represents action `Refresh`. public static boolean isRefreshing(final Map customObject) { - return RunActions.REFRESH.toString().equals(customObject.get("@@action")); + return REFRESH.toString().equals(customObject.get(RUN_ACTION_KEY)); } /// Returns `true` if `Sorting` action is performed, otherwise `false`. @@ -257,7 +261,7 @@ public static boolean isAutoRunning(final Map customObject) { // For refresh / navigate action this instance was not changed from previous run / refresh / navigate action. // For run action this instance was already updated from FRESH surrogate centre and includes "fresh" pageCapacity for the purposes of running // (the only way to make FRESH pageCapacity different from PREVIOUSLY_RUN is to change it using Customise Columns and press DISCARD on selection criteria). - final String action = (String) customObject.get("@@action"); + final String action = (String) customObject.get(RUN_ACTION_KEY); final int pageNumber = isRunning(customObject) ? 0 : (Integer) customObject.get("@@pageNumber"); final boolean retrieveAll = isRetrieveAll(customObject); if (isRunning(customObject)) { @@ -283,7 +287,7 @@ public static boolean isAutoRunning(final Map customObject) { data = refreshedData.getKey(); resultantCustomObject.put("summary", refreshedData.getValue()); } - } else if (RunActions.NAVIGATE.toString().equals(action)) { + } else if (NAVIGATE.toString().equals(action)) { final int pageCapacity = secondTick.getPageCapacity(); try { page = updatedPreviouslyRunCriteriaEntity.getPage(pageNumber, pageCapacity); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 8bb0d8ea789..eed37f87f1d 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -682,7 +682,7 @@ public Representation put(final Representation envelope) { } } - final var resultList = run( + final var resultList = executeEntityCentreConfiguration( new ConfigSettings(saveAsName, user, device(), miType), of(t2(updatedFreshCentre, previouslyRunCentre)), isRunning, @@ -713,13 +713,18 @@ public Representation put(final Representation envelope) { }, restUtil); } - public static List run( + /// Executes Entity Centre configuration with [ConfigSettings]. Uses `criteriaEntity` for execution. + /// + /// @param updatedFreshCentreAndPreviouslyRunCentre an [Optional] pair of centre managers for more comprehensive and complete running; + /// if passed, rendering hints / UI action indices / criteria indicator will be computed + /// + public static List executeEntityCentreConfiguration( final ConfigSettings configSettings, - final Optional> updatedFreshCentreAndPreviouslyRunCentre, + final Optional> updatedFreshCentreAndPreviouslyRunCentre, final boolean isRunning, - final Map customObject, final EnhancedCentreEntityQueryCriteria, ?> criteriaEntity, + final IWebUiConfig webUiConfig, final ICompanionObjectFinder companionFinder, final ICriteriaGenerator critGenerator, diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 0989157edb0..19744cb49b0 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -6,7 +6,6 @@ import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.entity.factory.EntityFactory; import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; -import ua.com.fielden.platform.entity.functional.centre.CentreContextHolder; import ua.com.fielden.platform.entity.query.fluent.EntityQueryProgressiveInterfaces.ICompoundCondition0; import ua.com.fielden.platform.entity_centre.exceptions.EntityCentreExecutionException; import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; @@ -17,6 +16,7 @@ import ua.com.fielden.platform.security.user.IUserProvider; import ua.com.fielden.platform.security.user.User; import ua.com.fielden.platform.types.either.Either; +import ua.com.fielden.platform.types.either.Left; import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; import ua.com.fielden.platform.ui.config.MainMenuItem; @@ -49,6 +49,7 @@ import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.MOBILE; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; +import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.RunActions.RUN; import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; public class EntityCentreAPIImpl implements EntityCentreAPI { @@ -99,6 +100,8 @@ private static ICompoundCondition0 centreConfigQueryFor(fina /// Determines [ConfigSettings] for a configuration, defined by `configUuid`. /// These include who owns the configuration, it's "save-as" name and [DeviceProfile], where it was created. /// + /// Returns [Left] for invalid configuration. + /// private static Either determineConfigurationSettings(final String configUuid, final ICompanionObjectFinder companionFinder) { // Blank uuid does not represent any centre configuration. if (isBlank(configUuid)) { @@ -155,59 +158,55 @@ private static Either determineConfigurationSettings(fin public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( final String configUuid ) { + // Find out the settings for configuration. Stop execution if the settings can not be determined or inapplicable. final var resultOrConfigSettings = determineConfigurationSettings(configUuid, companionFinder); if (resultOrConfigSettings.isLeft()) { return left(resultOrConfigSettings.asLeft().value()); } - final var configSettings = resultOrConfigSettings.asRight().value(); + // Determine current user to be returned back into the user provider once the execution has been performed. final User currentUser = userProvider.getUser(); - try { - + // Apply the configuration owner to user provider temporarily. userProvider.setUser(configSettings.owner()); - final CentreContextHolder centreContextHolder = null; - final Map customObject = mapOf(t2("@@action", RunActions.RUN.toString())); - - final boolean isRunning = true; - final boolean isSorting = false; + // Create custom object for centre running, containing all settings. + // The only necessary setting is indication that centre should be run (i.e. not a page refresh / navigate). + final Map customObject = mapOf(t2(RUN_ACTION_KEY, RUN.toString())); final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); final IUser userCompanion = companionFinder.find(User.class); - final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); - final M freshCriteriaEntity = createCriteriaValidationPrototype( - FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel - ); + // Create criteria entity for "fresh" surrogate configuration. + final M freshCriteriaEntity = createCriteriaValidationPrototype(FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + // Validate the criteria entity. Stop execution if it is invalid. final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, authorisationModel, securityTokenProvider); if (!validationResult.isSuccessful()) { return left(validationResult); } - final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, webUiConfig, isRunning, isSorting, customObject); - // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result - // otherwise, proceed with the request handling further to actually query the data - // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' + // Generate entities if the centre has IGenerator defined. Stop execution if generation result is not successful. + // `customObject` does not have parameter for generation forcing -- this parameter is not important. + final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, webUiConfig, true, false, customObject); if (!generationResult.isSuccessful()) { return left(generationResult); } - final var resultList = run( + // Perform actual running of `freshCriteriaEntity` with `configSettings`. + final var resultList = executeEntityCentreConfiguration( configSettings, empty(), - isRunning, - + true, customObject, (EnhancedCentreEntityQueryCriteria, ?>) freshCriteriaEntity, webUiConfig, companionFinder, critGenerator, entityFactory, - centreContextHolder, + null, eccCompanion, mmiCompanion, userCompanion, @@ -215,10 +214,12 @@ private static Either determineConfigurationSettings(fin ); final List list = new ArrayList<>(); - resultList.forEach(entity -> list.add((T) entity) ); + resultList.forEach(entity -> list.add((T) entity)); return right(list); - + } catch (final Exception exception) { + return left(failure(new EntityCentreExecutionException("Configuration with UUID [%s] and settings [%s] could not be executed.".formatted(configUuid, configSettings), exception))); } finally { + // Return original user back to user provider. userProvider.setUser(currentUser); } } From 6954eb975c3c6d6e5c666ce28444870c1b85b5e8 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 24 Feb 2026 00:18:10 +0200 Subject: [PATCH 30/76] #2224 Add 'exists' API method (+test). Remove unnecessary API type params. + Reduce test manual crafting of data (reuse web-related functions). --- .../platform/web/utils/EntityCentreAPI.java | 12 ++- .../web/utils/EntityCentreAPIImpl.java | 25 +++++-- .../web/utils/EntityCentreAPITest.java | 73 +++++++++++++------ 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java index 7f60ae91202..ff677f8d7f0 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java @@ -1,20 +1,18 @@ package ua.com.fielden.platform.web.utils; -import ua.com.fielden.platform.dao.IEntityDao; import ua.com.fielden.platform.entity.AbstractEntity; -import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; import ua.com.fielden.platform.error.Result; -import ua.com.fielden.platform.security.user.User; -import ua.com.fielden.platform.types.Hyperlink; import ua.com.fielden.platform.types.either.Either; -import ua.com.fielden.platform.types.tuples.T2; import java.util.List; public interface EntityCentreAPI { - , M extends EnhancedCentreEntityQueryCriteria>> - Either> entityCentreResult( + > Either> entityCentreResult( + String configUuid + ); + + Either entityCentreResultExists( String configUuid ); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 19744cb49b0..0ba4a131997 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -2,7 +2,6 @@ import com.google.inject.Inject; import ua.com.fielden.platform.criteria.generator.ICriteriaGenerator; -import ua.com.fielden.platform.dao.IEntityDao; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.entity.factory.EntityFactory; import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; @@ -155,8 +154,13 @@ private static Either determineConfigurationSettings(fin } @Override - public , M extends EnhancedCentreEntityQueryCriteria>> Either> entityCentreResult( - final String configUuid + public > Either> entityCentreResult(final String configUuid) { + return entityCentreResult(configUuid, empty()); + } + + private > Either> entityCentreResult( + final String configUuid, + final Optional maybeCustomPageCapacity ) { // Find out the settings for configuration. Stop execution if the settings can not be determined or inapplicable. final var resultOrConfigSettings = determineConfigurationSettings(configUuid, companionFinder); @@ -180,7 +184,7 @@ private static Either determineConfigurationSettings(fin final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); // Create criteria entity for "fresh" surrogate configuration. - final M freshCriteriaEntity = createCriteriaValidationPrototype(FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + final EnhancedCentreEntityQueryCriteria, ?> freshCriteriaEntity = createCriteriaValidationPrototype(FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); // Validate the criteria entity. Stop execution if it is invalid. final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, authorisationModel, securityTokenProvider); @@ -195,13 +199,18 @@ private static Either determineConfigurationSettings(fin return left(generationResult); } + // Adjust page capacity to some custom value, if present. + maybeCustomPageCapacity.ifPresent( + customPageCapacity -> freshCriteriaEntity.getCentreDomainTreeMangerAndEnhancer().getSecondTick().setPageCapacity(customPageCapacity) + ); + // Perform actual running of `freshCriteriaEntity` with `configSettings`. final var resultList = executeEntityCentreConfiguration( configSettings, empty(), true, customObject, - (EnhancedCentreEntityQueryCriteria, ?>) freshCriteriaEntity, + freshCriteriaEntity, webUiConfig, companionFinder, critGenerator, @@ -224,4 +233,10 @@ private static Either determineConfigurationSettings(fin } } + @Override + public Either entityCentreResultExists(final String configUuid) { + return entityCentreResult(configUuid, of(1)) + .map(result -> !result.isEmpty()); + } + } diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java index 00045f09b15..14410d55db8 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java @@ -5,7 +5,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import ua.com.fielden.platform.sample.domain.compound.TgCompoundEntity; -import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.security.user.User.system_users; import ua.com.fielden.platform.test_config.AbstractDaoTestCase; import ua.com.fielden.platform.test_config.H2OrPostgreSqlOrSqlServerContextSelectorForWebTests; import ua.com.fielden.platform.ui.config.EntityCentreConfig; @@ -13,9 +13,12 @@ import java.util.Map; +import static java.util.Optional.of; import static org.junit.Assert.*; import static ua.com.fielden.platform.types.tuples.T2.t2; import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; +import static ua.com.fielden.platform.web.centre.CentreUpdater.*; +import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; /// Tests for {@link EntityCentreAPI}. /// These should serve as an example of how end-application tests can be implemented. @@ -23,7 +26,7 @@ @RunWith(H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.class) public class EntityCentreAPITest extends AbstractDaoTestCase { - /// Creates Entity Centre persisted configuration for concrete surrogate version of it (FRESH or SAVED). + /// Creates Entity Centre persisted configuration for concrete surrogate version of it. /// private void createConfig( final String surrogateName, @@ -35,7 +38,7 @@ private void createConfig( save(new_(EntityCentreConfig.class) .setOwner(getUser()) .setMenuItem(mainMenuItem) - .setTitle("__________%s[saveAs]__________DIFFERENCES".formatted(surrogateName)) + .setTitle(NAME_OF.apply(surrogateName).apply(of("saveAs")).apply(DESKTOP)) .setConfigBody(new ObjectMapper().writeValueAsBytes(diffObject)) .setConfigUuid(uuid) ); @@ -45,33 +48,18 @@ private void createConfig( } @Test - public void executing_entityCentreResult_method_returns_entities_for_named_configuration_for_no_additional_criteria() { - setupUser(User.system_users.UNIT_TEST_USER, "example.tg.test"); - - //////// Initialise data for `MiTgCompoundEntity` Entity Centre execution. //////// - - // Create entity instance first. - save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); + public void executing_entityCentreResult_method_returns_entities_for_named_configuration_with_data() { // Define uuid for named configuration. final var uuid = "c95ec1ae-09e8-439f-92e7-880261fce023"; - // Create MainMenuItem instance conforming to MiTgCompoundEntity. - final var mainMenuItem = (MainMenuItem) save(new_(MainMenuItem.class).setKey("fielden.test_app.main.menu.compound.MiTgCompoundEntity")); - // Create empty diff object for Entity Centre configuration, meaning it will behave exactly as defined in `TgCompoundEntityWebUiConfig`. - final Map diffObject = mapOf( - t2("PROPERTIES", mapOf()) - ); - // Create "fresh" config, that would get executed through the API. - createConfig("FRESH", mainMenuItem, diffObject, uuid); - // Create "saved" config for current user too, which indicates that the user own it. - createConfig("SAVED", mainMenuItem, diffObject, uuid); + initTestData(uuid); - //////// Run Entity Centre through the API //////// + // Run Entity Centre through the API. final var entityCentreApi = getInstance(EntityCentreAPI.class); final var result = entityCentreApi.entityCentreResult(uuid); - //////// Check the result //////// + // Check the result. assertNotNull(result); assertTrue(result.isRight()); @@ -80,9 +68,48 @@ public void executing_entityCentreResult_method_returns_entities_for_named_confi assertEquals("KEY1", result.asRight().value().getFirst().getKey()); } + @Test + public void executing_entityCentreResultExists_method_returns_true_for_named_configuration_with_data() { + // Define uuid for named configuration. + final var uuid = "c95ec1ae-09e8-439f-92e7-880261fce023"; + + initTestData(uuid); + + // Run Entity Centre through the API. + + final var entityCentreApi = getInstance(EntityCentreAPI.class); + final var result = entityCentreApi.entityCentreResultExists(uuid); + + // Check the result. + + assertNotNull(result); + assertTrue(result.isRight()); + assertTrue(result.asRight().value()); + } + + private void initTestData(final String uuid) { + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + // Initialise data for `MiTgCompoundEntity` Entity Centre execution. + + // Create entity instance first. + save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); + // Create MainMenuItem instance conforming to MiTgCompoundEntity. + final var mainMenuItem = (MainMenuItem) save(new_(MainMenuItem.class).setKey("fielden.test_app.main.menu.compound.MiTgCompoundEntity")); + // Create empty diff object for Entity Centre configuration, meaning it will behave exactly as defined in `TgCompoundEntityWebUiConfig`. + final Map diffObject = mapOf( + t2("PROPERTIES" /* CentreUpdater.PROPERTIES */, mapOf()) + ); + + // Create "fresh" config, that would get executed through the API. + createConfig(FRESH_CENTRE_NAME, mainMenuItem, diffObject, uuid); + // Create "saved" config for current user too, which indicates that the user own it. + createConfig(SAVED_CENTRE_NAME, mainMenuItem, diffObject, uuid); + } + @Override protected void populateDomain() { - // do nothing to save time + // Do nothing to save time. } } \ No newline at end of file From 0cdaf0f1caf4f95dd118040ed22d575adca578ef Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 24 Feb 2026 00:25:01 +0200 Subject: [PATCH 31/76] #2224 Correct import. --- .../ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java index 0ba4a131997..71e1972d8b4 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java @@ -34,7 +34,7 @@ import static java.lang.Class.forName; import static java.util.Optional.empty; import static java.util.Optional.of; -import static org.apache.tika.utils.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.from; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select; import static ua.com.fielden.platform.error.Result.failure; From cfa11dae1e1b9e4863cbbd7a9106c0e43e728a12 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 24 Feb 2026 01:08:34 +0200 Subject: [PATCH 32/76] #2224 Rename API, its methods and implementation. --- .../platform/share/ShareActionProducer.java | 10 +++++----- ...CentreAPI.java => EntityCentreProcessor.java} | 6 +++--- .../ioc/IBasicWebApplicationServerModule.java | 6 +++--- .../TgTestWebApplicationServerIocModule.java | 6 +++--- ...pl.java => DefaultEntityCentreProcessor.java} | 8 ++++---- ...ITest.java => EntityCentreProcessorTest.java} | 16 ++++++++-------- 6 files changed, 26 insertions(+), 26 deletions(-) rename platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/{EntityCentreAPI.java => EntityCentreProcessor.java} (63%) rename platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/{EntityCentreAPIImpl.java => DefaultEntityCentreProcessor.java} (98%) rename platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/{EntityCentreAPITest.java => EntityCentreProcessorTest.java} (86%) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index cf77516bb81..f70f3be7504 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -12,7 +12,7 @@ import ua.com.fielden.platform.tiny.TinyHyperlinkCo; import ua.com.fielden.platform.types.Hyperlink; import ua.com.fielden.platform.web.centre.CentreContext; -import ua.com.fielden.platform.web.utils.EntityCentreAPI; +import ua.com.fielden.platform.web.utils.EntityCentreProcessor; import java.util.Base64; import java.util.Optional; @@ -34,16 +34,16 @@ public class ShareActionProducer extends DefaultEntityProducerWithContext { private CentreContextHolder centreContextHolder; - private final EntityCentreAPI entityCentreAPI; + private final EntityCentreProcessor entityCentreProcessor; @Inject ShareActionProducer( final EntityFactory factory, final ICompanionObjectFinder companionFinder, - final EntityCentreAPI entityCentreAPI) + final EntityCentreProcessor entityCentreProcessor) { super(factory, ShareAction.class, companionFinder); - this.entityCentreAPI = entityCentreAPI; + this.entityCentreProcessor = entityCentreProcessor; } @Override @@ -80,7 +80,7 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { } }); - final var result = entityCentreAPI.entityCentreResult("48b767df-bf45-48b7-932a-d2892eec47e8"); // Daniel.Truong Troy.Plecas + final var result = entityCentreProcessor.getResult("c95ec1ae-09e8-439f-92e7-880261fce023"); System.out.println("----------- API Execution (...) ------------"); if (result.isRight()) { result.asRight().value().forEach(ent -> { diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java similarity index 63% rename from platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java rename to platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index ff677f8d7f0..5703ef0a5be 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPI.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -6,13 +6,13 @@ import java.util.List; -public interface EntityCentreAPI { +public interface EntityCentreProcessor { - > Either> entityCentreResult( + > Either> getResult( String configUuid ); - Either entityCentreResultExists( + Either resultExists( String configUuid ); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java index 863019cdcb2..e48e7f7bffd 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/ioc/IBasicWebApplicationServerModule.java @@ -17,8 +17,8 @@ import ua.com.fielden.platform.web.test.server.TgTestWebApplicationServerIocModule; import ua.com.fielden.platform.web.uri.EntityMasterUrlProvider; import ua.com.fielden.platform.web.utils.CriteriaEntityRestorer; -import ua.com.fielden.platform.web.utils.EntityCentreAPI; -import ua.com.fielden.platform.web.utils.EntityCentreAPIImpl; +import ua.com.fielden.platform.web.utils.EntityCentreProcessor; +import ua.com.fielden.platform.web.utils.DefaultEntityCentreProcessor; import ua.com.fielden.platform.web.utils.ICriteriaEntityRestorer; import static ua.com.fielden.platform.basic.config.Workflows.deployment; @@ -63,7 +63,7 @@ default void bindWebAppResources(final IWebUiConfig webApp) { // dependent on IWebUiConfig, IUserProvider and other Web UI infrastructure bindType(ICriteriaEntityRestorer.class).to(CriteriaEntityRestorer.class); - bindType(EntityCentreAPI.class).to(EntityCentreAPIImpl.class); + bindType(EntityCentreProcessor.class).to(DefaultEntityCentreProcessor.class); // bind companion object implementations that are dependent on ICriteriaEntityRestorer PlatformDomainTypes.typesDependentOnWebUI.stream().forEach(type -> bindCo(type, (co, t) -> bindType(co).to(t))); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java index 141d47b5f92..14664a4f2ad 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/TgTestWebApplicationServerIocModule.java @@ -8,8 +8,8 @@ import ua.com.fielden.platform.entity.validation.CanBuildReferenceHierarchyForEveryEntityValidator; import ua.com.fielden.platform.entity.validation.ICanBuildReferenceHierarchyForEntityValidator; import ua.com.fielden.platform.web.ioc.IBasicWebApplicationServerModule; -import ua.com.fielden.platform.web.utils.EntityCentreAPI; -import ua.com.fielden.platform.web.utils.EntityCentreAPIImpl; +import ua.com.fielden.platform.web.utils.EntityCentreProcessor; +import ua.com.fielden.platform.web.utils.DefaultEntityCentreProcessor; import java.util.List; import java.util.Properties; @@ -34,7 +34,7 @@ protected void configure() { super.configure(); bind(ICanBuildReferenceHierarchyForEntityValidator.class).to(CanBuildReferenceHierarchyForEveryEntityValidator.class); bindWebAppResources(new WebUiConfig(props)); - bind(EntityCentreAPI.class).to(EntityCentreAPIImpl.class); + bind(EntityCentreProcessor.class).to(DefaultEntityCentreProcessor.class); } @Override diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java similarity index 98% rename from platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java rename to platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 71e1972d8b4..f459dbee287 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreAPIImpl.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -51,7 +51,7 @@ import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.RunActions.RUN; import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; -public class EntityCentreAPIImpl implements EntityCentreAPI { +public class DefaultEntityCentreProcessor implements EntityCentreProcessor { private final ICompanionObjectFinder companionFinder; private final IUserProvider userProvider; private final ICriteriaGenerator critGenerator; @@ -62,7 +62,7 @@ public class EntityCentreAPIImpl implements EntityCentreAPI { private final ISecurityTokenProvider securityTokenProvider; @Inject - public EntityCentreAPIImpl( + public DefaultEntityCentreProcessor( final ICompanionObjectFinder companionFinder, final IUserProvider userProvider, final ICriteriaGenerator critGenerator, @@ -154,7 +154,7 @@ private static Either determineConfigurationSettings(fin } @Override - public > Either> entityCentreResult(final String configUuid) { + public > Either> getResult(final String configUuid) { return entityCentreResult(configUuid, empty()); } @@ -234,7 +234,7 @@ private > Either> entityCentreResult } @Override - public Either entityCentreResultExists(final String configUuid) { + public Either resultExists(final String configUuid) { return entityCentreResult(configUuid, of(1)) .map(result -> !result.isEmpty()); } diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java similarity index 86% rename from platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java rename to platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 14410d55db8..bff4bff9e99 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreAPITest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -20,11 +20,11 @@ import static ua.com.fielden.platform.web.centre.CentreUpdater.*; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; -/// Tests for {@link EntityCentreAPI}. +/// Tests for {@link EntityCentreProcessor}. /// These should serve as an example of how end-application tests can be implemented. /// @RunWith(H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.class) -public class EntityCentreAPITest extends AbstractDaoTestCase { +public class EntityCentreProcessorTest extends AbstractDaoTestCase { /// Creates Entity Centre persisted configuration for concrete surrogate version of it. /// @@ -48,7 +48,7 @@ private void createConfig( } @Test - public void executing_entityCentreResult_method_returns_entities_for_named_configuration_with_data() { + public void executing_getResult_method_returns_entities_for_named_configuration_with_data() { // Define uuid for named configuration. final var uuid = "c95ec1ae-09e8-439f-92e7-880261fce023"; @@ -56,8 +56,8 @@ public void executing_entityCentreResult_method_returns_entities_for_named_confi // Run Entity Centre through the API. - final var entityCentreApi = getInstance(EntityCentreAPI.class); - final var result = entityCentreApi.entityCentreResult(uuid); + final var entityCentreProcessor = getInstance(EntityCentreProcessor.class); + final var result = entityCentreProcessor.getResult(uuid); // Check the result. @@ -69,7 +69,7 @@ public void executing_entityCentreResult_method_returns_entities_for_named_confi } @Test - public void executing_entityCentreResultExists_method_returns_true_for_named_configuration_with_data() { + public void executing_resultExists_method_returns_true_for_named_configuration_with_data() { // Define uuid for named configuration. final var uuid = "c95ec1ae-09e8-439f-92e7-880261fce023"; @@ -77,8 +77,8 @@ public void executing_entityCentreResultExists_method_returns_true_for_named_con // Run Entity Centre through the API. - final var entityCentreApi = getInstance(EntityCentreAPI.class); - final var result = entityCentreApi.entityCentreResultExists(uuid); + final var entityCentreProcessor = getInstance(EntityCentreProcessor.class); + final var result = entityCentreProcessor.resultExists(uuid); // Check the result. From c0fa36ff2aec43335c3bcd4737badc458929e8f9 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 24 Feb 2026 16:49:38 +0200 Subject: [PATCH 33/76] #2224 Add tests for no data and for present data, but custom criteria. --- .../web/utils/EntityCentreProcessorTest.java | 159 +++++++++++++----- .../web/centre/CentreUpdaterUtils.java | 14 +- 2 files changed, 123 insertions(+), 50 deletions(-) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index bff4bff9e99..811732331d9 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -2,20 +2,32 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import fielden.test_app.main.menu.compound.MiTgCompoundEntity; import org.junit.Test; import org.junit.runner.RunWith; +import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer; +import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; import ua.com.fielden.platform.sample.domain.compound.TgCompoundEntity; +import ua.com.fielden.platform.security.user.IUser; +import ua.com.fielden.platform.security.user.User; import ua.com.fielden.platform.security.user.User.system_users; import ua.com.fielden.platform.test_config.AbstractDaoTestCase; import ua.com.fielden.platform.test_config.H2OrPostgreSqlOrSqlServerContextSelectorForWebTests; import ua.com.fielden.platform.ui.config.EntityCentreConfig; +import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; import ua.com.fielden.platform.ui.config.MainMenuItem; +import ua.com.fielden.platform.ui.config.MainMenuItemCo; +import ua.com.fielden.platform.web.app.IWebUiConfig; +import ua.com.fielden.platform.web.resources.webui.ConfigSettings; import java.util.Map; +import java.util.function.Consumer; import static java.util.Optional.of; +import static java.util.UUID.randomUUID; import static org.junit.Assert.*; import static ua.com.fielden.platform.types.tuples.T2.t2; +import static ua.com.fielden.platform.utils.CollectionUtil.listOf; import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; import static ua.com.fielden.platform.web.centre.CentreUpdater.*; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; @@ -26,40 +38,37 @@ @RunWith(H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.class) public class EntityCentreProcessorTest extends AbstractDaoTestCase { - /// Creates Entity Centre persisted configuration for concrete surrogate version of it. - /// - private void createConfig( - final String surrogateName, - final MainMenuItem mainMenuItem, - final Map diffObject, - final String uuid - ) { - try { - save(new_(EntityCentreConfig.class) - .setOwner(getUser()) - .setMenuItem(mainMenuItem) - .setTitle(NAME_OF.apply(surrogateName).apply(of("saveAs")).apply(DESKTOP)) - .setConfigBody(new ObjectMapper().writeValueAsBytes(diffObject)) - .setConfigUuid(uuid) - ); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } + @Test + public void executing_getResult_method_returns_zero_entities_for_named_configuration_without_data() { + final var uuid = randomUUID().toString(); + initTestData(uuid, () -> {}, m -> {}); + + final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); + + assertNotNull(result); + assertTrue(result.isRight()); + assertNotNull(result.asRight().value()); + assertEquals(0, result.asRight().value().size()); } @Test - public void executing_getResult_method_returns_entities_for_named_configuration_with_data() { - // Define uuid for named configuration. - final var uuid = "c95ec1ae-09e8-439f-92e7-880261fce023"; + public void executing_resultExists_method_returns_false_for_named_configuration_without_data() { + final var uuid = randomUUID().toString(); + initTestData(uuid, () -> {}, m -> {}); - initTestData(uuid); + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); - // Run Entity Centre through the API. + assertNotNull(result); + assertTrue(result.isRight()); + assertFalse(result.asRight().value()); + } - final var entityCentreProcessor = getInstance(EntityCentreProcessor.class); - final var result = entityCentreProcessor.getResult(uuid); + @Test + public void executing_getResult_method_returns_entities_for_named_configuration() { + final var uuid = randomUUID().toString(); + initTestData(uuid, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), m -> {}); - // Check the result. + final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); assertNotNull(result); assertTrue(result.isRight()); @@ -69,42 +78,100 @@ public void executing_getResult_method_returns_entities_for_named_configuration_ } @Test - public void executing_resultExists_method_returns_true_for_named_configuration_with_data() { - // Define uuid for named configuration. - final var uuid = "c95ec1ae-09e8-439f-92e7-880261fce023"; + public void executing_resultExists_method_returns_true_for_named_configuration() { + final var uuid = randomUUID().toString(); + initTestData(uuid, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), m -> {}); - initTestData(uuid); + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); - // Run Entity Centre through the API. + assertNotNull(result); + assertTrue(result.isRight()); + assertTrue(result.asRight().value()); + } - final var entityCentreProcessor = getInstance(EntityCentreProcessor.class); - final var result = entityCentreProcessor.resultExists(uuid); + @Test + public void executing_getResult_method_returns_entities_for_named_configuration_with_custom_criteria() { + final var uuid = randomUUID().toString(); + initTestData(uuid, () -> { + save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); + save(new_(TgCompoundEntity.class, "KEY2").setActive(true).setDesc("desc 2")); + }, centreManager -> { + centreManager.getFirstTick().setValue(TgCompoundEntity.class, "", listOf("*2")); + }); - // Check the result. + final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); + + assertNotNull(result); + assertTrue(result.isRight()); + assertNotNull(result.asRight().value()); + assertEquals(1, result.asRight().value().size()); + assertEquals("KEY2", result.asRight().value().getFirst().getKey()); + } + + @Test + public void executing_resultExists_method_returns_true_for_named_configuration_with_custom_criteria() { + final var uuid = randomUUID().toString(); + initTestData(uuid, () -> { + save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); + save(new_(TgCompoundEntity.class, "KEY2").setActive(true).setDesc("desc 2")); + }, centreManager -> { + centreManager.getFirstTick().setValue(TgCompoundEntity.class, "", listOf("*2")); + }); + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); assertNotNull(result); assertTrue(result.isRight()); assertTrue(result.asRight().value()); } - private void initTestData(final String uuid) { + private void initTestData(final String uuid, final Runnable createData, final Consumer enhanceCentreManager) { setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - // Initialise data for `MiTgCompoundEntity` Entity Centre execution. + createData.run(); + + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgCompoundEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + + final IWebUiConfig webUiConfig = getInstance(IWebUiConfig.class); + final ICompanionObjectFinder companionFinder = getInstance(ICompanionObjectFinder.class); + final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); + final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); + final IUser userCompanion = companionFinder.find(User.class); - // Create entity instance first. - save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); - // Create MainMenuItem instance conforming to MiTgCompoundEntity. - final var mainMenuItem = (MainMenuItem) save(new_(MainMenuItem.class).setKey("fielden.test_app.main.menu.compound.MiTgCompoundEntity")); - // Create empty diff object for Entity Centre configuration, meaning it will behave exactly as defined in `TgCompoundEntityWebUiConfig`. + final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), FRESH_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + enhanceCentreManager.accept(centreManager); + commitCentreWithoutConflicts(configSettings.owner(), configSettings.miType(), FRESH_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), centreManager, null /* newDesc */, webUiConfig, eccCompanion, mmiCompanion, userCompanion); + } + + /// Creates Entity Centre persisted configuration for concrete surrogate version of it. + /// + private void createConfig( + final ConfigSettings configSettings, + final String surrogateName, + final String uuid + ) { final Map diffObject = mapOf( t2("PROPERTIES" /* CentreUpdater.PROPERTIES */, mapOf()) ); + try { + save(new_(EntityCentreConfig.class) + .setOwner(configSettings.owner()) + .setMenuItem(getMenuItem(configSettings.miType())) + .setTitle(NAME_OF.apply(surrogateName).apply(configSettings.saveAsName()).apply(configSettings.device())) + .setConfigBody(new ObjectMapper().writeValueAsBytes(diffObject)) + .setConfigUuid(uuid) + ); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } - // Create "fresh" config, that would get executed through the API. - createConfig(FRESH_CENTRE_NAME, mainMenuItem, diffObject, uuid); - // Create "saved" config for current user too, which indicates that the user own it. - createConfig(SAVED_CENTRE_NAME, mainMenuItem, diffObject, uuid); + private MainMenuItem getMenuItem(final Class menuItemType) { + return co(MainMenuItem.class) + .findByKeyOptional(menuItemType.getName()) + .orElseGet(() -> (MainMenuItem) save(new_(MainMenuItem.class).setKey(menuItemType.getName()))); } @Override diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java index 022ec377129..f6765be2468 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java @@ -165,15 +165,21 @@ public static Long saveNewEntityCentreManager( final MainMenuItemCo mmiCompanion, final Function adjustConfig ) { - final MainMenuItem menuItem = mmiCompanion.findByKeyOptional(menuItemType.getName()).orElseGet(() -> { + final MainMenuItem menuItem = getMenuItem(menuItemType, mmiCompanion); + final EntityCentreConfig ecc = adjustConfig.apply(eccCompanion.new_().setOwner(user).setTitle(newName).setMenuItem(menuItem).setConfigBody(serialisedDifferences).setDesc(newDesc)); + return eccCompanion.saveWithRetry(ecc); + } + + /// Gets (or creates) [MainMenuItem] entity for `menuItemType`. + /// + private static MainMenuItem getMenuItem(final Class menuItemType, final MainMenuItemCo mmiCompanion) { + return mmiCompanion.findByKeyOptional(menuItemType.getName()).orElseGet(() -> { final MainMenuItem newMainMenuItem = mmiCompanion.new_(); newMainMenuItem.setKey(menuItemType.getName()); return mmiCompanion.save(newMainMenuItem); }); - final EntityCentreConfig ecc = adjustConfig.apply(eccCompanion.new_().setOwner(user).setTitle(newName).setMenuItem(menuItem).setConfigBody(serialisedDifferences).setDesc(newDesc)); - return eccCompanion.saveWithRetry(ecc); } - + /** * Overrides existing {@link EntityCentreConfig} instance with new serialised diff. * Otherwise, in case where there is no such instance in database, creates and saves new {@link EntityCentreConfig} instance with serialised diff inside. From 1a93d19908b15cbb05fe9ad7a491817567439ae6 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 24 Feb 2026 17:25:39 +0200 Subject: [PATCH 34/76] #2224 Cover unexpected exception handling also at the stage of config settings determination. Add initial docs for API with precautions for transaction handling. --- .../web/utils/EntityCentreProcessor.java | 16 ++++++++++++++++ .../web/utils/DefaultEntityCentreProcessor.java | 16 ++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 5703ef0a5be..77e20fd3935 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -3,15 +3,31 @@ import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.error.Result; import ua.com.fielden.platform.types.either.Either; +import ua.com.fielden.platform.types.either.Left; import java.util.List; +import java.util.function.Function; +/// An interface for Entity Centre processing API. +/// public interface EntityCentreProcessor { + /// Executes named Entity Centre configuration, defined by UUID, similarly as the owner may have run it through Web UI. + /// Takes into account all unsaved changes in that configuration. + /// + /// Important: running of this method in context of `@SessionRequired` scope may roll back active transaction. + /// This means that API users must exercise caution if [Left] is returned (use [Either#orElseThrow(Function)]). + /// > Either> getResult( String configUuid ); + /// Finds out whether named Entity Centre configuration, defined by UUID, has non-empty result (similarly to Web UI running). + /// Takes into account all unsaved changes in that configuration. + /// + /// Important: running of this method in context of `@SessionRequired` scope may roll back active transaction. + /// This means that API users must exercise caution if [Left] is returned (use [Either#orElseThrow(Function)]). + /// Either resultExists( String configUuid ); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index f459dbee287..c4bc5b346dc 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -162,16 +162,16 @@ private > Either> entityCentreResult final String configUuid, final Optional maybeCustomPageCapacity ) { - // Find out the settings for configuration. Stop execution if the settings can not be determined or inapplicable. - final var resultOrConfigSettings = determineConfigurationSettings(configUuid, companionFinder); - if (resultOrConfigSettings.isLeft()) { - return left(resultOrConfigSettings.asLeft().value()); - } - final var configSettings = resultOrConfigSettings.asRight().value(); - // Determine current user to be returned back into the user provider once the execution has been performed. final User currentUser = userProvider.getUser(); try { + // Find out the settings for configuration. Stop execution if the settings can not be determined or inapplicable. + final var resultOrConfigSettings = determineConfigurationSettings(configUuid, companionFinder); + if (resultOrConfigSettings.isLeft()) { + return left(resultOrConfigSettings.asLeft().value()); + } + final var configSettings = resultOrConfigSettings.asRight().value(); + // Apply the configuration owner to user provider temporarily. userProvider.setUser(configSettings.owner()); @@ -226,7 +226,7 @@ private > Either> entityCentreResult resultList.forEach(entity -> list.add((T) entity)); return right(list); } catch (final Exception exception) { - return left(failure(new EntityCentreExecutionException("Configuration with UUID [%s] and settings [%s] could not be executed.".formatted(configUuid, configSettings), exception))); + return left(failure(new EntityCentreExecutionException("Configuration with [%s] UUID could not be executed.".formatted(configUuid), exception))); } finally { // Return original user back to user provider. userProvider.setUser(currentUser); From 2bcf291774af8eecffbd84f89e6ec77a4d0dc545 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 24 Feb 2026 23:56:25 +0200 Subject: [PATCH 35/76] #2224 Misc improvements for validation (+tests). --- .../sample/domain/TgGeneratedEntity.java | 45 +-- .../TgGeneratedEntity_CanRead_Token.java | 15 + .../config/TgGeneratedEntityWebUiConfig.java | 10 +- .../utils/DefaultEntityCentreProcessor.java | 33 ++- .../web/utils/EntityCentreProcessorTest.java | 259 ++++++++++++++++-- 5 files changed, 311 insertions(+), 51 deletions(-) create mode 100644 platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/persistent/TgGeneratedEntity_CanRead_Token.java diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/TgGeneratedEntity.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/TgGeneratedEntity.java index 966d8e5104d..e1a588e4d29 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/TgGeneratedEntity.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/sample/domain/TgGeneratedEntity.java @@ -3,31 +3,25 @@ import ua.com.fielden.platform.data.generator.WithCreatedByUser; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.entity.DynamicEntityKey; -import ua.com.fielden.platform.entity.annotation.CompanionObject; -import ua.com.fielden.platform.entity.annotation.CompositeKeyMember; -import ua.com.fielden.platform.entity.annotation.CritOnly; +import ua.com.fielden.platform.entity.annotation.*; import ua.com.fielden.platform.entity.annotation.CritOnly.Type; -import ua.com.fielden.platform.entity.annotation.DescTitle; -import ua.com.fielden.platform.entity.annotation.IsProperty; -import ua.com.fielden.platform.entity.annotation.KeyType; -import ua.com.fielden.platform.entity.annotation.MapEntityTo; -import ua.com.fielden.platform.entity.annotation.MapTo; -import ua.com.fielden.platform.entity.annotation.Observable; -import ua.com.fielden.platform.entity.annotation.Title; +import ua.com.fielden.platform.reflection.TitlesDescsGetter; import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.utils.Pair; -/** - * Represents a typical example of an entity to be generated. This particular entity is generated by generator {@link TgGeneratedEntityGenerator}. - * - * @author TG Team - * - */ +import static ua.com.fielden.platform.entity.annotation.CritOnly.Type.SINGLE; + +/// Represents a typical example of an entity to be generated. This particular entity is generated by generator `TgGeneratedEntityGenerator`. +/// @KeyType(DynamicEntityKey.class) @CompanionObject(ITgGeneratedEntity.class) @MapEntityTo @DescTitle("Desc") public class TgGeneratedEntity extends AbstractEntity implements WithCreatedByUser { - private static final long serialVersionUID = 1L; + + private static final Pair entityTitleAndDesc = TitlesDescsGetter.getEntityTitleAndDesc(TgGeneratedEntity.class); + public static final String ENTITY_TITLE = entityTitleAndDesc.getKey(); + public static final String ENTITY_DESC = entityTitleAndDesc.getValue(); @IsProperty(assignBeforeSave = true) @MapTo @@ -47,10 +41,25 @@ public class TgGeneratedEntity extends AbstractEntity implemen private User critOnlyMultiProp; @IsProperty - @CritOnly(Type.SINGLE) + @CritOnly(SINGLE) @Title(value = "CritOnly Single", desc = "CritOnly single property") private User critOnlySingleProp; + @IsProperty + @CritOnly(SINGLE) + @Required + private Integer requiredProp; + + public Integer getRequiredProp() { + return requiredProp; + } + + @Observable + public TgGeneratedEntity setRequiredProp(Integer requiredProp) { + this.requiredProp = requiredProp; + return this; + } + @Observable public TgGeneratedEntity setEntityKey(final String entityKey) { this.entityKey = entityKey; diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/persistent/TgGeneratedEntity_CanRead_Token.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/persistent/TgGeneratedEntity_CanRead_Token.java new file mode 100644 index 00000000000..3cef0f3b144 --- /dev/null +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/persistent/TgGeneratedEntity_CanRead_Token.java @@ -0,0 +1,15 @@ +package ua.com.fielden.platform.security.tokens.persistent; + +import ua.com.fielden.platform.sample.domain.TgGeneratedEntity; +import ua.com.fielden.platform.sample.domain.TgVehicleModel; +import ua.com.fielden.platform.security.ISecurityToken; +import ua.com.fielden.platform.security.tokens.Template; + +import static java.lang.String.format; + +/// A security token for entity [TgGeneratedEntity] to guard Read. +/// +public class TgGeneratedEntity_CanRead_Token implements ISecurityToken { + public final static String TITLE = format(Template.READ.forTitle(), TgGeneratedEntity.ENTITY_TITLE); + public final static String DESC = format(Template.READ.forDesc(), TgGeneratedEntity.ENTITY_TITLE); +} diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityWebUiConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityWebUiConfig.java index 6922368a75b..57b95c3f364 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityWebUiConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/config/TgGeneratedEntityWebUiConfig.java @@ -20,7 +20,10 @@ import ua.com.fielden.platform.web.view.master.api.IMaster; import ua.com.fielden.platform.web.view.master.api.actions.MasterActions; import ua.com.fielden.platform.web.view.master.api.impl.SimpleMasterBuilder; -/** + +import static ua.com.fielden.platform.web.centre.api.crit.defaults.mnemonics.construction.options.DefaultValueOptions.single; + +/** * {@link TgGeneratedEntity} Web UI configuration. * * @author TG Team @@ -49,7 +52,7 @@ private TgGeneratedEntityWebUiConfig(final Injector injector, final IWebUiBuilde * @return created entity centre */ private EntityCentre createCentre(final Injector injector) { - final String layout = LayoutComposer.mkGridForCentre(2, 2); + final String layout = LayoutComposer.mkVarGridForCentre(2, 2, 1); final EntityActionConfig standardNewAction = StandardActions.NEW_ACTION.mkAction(TgGeneratedEntity.class); final EntityActionConfig standardDeleteAction = StandardActions.DELETE_ACTION.mkAction(TgGeneratedEntity.class); @@ -65,7 +68,8 @@ private EntityCentre createCentre(final Injector injector) { .addCrit("this").asMulti().autocompleter(TgGeneratedEntity.class).also() .addCrit("critOnlyMultiProp").asMulti().autocompleter(User.class).also() .addCrit("critOnlySingleProp").asSingle().autocompleter(User.class).also() - .addCrit("createdBy").asMulti().autocompleter(User.class) + .addCrit("createdBy").asMulti().autocompleter(User.class).also() + .addCrit("requiredProp").asSingle().integer().setDefaultValue(single().integer().setValue(1).value()) .setLayoutFor(Device.DESKTOP, Optional.empty(), layout) .setLayoutFor(Device.TABLET, Optional.empty(), layout) .setLayoutFor(Device.MOBILE, Optional.empty(), layout) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index c4bc5b346dc..fc80889d3f6 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -30,6 +30,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import static java.lang.Class.forName; import static java.util.Optional.empty; @@ -52,6 +53,16 @@ import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; public class DefaultEntityCentreProcessor implements EntityCentreProcessor { + private static final String ERR_EXECUTION_FAILED_PREFIX = "Entity Centre configuration execution failed. "; + public static final String ERR_CONFIG_UUID_IS_BLANK = ERR_EXECUTION_FAILED_PREFIX + "Config UUID [%s] is blank."; + public static final String ERR_CONFIG_DOES_NOT_EXIST = ERR_EXECUTION_FAILED_PREFIX + "Config with [%s] UUID does not exist."; + public static final String ERR_CONFIG_MENU_ITEM_TYPE_CANT_BE_FOUND = ERR_EXECUTION_FAILED_PREFIX + "[%s] config's menu item type [%s] can not be found."; + public static final String ERR_LINK_CONFIG_IS_NOT_AVAILABLE_FOR_RUNNING = ERR_EXECUTION_FAILED_PREFIX + "[%s] link config ([%s]) is not available for API running."; + public static final String ERR_DEFAULT_CONFIG_WITH_UUID_SHOULD_NOT_EXIST = ERR_EXECUTION_FAILED_PREFIX + "[%s] configuration with blank name [%s] shouldn't exist (default configuration should never have UUID)."; + + private static final Function> TITLE_PREFIX_INCL_DEFAULT = + surrogateName -> device -> PREFIX_OF.apply(surrogateName).apply(device).replace("[%", "%"); + private final ICompanionObjectFinder companionFinder; private final IUserProvider userProvider; private final ICriteriaGenerator critGenerator; @@ -90,8 +101,8 @@ private static ICompoundCondition0 centreConfigQueryFor(fina final var selectStart = select(EntityCentreConfig.class); return maybeAlias.map(selectStart::as).orElse(selectStart) .where().begin() - .prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(DESKTOP)) - .or().prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(MOBILE)) + .prop("title").like().val(TITLE_PREFIX_INCL_DEFAULT.apply(surrogateName).apply(DESKTOP)) + .or().prop("title").like().val(TITLE_PREFIX_INCL_DEFAULT.apply(surrogateName).apply(MOBILE)) .end() .and().condition(centreConfigCondFor(uuid)); } @@ -104,7 +115,7 @@ private static ICompoundCondition0 centreConfigQueryFor(fina private static Either determineConfigurationSettings(final String configUuid, final ICompanionObjectFinder companionFinder) { // Blank uuid does not represent any centre configuration. if (isBlank(configUuid)) { - return left(failure("Configuration UUID [%s] is blank.".formatted(configUuid))); + return left(failure(ERR_CONFIG_UUID_IS_BLANK.formatted(configUuid))); } // Find "fresh" persisted configuration instance for which there is a corresponding "saved" instance for the same owner. @@ -124,7 +135,7 @@ private static Either determineConfigurationSettings(fin // If there is no such configuration, return invalid `Result`. if (freshConfigOpt.isEmpty()) { - return left(failure("Configuration with [%s] UUID does not exist.".formatted(configUuid))); + return left(failure(ERR_CONFIG_DOES_NOT_EXIST.formatted(configUuid))); } final var freshConfig = freshConfigOpt.get(); @@ -138,19 +149,21 @@ private static Either determineConfigurationSettings(fin try { miType = (Class>) forName(miTypeName); } catch (final ClassNotFoundException notFoundException) { - return left(failure(new EntityCentreExecutionException("Configuration's menu item type [%s] can not be found.".formatted(miTypeName), notFoundException))); + return left(failure(new EntityCentreExecutionException(ERR_CONFIG_MENU_ITEM_TYPE_CANT_BE_FOUND.formatted(configUuid, miTypeName), notFoundException))); } // Determine device. final var device = freshConfig.getTitle().startsWith(MOBILE.name()) ? MOBILE : DESKTOP; // Determine "save-as" name. - final Optional saveAsName = of(obtainTitleFrom(freshConfig.getTitle(), FRESH_CENTRE_NAME, device)); - if (LINK_CONFIG_TITLE.equals(saveAsName.get())) { - return left(failure("Link configuration [%s] is not available for API running.".formatted(saveAsName))); + final var saveAsNameString = freshConfig.getTitle().contains("[") ? obtainTitleFrom(freshConfig.getTitle(), FRESH_CENTRE_NAME, device) : ""; + if (isBlank(saveAsNameString)) { + return left(failure(ERR_DEFAULT_CONFIG_WITH_UUID_SHOULD_NOT_EXIST.formatted(configUuid, saveAsNameString))); } - - return right(new ConfigSettings(saveAsName, owner, device, miType)); + if (LINK_CONFIG_TITLE.equals(saveAsNameString)) { + return left(failure(ERR_LINK_CONFIG_IS_NOT_AVAILABLE_FOR_RUNNING.formatted(configUuid, saveAsNameString))); + } + return right(new ConfigSettings(of(saveAsNameString), owner, device, miType)); } @Override diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 811732331d9..6d92899801c 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -7,9 +7,10 @@ import org.junit.runner.RunWith; import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer; import ua.com.fielden.platform.entity.factory.ICompanionObjectFinder; +import ua.com.fielden.platform.sample.domain.TgGeneratedEntity; import ua.com.fielden.platform.sample.domain.compound.TgCompoundEntity; -import ua.com.fielden.platform.security.user.IUser; -import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.security.tokens.persistent.TgGeneratedEntity_CanRead_Token; +import ua.com.fielden.platform.security.user.*; import ua.com.fielden.platform.security.user.User.system_users; import ua.com.fielden.platform.test_config.AbstractDaoTestCase; import ua.com.fielden.platform.test_config.H2OrPostgreSqlOrSqlServerContextSelectorForWebTests; @@ -17,31 +18,39 @@ import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; import ua.com.fielden.platform.ui.config.MainMenuItem; import ua.com.fielden.platform.ui.config.MainMenuItemCo; +import ua.com.fielden.platform.ui.menu.sample.MiTgGeneratedEntity; import ua.com.fielden.platform.web.app.IWebUiConfig; +import ua.com.fielden.platform.web.interfaces.DeviceProfile; import ua.com.fielden.platform.web.resources.webui.ConfigSettings; import java.util.Map; import java.util.function.Consumer; +import static java.util.Optional.empty; import static java.util.Optional.of; import static java.util.UUID.randomUUID; import static org.junit.Assert.*; +import static ua.com.fielden.platform.entity.meta.MetaProperty.ERR_REQUIRED; import static ua.com.fielden.platform.types.tuples.T2.t2; -import static ua.com.fielden.platform.utils.CollectionUtil.listOf; -import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; +import static ua.com.fielden.platform.utils.CollectionUtil.*; import static ua.com.fielden.platform.web.centre.CentreUpdater.*; +import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; +import static ua.com.fielden.platform.web.interfaces.DeviceProfile.MOBILE; +import static ua.com.fielden.platform.web.utils.DefaultEntityCentreProcessor.*; -/// Tests for {@link EntityCentreProcessor}. -/// These should serve as an example of how end-application tests can be implemented. +/// Tests for {@link EntityCentreProcessor}. These should serve as an example of how end-application tests can be +/// implemented. /// @RunWith(H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.class) public class EntityCentreProcessorTest extends AbstractDaoTestCase { + public static final String NON_EXISTING_MI_TYPE_NAME = "fielden.test_app.main.menu.compound.NON_EXISTING"; + @Test public void executing_getResult_method_returns_zero_entities_for_named_configuration_without_data() { final var uuid = randomUUID().toString(); - initTestData(uuid, () -> {}, m -> {}); + initTestData(uuid, () -> {}, _ -> {}); final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); @@ -54,7 +63,7 @@ public void executing_getResult_method_returns_zero_entities_for_named_configura @Test public void executing_resultExists_method_returns_false_for_named_configuration_without_data() { final var uuid = randomUUID().toString(); - initTestData(uuid, () -> {}, m -> {}); + initTestData(uuid, () -> {}, _ -> {}); final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); @@ -66,7 +75,7 @@ public void executing_resultExists_method_returns_false_for_named_configuration_ @Test public void executing_getResult_method_returns_entities_for_named_configuration() { final var uuid = randomUUID().toString(); - initTestData(uuid, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), m -> {}); + initTestData(uuid, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), _ -> {}); final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); @@ -80,7 +89,7 @@ public void executing_getResult_method_returns_entities_for_named_configuration( @Test public void executing_resultExists_method_returns_true_for_named_configuration() { final var uuid = randomUUID().toString(); - initTestData(uuid, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), m -> {}); + initTestData(uuid, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), _ -> {}); final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); @@ -95,9 +104,7 @@ public void executing_getResult_method_returns_entities_for_named_configuration_ initTestData(uuid, () -> { save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); save(new_(TgCompoundEntity.class, "KEY2").setActive(true).setDesc("desc 2")); - }, centreManager -> { - centreManager.getFirstTick().setValue(TgCompoundEntity.class, "", listOf("*2")); - }); + }, centreManager -> centreManager.getFirstTick().setValue(TgCompoundEntity.class, "", listOf("*2"))); final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); @@ -114,9 +121,33 @@ public void executing_resultExists_method_returns_true_for_named_configuration_w initTestData(uuid, () -> { save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")); save(new_(TgCompoundEntity.class, "KEY2").setActive(true).setDesc("desc 2")); - }, centreManager -> { - centreManager.getFirstTick().setValue(TgCompoundEntity.class, "", listOf("*2")); - }); + }, centreManager -> centreManager.getFirstTick().setValue(TgCompoundEntity.class, "", listOf("*2"))); + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); + + assertNotNull(result); + assertTrue(result.isRight()); + assertTrue(result.asRight().value()); + } + + @Test + public void executing_getResult_method_returns_entities_for_named_configuration_in_mobile_namespace() { + final var uuid = randomUUID().toString(); + initTestData(uuid, MOBILE, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), _ -> {}); + + final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); + + assertNotNull(result); + assertTrue(result.isRight()); + assertNotNull(result.asRight().value()); + assertEquals(1, result.asRight().value().size()); + assertEquals("KEY1", result.asRight().value().getFirst().getKey()); + } + + @Test + public void executing_resultExists_method_returns_true_for_named_configuration_in_mobile_namespace() { + final var uuid = randomUUID().toString(); + initTestData(uuid, MOBILE, () -> save(new_(TgCompoundEntity.class, "KEY1").setActive(true).setDesc("desc 1")), _ -> {}); final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); @@ -125,15 +156,193 @@ public void executing_resultExists_method_returns_true_for_named_configuration_w assertTrue(result.asRight().value()); } + @Test + public void executing_resultExists_method_returns_invalid_result_for_blank_uuid() { + final var uuid = ""; + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals(ERR_CONFIG_UUID_IS_BLANK.formatted(uuid), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_non_existing_named_configuration() { + final var uuid = randomUUID().toString(); + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals(ERR_CONFIG_DOES_NOT_EXIST.formatted(uuid), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_orphan_inherited_from_shared_named_configuration() { + final var uuid = randomUUID().toString(); + + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgCompoundEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, null); + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals(ERR_CONFIG_DOES_NOT_EXIST.formatted(uuid), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_unusual_named_configuration_with_no_miType() { + final var uuid = randomUUID().toString(); + + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, null); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals(ERR_CONFIG_MENU_ITEM_TYPE_CANT_BE_FOUND.formatted(uuid, NON_EXISTING_MI_TYPE_NAME), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_unusual_default_configuration_with_uuid() { + final var uuid = randomUUID().toString(); + + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + final var configSettings = new ConfigSettings(empty(), getUser(), DESKTOP, MiTgCompoundEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals(ERR_DEFAULT_CONFIG_WITH_UUID_SHOULD_NOT_EXIST.formatted(uuid, ""), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_link_configuration() { + final var uuid = randomUUID().toString(); + + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + final var configSettings = new ConfigSettings(of(LINK_CONFIG_TITLE), getUser(), DESKTOP, MiTgCompoundEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + + final var result = getInstance(EntityCentreProcessor.class).resultExists(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals(ERR_LINK_CONFIG_IS_NOT_AVAILABLE_FOR_RUNNING.formatted(uuid, LINK_CONFIG_TITLE), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_invalid_configuration() { + final var uuid = randomUUID().toString(); + + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + + initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "requiredProp", null), configSettings); + + final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals(ERR_REQUIRED.formatted("Required Prop", "Centre Selection Criteria"), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_unauthorised_configuration() { + final var uuid = randomUUID().toString(); + + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + + final SecurityRoleAssociationCo co$ = co$(SecurityRoleAssociation.class); + co$.removeAssociations(setOf( + co$.new_() + .setRole(co(UserRole.class).findByKey(ADMIN)) + .setSecurityToken(TgGeneratedEntity_CanRead_Token.class) + )); + + final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals("Permission denied due to token [%s] restriction.".formatted(TgGeneratedEntity_CanRead_Token.TITLE), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultExists_method_returns_invalid_result_for_configuration_with_invalid_generation() { + final var uuid = randomUUID().toString(); + + setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); + + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + + initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "critOnlySingleProp", getUser()), configSettings); + + final var result = getInstance(EntityCentreProcessor.class).getResult(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals("Can not generate the instance based on current user [%s], choose another user for that.".formatted(getUser()), result.asLeft().value().getMessage()); + } + private void initTestData(final String uuid, final Runnable createData, final Consumer enhanceCentreManager) { + initTestData(uuid, DESKTOP, createData, enhanceCentreManager); + } + + private void initTestData(final String uuid, final DeviceProfile device, final Runnable createData, final Consumer enhanceCentreManager) { setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); createData.run(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgCompoundEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), device, MiTgCompoundEntity.class); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + initTestData(enhanceCentreManager, configSettings); + } + + private void initTestData(Consumer enhanceCentreManager, ConfigSettings configSettings) { final IWebUiConfig webUiConfig = getInstance(IWebUiConfig.class); final ICompanionObjectFinder companionFinder = getInstance(ICompanionObjectFinder.class); final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); @@ -169,14 +378,24 @@ private void createConfig( } private MainMenuItem getMenuItem(final Class menuItemType) { + if (menuItemType == null) { + return getMenuItemForName(null); + } + return getMenuItemForName(menuItemType.getName()); + } + + private MainMenuItem getMenuItemForName(final String menuItemTypeName) { + if (menuItemTypeName == null) { + return getMenuItemForName(NON_EXISTING_MI_TYPE_NAME); + } return co(MainMenuItem.class) - .findByKeyOptional(menuItemType.getName()) - .orElseGet(() -> (MainMenuItem) save(new_(MainMenuItem.class).setKey(menuItemType.getName()))); + .findByKeyOptional(menuItemTypeName) + .orElseGet(() -> (MainMenuItem) save(new_(MainMenuItem.class).setKey(menuItemTypeName))); } @Override protected void populateDomain() { - // Do nothing to save time. + // Override to use standard IUniversalConstants instead of UniversalConstantsForTesting. } } \ No newline at end of file From 0123c2d55351adc95814d99d2eb7bbf2535193dd Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 25 Feb 2026 20:18:23 +0200 Subject: [PATCH 36/76] #2224 Misc minor improvements, suggested by IDE, including reduced method visibility. --- .../web/resources/webui/CriteriaResource.java | 126 ++++++++---------- 1 file changed, 58 insertions(+), 68 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index eed37f87f1d..667d0328f9a 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -316,7 +316,7 @@ private String determineNonConflictingName(final String preliminaryName, final i name = preliminaryName + (index == -1 ? "" : format(CONFLICTING_TITLE_SUFFIX, index == 0 ? "" : " " + index)); } return findConfigOpt(miType, user, NAME_OF.apply(FRESH_CENTRE_NAME).apply(of(name)).apply(device()), eccCompanion, FETCH_CONFIG) - .map(conflictingConfig -> determineNonConflictingName(preliminaryName, index + 1)) + .map(_ -> determineNonConflictingName(preliminaryName, index + 1)) .orElse(name); } @@ -456,7 +456,7 @@ public static Representation createCriteriaRetrievalEnvelope( ); } - public static Representation createCriteriaDiscardEnvelope( + static Representation createCriteriaDiscardEnvelope( final ICentreDomainTreeManagerAndEnhancer updatedFreshCentre, final Class> miType, final Optional saveAsName, @@ -491,7 +491,7 @@ public static Representation createCriteriaDiscardEnvelope( ); } - public static , M extends EnhancedCentreEntityQueryCriteria>> CriteriaIndication createCriteriaIndication( + static CriteriaIndication createCriteriaIndication( final String wasRun, final ICentreDomainTreeManagerAndEnhancer freshCentre, final Class> miType, @@ -515,7 +515,7 @@ public static Representation createCriteriaDiscardEnvelope( return NONE; } - public static CriteriaIndication createChangedCriteriaIndication(final ICentreDomainTreeManagerAndEnhancer freshCentre, final ICentreDomainTreeManagerAndEnhancer savedCentre) { + static CriteriaIndication createChangedCriteriaIndication(final ICentreDomainTreeManagerAndEnhancer freshCentre, final ICentreDomainTreeManagerAndEnhancer savedCentre) { return !savedCentre.getFirstTick().selectionCriteriaEquals(freshCentre.getFirstTick()) ? CHANGED : NONE; } @@ -547,6 +547,7 @@ public static Result validateCriteriaBeforeRunning( return successful(); } + @SuppressWarnings("unchecked") public static Result generateDataIfNeeded( final EnhancedCentreEntityQueryCriteria criteriaEntity, final IWebUiConfig webUiConfig, @@ -566,7 +567,7 @@ public static Result generateDataIfNeeded( // create and execute a generator instance final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); final Map> params = criteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( - (final MetaProperty mp) -> mp.getName(), + MetaProperty::getName, (final MetaProperty mp) -> ofNullable(mp.getValue()))); params.putAll(criteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); if (shouldForceRegeneration(customObject)) { @@ -579,7 +580,6 @@ public static Result generateDataIfNeeded( /// Handles `PUT` requests triggered by the `tg-selection-criteria.run()` method. /// - @SuppressWarnings("unchecked") @Put @Override public Representation put(final Representation envelope) { @@ -593,7 +593,7 @@ public Representation put(final Representation envelope) { miType = centre.getMenuItemType(); // obtain lock for current user and miType of the centre (disregard saveAsName as it is unlikely that self-concurrent running will occur for different configurations of the same centre) - final Lock lock = locks.computeIfAbsent(t2(user, miType), t2 -> new ReentrantLock()); // create Lock if not yet present; atomic action + final Lock lock = locks.computeIfAbsent(t2(user, miType), _ -> new ReentrantLock()); // create Lock if not yet present; atomic action final boolean lockAcquired = tryLocking(lock); if (!lockAcquired) { LOGGER.info("The lock could not be acquired for [%s] seconds. Let's continue concurrent running of the [%s] centre and user [%s].".formatted(RUNNING_LOCK_TIMEOUT, miType.getSimpleName(), user)); @@ -804,7 +804,7 @@ public static List executeEntityCentreConfiguration( processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); } - final var list = new ArrayList(); + final var list = new ArrayList<>(); if (!skipCustomObjectCalculations) { list.add(isRunning ? criteriaEntity : null); list.add(pair.getKey()); @@ -883,11 +883,11 @@ private static Stream> enhanceResultEntitiesWithDynamicPropert entityRendHints = renderingHints.get(idx); } if (entityRendHints instanceof Map) { - resPropsWithContext.forEach(resPropWithContext -> { - resPropWithContext.getKey().renderingHintsProvider.ifPresent(hintProvider -> { - ((Map)entityRendHints).putAll(hintProvider.apply(entity, resPropWithContext.getValue())); - }); - }); + resPropsWithContext.forEach(resPropWithContext -> + resPropWithContext.getKey().renderingHintsProvider.ifPresent(hintProvider -> + ((Map) entityRendHints).putAll(hintProvider.apply(entity, resPropWithContext.getValue())) + ) + ); } return entity; }); @@ -909,38 +909,34 @@ private static Stream> enhanceResultEntitiesWithDynamicPropert final ICentreConfigSharingModel sharingModel) { final List>, Optional, ?>>>> resList = new ArrayList<>(); - centre.getDynamicProperties().forEach(resProp -> { - resProp.dynamicColBuilderType.ifPresent(propDefinerClass -> { - final Optional, ?>> optionalCentreContext = CentreResourceUtils.createCentreContext( - true, // full context, fully-fledged restoration. This means that IQueryEnhancer descendants (centre query enhancers) could use IContextDecomposer for context decomposition on deep levels. - webUiConfig, - companionFinder, - user, - critGenerator, - entityFactory, - centreContextHolder, - criteriaEntity, - resProp.contextConfig, - null, /* chosenProperty is not applicable in queryEnhancer context */ - device, - eccCompanion, - mmiCompanion, - userCompanion, - sharingModel - ); - resList.add(new Pair<>(resProp, optionalCentreContext)); - }); - }); + centre.getDynamicProperties().forEach(resProp -> resProp.dynamicColBuilderType.ifPresent(_ -> { + final Optional, ?>> optionalCentreContext = CentreResourceUtils.createCentreContext( + true, // full context, fully-fledged restoration. This means that IQueryEnhancer descendants (centre query enhancers) could use IContextDecomposer for context decomposition on deep levels. + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + criteriaEntity, + resProp.contextConfig, + null, /* chosenProperty is not applicable in queryEnhancer context */ + device, + eccCompanion, + mmiCompanion, + userCompanion, + sharingModel + ); + resList.add(new Pair<>(resProp, optionalCentreContext)); + })); return resList; } private static Map>> createDynamicProperties(final List>, Optional, ?>>>> resPropsWithContext, final EntityCentre> centre) { final Map>> dynamicColumns = new LinkedHashMap<>(); - resPropsWithContext.forEach(resPropWithContext -> { - centre.getDynamicColumnBuilderFor(resPropWithContext.getKey()) - .flatMap(dynColumnBuilder -> dynColumnBuilder.getColumnsConfig(resPropWithContext.getValue())) - .ifPresent(config -> dynamicColumns.put(resPropWithContext.getKey().propName.get() + "Columns", config.build())); - }); + resPropsWithContext.forEach(resPropWithContext -> centre.getDynamicColumnBuilderFor(resPropWithContext.getKey()) + .flatMap(dynColumnBuilder -> dynColumnBuilder.getColumnsConfig(resPropWithContext.getValue())) + .ifPresent(config -> dynamicColumns.put(resPropWithContext.getKey().propName.get() + "Columns", config.build()))); return dynamicColumns; } @@ -986,30 +982,26 @@ private static Map updateResultantCustomObject( final MainMenuItemCo mmiCompanion, final IUser userCompanion, final ICentreConfigSharingModel sharingModel) { - if (queryEnhancerConfig.isPresent()) { - return of(new Pair<>( - queryEnhancerConfig.get().getKey(), - CentreResourceUtils.createCentreContext( - true, // full context, fully-fledged restoration. This means that IQueryEnhancer descendants (centre query enhancers) could use IContextDecomposer for context decomposition on deep levels. - webUiConfig, - companionFinder, - user, - critGenerator, - entityFactory, - centreContextHolder, - criteriaEntity, - queryEnhancerConfig.get().getValue(), - null, /* chosenProperty is not applicable in queryEnhancer context */ - device, - eccCompanion, - mmiCompanion, - userCompanion, - sharingModel - ) - )); - } else { - return empty(); - } + return queryEnhancerConfig.map(iQueryEnhancerOptionalPair -> new Pair<>( + iQueryEnhancerOptionalPair.getKey(), + CentreResourceUtils.createCentreContext( + true, // full context, fully-fledged restoration. This means that IQueryEnhancer descendants (centre query enhancers) could use IContextDecomposer for context decomposition on deep levels. + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + criteriaEntity, + iQueryEnhancerOptionalPair.getValue(), + null, /* chosenProperty is not applicable in queryEnhancer context */ + device, + eccCompanion, + mmiCompanion, + userCompanion, + sharingModel + ) + )); } /// Assigns values to the custom properties. @@ -1022,7 +1014,7 @@ public static Stream> enhanceResultEntitiesWithCustomPropertyV { final Optional>> assignedEntitiesOp = customPropertiesAsignmentHandler - .map(handlerType -> centre.createAssignmentHandlerInstance(handlerType)) + .map(centre::createAssignmentHandlerInstance) .map(handler -> entities.map(entity -> {handler.assignValues(entity); return entity;})); final Stream> assignedEntities = assignedEntitiesOp.orElse(entities); @@ -1032,9 +1024,7 @@ public static Stream> enhanceResultEntitiesWithCustomPropertyV if (customProp.propDef.isPresent()) { final PropDef propDef = customProp.propDef.get(); final String propertyName = CalculatedProperty.generateNameFrom(propDef.title); - if (propDef.value.isPresent()) { - entity.set(propertyName, propDef.value.get()); - } + propDef.value.ifPresent(o -> entity.set(propertyName, o)); } } return entity; From 1e3af44b0191b86d95634485b6d6545c64d5b7a1 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Thu, 26 Feb 2026 00:31:33 +0200 Subject: [PATCH 37/76] #2224 Fix regression for centres with IGenerator during refresh / navigate. This was caught using web tests. Also, IDEA was showing a warning about possibly null argument leading to NPE. --- .../platform/web/resources/webui/CriteriaResource.java | 5 +++-- .../platform/web/utils/DefaultEntityCentreProcessor.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 667d0328f9a..ce10d41bf0e 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -550,12 +550,13 @@ public static Result validateCriteriaBeforeRunning( @SuppressWarnings("unchecked") public static Result generateDataIfNeeded( final EnhancedCentreEntityQueryCriteria criteriaEntity, + final Class> miType, final IWebUiConfig webUiConfig, final boolean isRunning, final boolean isSorting, final Map customObject ) { - final EntityCentre> centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); + final EntityCentre> centre = getEntityCentre(miType.getName(), webUiConfig); // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) // then for an entity centre configuration check if a generator was provided final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); @@ -650,7 +651,7 @@ public Representation put(final Representation envelope) { freshCentreAppliedCriteriaEntity = null; } - final var generationResult = generateDataIfNeeded(freshCentreAppliedCriteriaEntity, webUiConfig, isRunning, isSorting, customObject); + final var generationResult = generateDataIfNeeded(freshCentreAppliedCriteriaEntity, miType, webUiConfig, isRunning, isSorting, customObject); // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result // otherwise, proceed with the request handling further to actually query the data // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index fc80889d3f6..01fdbb7c72a 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -207,7 +207,7 @@ private > Either> entityCentreResult // Generate entities if the centre has IGenerator defined. Stop execution if generation result is not successful. // `customObject` does not have parameter for generation forcing -- this parameter is not important. - final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, webUiConfig, true, false, customObject); + final Result generationResult = generateDataIfNeeded(freshCriteriaEntity, freshCriteriaEntity.miType(), webUiConfig, true, false, customObject); if (!generationResult.isSuccessful()) { return left(generationResult); } From f7b1dbdeab71b469f6ba348258532fe018e26943 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 27 Feb 2026 19:01:24 +0200 Subject: [PATCH 38/76] #2224 Temp changes to test broader range of centres. --- .../platform/share/ShareActionProducer.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index f70f3be7504..50dba10ef47 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -19,6 +19,7 @@ import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isBlank; +import static ua.com.fielden.platform.entity.AbstractEntity.VERSION; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchIdOnly; import static ua.com.fielden.platform.tiny.TinyHyperlink.*; import static ua.com.fielden.platform.utils.QrCodeUtils.*; @@ -53,10 +54,22 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { ofNullable((String) getContext().getCustomObject().get(CUSTOM_OBJECT_SHARED_URI)).ifPresentOrElse(sharedUri -> { // Create and save a tiny hyperlink that points to the respective entity master. final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); final var tinyUrlHyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(tinyUrlHyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(tinyUrlHyperlink.value, PNG, 512, 512, 24, WHITE, BLACK))); + + final var result = entityCentreProcessor.getResult(sharedUri.substring(sharedUri.lastIndexOf("/") + 1)); + System.out.println("----------- API Execution (...) ------------"); + if (result.isRight()) { + result.asRight().value().forEach(ent -> { + System.out.println(ent); + }); + } + else { + System.out.println(result.asLeft().value().getMessage()); + } + System.out.println("----------- API Execution (end) ------------"); }, () -> { // This action must have been invoked on a master for a new persistent entity or an action entity. if (masterEntityNotEmpty()) { @@ -73,24 +86,12 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { final var savingInfoHolder = (SavingInfoHolder) centreContextHolder.getMasterEntity(); final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); final var hyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(hyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(hyperlink.value, PNG, 512, 512, 12, WHITE, BLACK))); } }); - - final var result = entityCentreProcessor.getResult("c95ec1ae-09e8-439f-92e7-880261fce023"); - System.out.println("----------- API Execution (...) ------------"); - if (result.isRight()) { - result.asRight().value().forEach(ent -> { - System.out.println(ent.get("details") + " " + ent.get("person") + " " + ent.get("date")); - }); - } - else { - System.out.println(result.asLeft().value().getMessage()); - } - System.out.println("----------- API Execution (end) ------------"); } return super.provideDefaultValues(entity); From 682275e4a6934999143e7d086c663526c5f2ef90 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 27 Feb 2026 20:25:02 +0200 Subject: [PATCH 39/76] #2224 Remove temporary code. --- .../platform/share/ShareActionProducer.java | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index 50dba10ef47..6e8ac63b69f 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -12,14 +12,12 @@ import ua.com.fielden.platform.tiny.TinyHyperlinkCo; import ua.com.fielden.platform.types.Hyperlink; import ua.com.fielden.platform.web.centre.CentreContext; -import ua.com.fielden.platform.web.utils.EntityCentreProcessor; import java.util.Base64; import java.util.Optional; import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isBlank; -import static ua.com.fielden.platform.entity.AbstractEntity.VERSION; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchIdOnly; import static ua.com.fielden.platform.tiny.TinyHyperlink.*; import static ua.com.fielden.platform.utils.QrCodeUtils.*; @@ -35,16 +33,13 @@ public class ShareActionProducer extends DefaultEntityProducerWithContext { private CentreContextHolder centreContextHolder; - private final EntityCentreProcessor entityCentreProcessor; @Inject ShareActionProducer( final EntityFactory factory, - final ICompanionObjectFinder companionFinder, - final EntityCentreProcessor entityCentreProcessor) + final ICompanionObjectFinder companionFinder) { super(factory, ShareAction.class, companionFinder); - this.entityCentreProcessor = entityCentreProcessor; } @Override @@ -54,22 +49,10 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { ofNullable((String) getContext().getCustomObject().get(CUSTOM_OBJECT_SHARED_URI)).ifPresentOrElse(sharedUri -> { // Create and save a tiny hyperlink that points to the respective entity master. final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); final var tinyUrlHyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(tinyUrlHyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(tinyUrlHyperlink.value, PNG, 512, 512, 24, WHITE, BLACK))); - - final var result = entityCentreProcessor.getResult(sharedUri.substring(sharedUri.lastIndexOf("/") + 1)); - System.out.println("----------- API Execution (...) ------------"); - if (result.isRight()) { - result.asRight().value().forEach(ent -> { - System.out.println(ent); - }); - } - else { - System.out.println(result.asLeft().value().getMessage()); - } - System.out.println("----------- API Execution (end) ------------"); }, () -> { // This action must have been invoked on a master for a new persistent entity or an action entity. if (masterEntityNotEmpty()) { @@ -86,7 +69,7 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { final var savingInfoHolder = (SavingInfoHolder) centreContextHolder.getMasterEntity(); final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); final var hyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(hyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(hyperlink.value, PNG, 512, 512, 12, WHITE, BLACK))); From 9571c3f54172bd6d32cdab8b2ec59ecd5b22bf57 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 27 Feb 2026 21:56:08 +0200 Subject: [PATCH 40/76] #2224 Add API docs. --- .../web/utils/EntityCentreProcessor.java | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 77e20fd3935..1b31b1d0f55 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -1,23 +1,62 @@ package ua.com.fielden.platform.web.utils; +import ua.com.fielden.platform.data.generator.IGenerator; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.error.Result; import ua.com.fielden.platform.types.either.Either; import ua.com.fielden.platform.types.either.Left; +import ua.com.fielden.platform.types.either.Right; +import ua.com.fielden.platform.web.centre.IQueryEnhancer; +import ua.com.fielden.platform.web.interfaces.DeviceProfile; import java.util.List; import java.util.function.Function; /// An interface for Entity Centre processing API. /// +/// The intended main usage is to execute named Entity Centre configurations on behalf of their owners as part of business logic. +/// +/// Configuration to be executed may belong to [DeviceProfile#DESKTOP] or [DeviceProfile#MOBILE] namespaces. +/// It should correspond to some standalone Entity Centre on module menus of the web application. +/// Embedded Centres are not currently supported due to the need to specify their context through some additional API. +/// +/// The next extension to this API may include: +/// - create custom named configurations; +/// - modify existing named configurations with custom criteria values, ordering, page capacity etc. +/// - cover operations for default configurations +/// - cover operations for embedded Entity Centres with some master context (see [IQueryEnhancer]). +/// public interface EntityCentreProcessor { /// Executes named Entity Centre configuration, defined by UUID, similarly as the owner may have run it through Web UI. /// Takes into account all unsaved changes in that configuration. /// - /// Important: running of this method in context of `@SessionRequired` scope may roll back active transaction. + /// Returns [Left] with invalid [Result] for the cases where + /// - UUID is not valid + /// - there is no configuration with that UUID (or there are multiple ones for some reason) + /// - there are only "orphan" inherited-from-shared configurations with no original one (i.e. if it was deleted) + /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001" + /// - UUID represents configuration with validation errors (e.g. requiredness or others) + /// - UUID represents configuration with authorisation errors (either Can Read or Can Read Property for non-empty criterion) + /// - UUID represents configuration with generator errors. + /// + /// Returns [Right] with [List] of entities corresponding to configuration criteria, ordering and page capacity. + /// Entity Centre with [IGenerator] performs generation of new data during execution through this API. + /// + /// Entity Centres with dynamic properties are supported (see `IResultSetBuilderDynamicProps#addProps` API). + /// + /// Example: + /// ``` + /// final List workOrders = entityCentreProcessor + /// .getResult(configUuid) + /// .orElseThrow(Result::throwRuntime); + /// ``` + /// + /// **Important**: running of this method in context of `@SessionRequired` scope may roll back active transaction. /// This means that API users must exercise caution if [Left] is returned (use [Either#orElseThrow(Function)]). /// + /// @param corresponds to the root entity type of the executed Entity Centre configuration. + /// > Either> getResult( String configUuid ); @@ -25,7 +64,26 @@ > Either> getResult( /// Finds out whether named Entity Centre configuration, defined by UUID, has non-empty result (similarly to Web UI running). /// Takes into account all unsaved changes in that configuration. /// - /// Important: running of this method in context of `@SessionRequired` scope may roll back active transaction. + /// Returns [Left] with invalid [Result] for the cases where + /// - UUID is not valid + /// - there is no configuration with that UUID (or there are multiple ones for some reason) + /// - there are only "orphan" inherited-from-shared configurations with no original one (i.e. if it was deleted) + /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001" + /// - UUID represents configuration with validation errors (e.g. requiredness or others) + /// - UUID represents configuration with authorisation errors (either Can Read or Can Read Property for non-empty criterion) + /// - UUID represents configuration with generator errors. + /// + /// Returns [Right] with [Boolean] indicator for presence of entities corresponding to configuration criteria. + /// Entity Centre with [IGenerator] still performs fresh data generation during execution through this API. + /// + /// Example: + /// ``` + /// final boolean workOrdersPresent = entityCentreProcessor + /// .resultExists(configUuid) + /// .orElseThrow(Result::throwRuntime); + /// ``` + /// + /// **Important**: running of this method in context of `@SessionRequired` scope may roll back active transaction. /// This means that API users must exercise caution if [Left] is returned (use [Either#orElseThrow(Function)]). /// Either resultExists( From 8403a51bdfc03e6389394a754c3c4495b10ae09d Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 27 Feb 2026 23:30:15 +0200 Subject: [PATCH 41/76] #2224 Temporarily revert some enhancements to simplify further conflict resolution. --- .../ua/com/fielden/platform/web/centre/CentreUpdater.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java index db905372838..27e387956a3 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java @@ -833,7 +833,9 @@ static ConditionModel centreConfigCondFor(final User user) { /** * Loads centre through the following chain: 'default centre' + 'differences' := 'centre'. * + * @param user * @param miType + * @param saveAsName -- user-defined title of 'saveAs' centre configuration or empty {@link Optional} for unnamed centre * @param updatedDiff -- updated differences * @param companionFinder * @@ -858,6 +860,7 @@ private static ICentreDomainTreeManagerAndEnhancer loadCentreFromDefaultAndDiff( * Please note that 'default' centre is specific to the user on current thread ({@link IUserProvider}). All injector-based default values will be user-specific * if they are defined as user-specific in domain logic. * + * @param gdtm * @param miType * @return */ @@ -868,7 +871,7 @@ public static ICentreDomainTreeManagerAndEnhancer getDefaultCentre(final Class - * Centres defined as {@code runAutomatically} not only runs automatically on loading; criteria for such centres will be cleared before auto-running. + * Centres defined as {@code runAutomatically} not only runs automatically on loading; criteria for such centres will be cleared before auto-running (see {@link CriteriaResource#put} for more details). * * @param miType * @param webUiConfig @@ -1246,6 +1249,7 @@ private static void processValue(final Map diff, final String va * Applies the differences from 'differences centre' on top of 'target centre'. * * @param targetCentre + * @param differencesCentre * @param root * @param companionFinder -- to process crit-only single entity-typed values * @return From e1dfe7680e36177682755bd455726e0b19d9bf59 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 27 Feb 2026 23:38:38 +0200 Subject: [PATCH 42/76] #2224 Extend / enhance docs. --- ...rPostgreSqlOrSqlServerContextSelector.java | 4 +- .../criteria/EntityQueryCriteriaUtils.java | 2 +- .../platform/security/tokens/TokenUtils.java | 6 +- .../web/resources/webui/CriteriaResource.java | 41 +++++++--- .../utils/DefaultEntityCentreProcessor.java | 7 ++ ...OrSqlServerContextSelectorForWebTests.java | 11 ++- .../web/utils/EntityCentreProcessorTest.java | 27 ++++++- .../platform/web/centre/CentreUpdater.java | 76 ++++++++++++------- .../web/centre/CentreUpdaterUtils.java | 22 +++--- 9 files changed, 130 insertions(+), 66 deletions(-) diff --git a/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java b/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java index e5a1833ffa6..c914e8ef055 100644 --- a/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java +++ b/platform-dao/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelector.java @@ -5,9 +5,7 @@ import ua.com.fielden.platform.test.db_creators.H2DbCreator; import ua.com.fielden.platform.test.runners.AbstractDomainDrivenTestCaseRunner; import ua.com.fielden.platform.test.runners.H2DomainDrivenTestCaseRunner.H2TestContext; -import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner; import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner.PostgresqlTestContext; -import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner; import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner.SqlServerTestContext; import java.util.Optional; @@ -15,7 +13,7 @@ import static org.apache.commons.lang3.StringUtils.isEmpty; -/// A test runner that selects a test configuration [ITestContext] from [SqlServerDomainDrivenTestCaseRunner] or [PostgresqlDomainDrivenTestCaseRunner] for running unit test. +/// A test runner that selects a test configuration [ITestContext] from [SqlServerTestContext] or [PostgresqlTestContext]. /// The criteria for selecting the appropriate test runner is based on runtime settings. /// public class H2OrPostgreSqlOrSqlServerContextSelector extends AbstractDomainDrivenTestCaseRunner { diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java index 2cb3ac343eb..e11c9adbab8 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteriaUtils.java @@ -68,7 +68,7 @@ public static boolean isPropertyAuthorised(final Class root, final String pro // Root property (aka "entity itself") is always authorised. return property.isEmpty() // Non-root property access is governed by *_CanRead_property_* tokens, where the `property` part is never empty. - || authorisePropertyReading(root, property, authorisationModel).orElseGet(Result::successful).isSuccessful(); + || authorisePropertyIfGuarded(root, property, authorisationModel).orElseGet(Result::successful).isSuccessful(); } /// Creates an Entity Centre query for a list of [QueryProperty] and other parameters. diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java index 7fadb542be0..4aaefed06ce 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/security/tokens/TokenUtils.java @@ -68,7 +68,7 @@ public static Result authoriseCriteria(final List queryProperties // Root property (aka "entity itself") is always authorised. if (!queryProperty.isEmptyWithoutMnemonics() && !"".equals(queryProperty.getPropertyName())) { final var originalType = getOriginalType(stripIfNeeded(queryProperty.getEntityClass())); - return authorisePropertyReading(originalType, queryProperty.getPropertyName(), authorisation); + return authorisePropertyIfGuarded(originalType, queryProperty.getPropertyName(), authorisation); } return Optional.empty(); }) @@ -78,7 +78,9 @@ public static Result authoriseCriteria(final List queryProperties .orElseGet(Result::successful); } - public static Optional authorisePropertyReading(final Class entityType, final String propertyName, final IAuthorisationModel authorisation) { + /// Finds authorisation annotation for entity property and performs authorisation, if present. + /// + public static Optional authorisePropertyIfGuarded(final Class entityType, final String propertyName, final IAuthorisationModel authorisation) { return getPropertyAnnotationOptionally(Authorise.class, entityType, propertyName) .map(annot -> (Class) annot.value()) .map(authorisation::authorise); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index ce10d41bf0e..4863d5efebb 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -11,6 +11,7 @@ import org.restlet.resource.Put; import ua.com.fielden.platform.criteria.generator.ICriteriaGenerator; import ua.com.fielden.platform.dao.IEntityDao; +import ua.com.fielden.platform.data.generator.IGenerator; import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer; import ua.com.fielden.platform.domaintree.impl.CalculatedProperty; import ua.com.fielden.platform.entity.AbstractEntity; @@ -456,6 +457,8 @@ public static Representation createCriteriaRetrievalEnvelope( ); } + /// Creates resource envelope of type [Representation] for discarded Entity Centre configuration. + /// static Representation createCriteriaDiscardEnvelope( final ICentreDomainTreeManagerAndEnhancer updatedFreshCentre, final Class> miType, @@ -491,6 +494,8 @@ static Representation createCriteriaDiscardEnvelope( ); } + /// Calculates [CriteriaIndication] for the Entity Centre configuration to be returned as part of resultant envelopes to the client. + /// static CriteriaIndication createCriteriaIndication( final String wasRun, final ICentreDomainTreeManagerAndEnhancer freshCentre, @@ -515,10 +520,14 @@ static CriteriaIndication createCriteriaIndication( return NONE; } + /// Calculates [CriteriaIndication] for the Entity Centre configuration by comparing "fresh" vs "saved" versions. + /// static CriteriaIndication createChangedCriteriaIndication(final ICentreDomainTreeManagerAndEnhancer freshCentre, final ICentreDomainTreeManagerAndEnhancer savedCentre) { return !savedCentre.getFirstTick().selectionCriteriaEquals(freshCentre.getFirstTick()) ? CHANGED : NONE; } + /// Authorises criteria entity on data reading for the whole centre and for individual guarded criteria with non-empty values. + /// public static Result authoriseCriteriaEntity( final EnhancedCentreEntityQueryCriteria criteriaEntity, final IAuthorisationModel authorisationModel, @@ -530,6 +539,8 @@ public static Result authoriseCriteriaEntity( : entityAuthorisationResult; } + /// Validates Entity Centre criteria entity prior running, including crit-only single prototype validation and authorisation. + /// public static Result validateCriteriaBeforeRunning( final EnhancedCentreEntityQueryCriteria criteriaEntity, final IAuthorisationModel authorisationModel, @@ -547,6 +558,10 @@ public static Result validateCriteriaBeforeRunning( return successful(); } + /// Generates Entity Centre data for the cases, where [IGenerator] is specified. + /// Refresh / navigate / sort actions are not triggering re-generation, only run does. + /// Returns the [Result] of generation, which may be invalid, based on the business logic. + /// @SuppressWarnings("unchecked") public static Result generateDataIfNeeded( final EnhancedCentreEntityQueryCriteria criteriaEntity, @@ -557,15 +572,15 @@ public static Result generateDataIfNeeded( final Map customObject ) { final EntityCentre> centre = getEntityCentre(miType.getName(), webUiConfig); - // if the run() invocation warrants data generation (e.g. it has nothing to do with sorting) - // then for an entity centre configuration check if a generator was provided + // If the run() invocation warrants data generation (e.g. it has nothing to do with sorting), + // then check if a generator was provided for an entity centre DSL configuration. final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); final boolean generationShouldOccur = isRunning && !isSorting && createdByConstraintShouldOccur; if (generationShouldOccur) { - // obtain the type for entities to be generated + // Obtain the type for entities to be generated. final Class> generatorEntityType = (Class>) centre.getGeneratorTypes().get().getKey(); - // create and execute a generator instance + // Create and execute a generator instance. final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); final Map> params = criteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( MetaProperty::getName, @@ -593,7 +608,8 @@ public Representation put(final Representation envelope) { userCompanion = companionFinder.find(User.class); miType = centre.getMenuItemType(); - // obtain lock for current user and miType of the centre (disregard saveAsName as it is unlikely that self-concurrent running will occur for different configurations of the same centre) + // Obtain lock for current user and miType of the centre. + // Disregard saveAsName as it is unlikely that self-concurrent running will occur for different configurations of the same centre. final Lock lock = locks.computeIfAbsent(t2(user, miType), _ -> new ReentrantLock()); // create Lock if not yet present; atomic action final boolean lockAcquired = tryLocking(lock); if (!lockAcquired) { @@ -634,7 +650,8 @@ public Representation put(final Representation envelope) { updatedFreshCentre = freshCentreAppliedCriteriaEntity.getCentreDomainTreeMangerAndEnhancer(); } - // There is a need to validate criteria entity with the check for 'required' properties. If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. + // There is a need to validate criteria entity, particularly with the check for 'required' properties. + // If it is not successful -- immediately return result without query running, fresh centre persistence, data generation etc. final Result validationResult = validateCriteriaBeforeRunning(freshCentreAppliedCriteriaEntity, authorisationModel, securityTokenProvider); if (!validationResult.isSuccessful()) { LOGGER.debug("CRITERIA_RESOURCE: run failed (validation failed)."); @@ -651,10 +668,13 @@ public Representation put(final Representation envelope) { freshCentreAppliedCriteriaEntity = null; } + // Handle generation logic for Entity Centres with `IGenerator`. + /// Refresh / navigate / sort actions are not triggering re-generation, only run does. final var generationResult = generateDataIfNeeded(freshCentreAppliedCriteriaEntity, miType, webUiConfig, isRunning, isSorting, customObject); - // if the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result - // otherwise, proceed with the request handling further to actually query the data - // in most cases, the generated and queried data would be represented by the same entity and, thus, the final query needs to be enhanced with user related filtering by property 'createdBy' + // If the data generation was unsuccessful based on the returned Result value then stop any further logic and return the obtained result. + // Otherwise, proceed with the request handling further to actually query the data. + // In most cases, the generated and queried data would be represented by the same entity. + // And, in that case, the final query will be enhanced with user-related filtering by `createdBy` property. if (!generationResult.isSuccessful()) { LOGGER.debug("CRITERIA_RESOURCE: run finished (generation failed)."); final var criteriaIndication = createCriteriaIndication((String) centreContextHolder.getModifHolder().get("@@wasRun"), updatedFreshCentre, miType, saveAsName, user, companionFinder, device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion); @@ -683,6 +703,7 @@ public Representation put(final Representation envelope) { } } + // Execute actual Entity Centre configuration run / refresh / navigate / sort logic. final var resultList = executeEntityCentreConfiguration( new ConfigSettings(saveAsName, user, device(), miType), of(t2(updatedFreshCentre, previouslyRunCentre)), @@ -701,8 +722,6 @@ public Representation put(final Representation envelope) { sharingModel ); - // NOTE: the following line can be the example how 'criteria running' server errors manifest to the client application - // throw new IllegalStateException("Illegal state during criteria running."); LOGGER.debug("CRITERIA_RESOURCE: run finished."); return restUtil.rawListJsonRepresentation(resultList.toArray()); } finally { diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 01fdbb7c72a..274d124d63b 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -52,6 +52,8 @@ import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.RunActions.RUN; import static ua.com.fielden.platform.web.resources.webui.CriteriaResource.*; +/// Default [EntityCentreProcessor] implementation, that uses [IWebUiConfig] Entity Centres registry and persistent storage. +/// public class DefaultEntityCentreProcessor implements EntityCentreProcessor { private static final String ERR_EXECUTION_FAILED_PREFIX = "Entity Centre configuration execution failed. "; public static final String ERR_CONFIG_UUID_IS_BLANK = ERR_EXECUTION_FAILED_PREFIX + "Config UUID [%s] is blank."; @@ -134,6 +136,7 @@ private static Either determineConfigurationSettings(fin ); // If there is no such configuration, return invalid `Result`. + // This also covers situation with two or more such configurations, which should not be possible. if (freshConfigOpt.isEmpty()) { return left(failure(ERR_CONFIG_DOES_NOT_EXIST.formatted(configUuid))); } @@ -171,6 +174,10 @@ public > Either> getResult(final Str return entityCentreResult(configUuid, empty()); } + /// Executes Entity Centre configuration with `configUuid`. + /// + /// @param maybeCustomPageCapacity optional page capacity, which will override the page capacity of the configuration + /// private > Either> entityCentreResult( final String configUuid, final Optional maybeCustomPageCapacity diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java index daf85eccdbf..37c9ce1f36c 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/test_config/H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.java @@ -3,11 +3,8 @@ import ua.com.fielden.platform.test.DbCreator; import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; import ua.com.fielden.platform.test.runners.AbstractDomainDrivenTestCaseRunner; -import ua.com.fielden.platform.test.runners.H2DomainDrivenTestCaseRunner; import ua.com.fielden.platform.test.runners.H2DomainDrivenTestCaseRunner.H2TestContext; -import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner; import ua.com.fielden.platform.test.runners.PostgresqlDomainDrivenTestCaseRunner.PostgresqlTestContext; -import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner; import ua.com.fielden.platform.test.runners.SqlServerDomainDrivenTestCaseRunner.SqlServerTestContext; import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.test.server.DataPopulationConfigForWebTests; @@ -15,7 +12,7 @@ import java.util.Optional; import java.util.Properties; -/// A test runner that selects a test configuration [ITestContext] from [SqlServerDomainDrivenTestCaseRunner] or [PostgresqlDomainDrivenTestCaseRunner] for running unit test. +/// A test runner that selects a test configuration [ITestContext] from [SqlServerTestContextForWebTests] or [PostgresqlTestContextForWebTests]. /// The criteria for selecting the appropriate test runner is based on runtime settings. /// /// This test runner is capable for running tests with Web UI infrastructure such as [IWebUiConfig]. @@ -31,6 +28,8 @@ public H2OrPostgreSqlOrSqlServerContextSelectorForWebTests(Class klass, Class super(klass, dbCreatorType, testConfig); } + /// [PostgresqlTestContext] with Web UI infrastructure capabilities. + /// private static class PostgresqlTestContextForWebTests extends PostgresqlTestContext { @Override public Properties mkDbProps(String dbUri) { @@ -38,6 +37,8 @@ public Properties mkDbProps(String dbUri) { } } + /// [SqlServerTestContext] with Web UI infrastructure capabilities. + /// private static class SqlServerTestContextForWebTests extends SqlServerTestContext { @Override public Properties mkDbProps(String dbUri) { @@ -45,6 +46,8 @@ public Properties mkDbProps(String dbUri) { } } + /// [H2TestContext] with Web UI infrastructure capabilities. + /// private static class H2TestContextForWebTests extends H2TestContext { public H2TestContextForWebTests(AbstractDomainDrivenTestCaseRunner runner) { super(runner); diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 6d92899801c..c18646cb4dc 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -39,8 +39,9 @@ import static ua.com.fielden.platform.web.interfaces.DeviceProfile.MOBILE; import static ua.com.fielden.platform.web.utils.DefaultEntityCentreProcessor.*; -/// Tests for {@link EntityCentreProcessor}. These should serve as an example of how end-application tests can be -/// implemented. +/// Tests for {@link EntityCentreProcessor}. +/// +/// These can serve as an example of how end-application tests can be implemented. /// @RunWith(H2OrPostgreSqlOrSqlServerContextSelectorForWebTests.class) public class EntityCentreProcessorTest extends AbstractDaoTestCase { @@ -326,10 +327,20 @@ public void executing_resultExists_method_returns_invalid_result_for_configurati assertEquals("Can not generate the instance based on current user [%s], choose another user for that.".formatted(getUser()), result.asLeft().value().getMessage()); } + /// Initialise test data for config `uuid` (desktop device profile). + /// + /// @param createData runnable for custom data creation + /// @param enhanceCentreManager mutating function for centre manager to provide custom criteria and other configuration parameters + /// private void initTestData(final String uuid, final Runnable createData, final Consumer enhanceCentreManager) { initTestData(uuid, DESKTOP, createData, enhanceCentreManager); } + /// Initialise test data for config `uuid` and `device` profile. + /// + /// @param createData runnable for custom data creation + /// @param enhanceCentreManager mutating function for centre manager to provide custom criteria and other configuration parameters + /// private void initTestData(final String uuid, final DeviceProfile device, final Runnable createData, final Consumer enhanceCentreManager) { setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); @@ -342,6 +353,10 @@ private void initTestData(final String uuid, final DeviceProfile device, final R initTestData(enhanceCentreManager, configSettings); } + /// Initialise test data for `configSettings`. + /// + /// @param enhanceCentreManager mutating function for centre manager to provide custom criteria and other configuration parameters + /// private void initTestData(Consumer enhanceCentreManager, ConfigSettings configSettings) { final IWebUiConfig webUiConfig = getInstance(IWebUiConfig.class); final ICompanionObjectFinder companionFinder = getInstance(ICompanionObjectFinder.class); @@ -377,6 +392,9 @@ private void createConfig( } } + /// Get (or create) entity instance for `menuItemType`. + /// Use `null` for some non-existing type, that still exists in persistent storage for some reason. + /// private MainMenuItem getMenuItem(final Class menuItemType) { if (menuItemType == null) { return getMenuItemForName(null); @@ -384,6 +402,9 @@ private MainMenuItem getMenuItem(final Class menuItemType) { return getMenuItemForName(menuItemType.getName()); } + /// Get (or create) entity instance for `menuItemTypeName`. + /// Use `null` for some non-existing type, that still exists in persistent storage for some reason. + /// private MainMenuItem getMenuItemForName(final String menuItemTypeName) { if (menuItemTypeName == null) { return getMenuItemForName(NON_EXISTING_MI_TYPE_NAME); @@ -395,7 +416,7 @@ private MainMenuItem getMenuItemForName(final String menuItemTypeName) { @Override protected void populateDomain() { - // Override to use standard IUniversalConstants instead of UniversalConstantsForTesting. + // Override to use standard IUniversalConstants implementation instead of UniversalConstantsForTesting. } } \ No newline at end of file diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java index 27e387956a3..0c9cb1fabc3 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java @@ -173,13 +173,17 @@ enum MetaValueType { return ent.getKey().toString(); } }, entity); - /** - * Function to get title of surrogate configuration from surrogate name, save-as name and device. - */ + + /// Function to get title of surrogate configuration from surrogate name, save-as name and device. + /// public static final Function, Function>> NAME_OF = surrogateName -> saveAs -> device -> deviceSpecific(saveAsSpecific(surrogateName, saveAs), device) + DIFFERENCES_SUFFIX; + + /// Function to get query prefix for title of surrogate configuration from surrogate name and device. + /// public static final Function> PREFIX_OF = surrogateName -> device -> deviceSpecific(surrogateName, device) + "[%"; - /** Protected default constructor to prevent instantiation. */ + /// Protected default constructor to prevent instantiation. + /// protected CentreUpdater() { } @@ -761,7 +765,7 @@ public static String obtainTitleFrom(final String title, final String surrogateN /** * Receives actual title from surrogate name persisted inside {@link EntityCentreConfig#getTitle()}. - * + * * @param title * @param surrogateNamePrefix * @return @@ -771,63 +775,77 @@ private static String obtainTitleFrom(final String title, final String surrogate return surrogateWithSuffix.substring(1, surrogateWithSuffix.lastIndexOf("]")); } + /// Creates composable centre configuration query for `device` and `surrogateName`. + /// + /// @param device the device for which centre configurations are looked for + /// @param surrogateName surrogate name of the centre (fresh, previouslyRun etc.) + /// private static ICompoundCondition0 centreConfigQueryFor(final DeviceProfile device, final String surrogateName) { return select(EntityCentreConfig.class) .where().prop("title").like().val(PREFIX_OF.apply(surrogateName).apply(device)) .and().prop("title").notLike().val(PREFIX_OF.apply(surrogateName).apply(opposite(device))); } + /// Creates composable centre configuration query for `uuid`, `device` and `surrogateName`. + /// + /// @param device the device for which centre configurations are looked for + /// @param surrogateName surrogate name of the centre (fresh, previouslyRun etc.) + /// private static ICompoundCondition0 centreConfigQueryFor(final String uuid, final DeviceProfile device, final String surrogateName) { return centreConfigQueryFor(device, surrogateName) .and().condition(centreConfigCondFor(uuid)); } + /// Creates composable centre configuration query for `uuid`, `miType`, `device` and `surrogateName`. + /// + /// @param device the device for which centre configurations are looked for + /// @param surrogateName surrogate name of the centre (fresh, previouslyRun etc.) + /// static ICompoundCondition0 centreConfigQueryFor(final String uuid, final Class> miType, final DeviceProfile device, final String surrogateName) { return centreConfigQueryFor(uuid, device, surrogateName) .and().condition(centreConfigCondFor(miType)); } + /// Creates composable centre configuration condition for `uuid`. + /// public static ConditionModel centreConfigCondFor(final String uuid) { return cond().prop("configUuid").eq().val(uuid).model(); } - /** - * Creates a function that returns a query to find centre configurations persisted. - *

- * Looks only for named / link configurations, default configurations are avoided. - * - * @param miType - * @param device -- the device for which centre configurations are looked for - * @param surrogateName -- surrogate name of the centre (fresh, previouslyRun etc.) - * @return - */ + /// Creates a function that returns a query to find centre persisted configurations. + /// + /// Looks only for named / link configurations, default configurations are skipped. + /// + /// @param device -- the device for which centre configurations are looked for + /// @param surrogateName -- surrogate name of the centre (fresh, previouslyRun etc.) + /// static ICompoundCondition0 centreConfigQueryFor(final Class> miType, final DeviceProfile device, final String surrogateName) { return centreConfigQueryFor(device, surrogateName) - .and().condition(centreConfigCondFor(miType)) ; + .and().condition(centreConfigCondFor(miType)); } + /// Creates composable centre configuration condition for `miType`. + /// private static ConditionModel centreConfigCondFor(Class> miType) { return cond().prop("menuItem.key").eq().val(miType.getName()).model(); } - /** - * Creates a function that returns a query to find centre configurations persisted for user. - *

- * Looks only for named / link configurations, default configurations are avoided. - * - * @param user - * @param miType - * @param device -- the device for which centre configurations are looked for - * @param surrogateName -- surrogate name of the centre (fresh, previouslyRun etc.) - * @return - */ + /// Creates a function that returns a query to find persisted centre configurations for `user`. + /// + /// Looks only for named / link configurations, default configurations are skipped. + /// + /// @param device -- the device for which centre configurations are looked for + /// @param surrogateName -- surrogate name of the centre (fresh, previouslyRun etc.) + /// static ICompoundCondition0 centreConfigQueryFor(final User user, final Class> miType, final DeviceProfile device, final String surrogateName) { return centreConfigQueryFor(miType, device, surrogateName) .and().condition(centreConfigCondFor(user)); } - static ConditionModel centreConfigCondFor(final User user) { - return cond().prop("owner").eq().val(user).model(); + /// Creates composable centre configuration condition for `owner`. + /// + static ConditionModel centreConfigCondFor(final User owner) { + return cond().prop("owner").eq().val(owner).model(); } /** diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java index f6765be2468..87c5546431a 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdaterUtils.java @@ -180,10 +180,9 @@ private static MainMenuItem getMenuItem(final Class menuItemType, final MainM }); } - /** - * Overrides existing {@link EntityCentreConfig} instance with new serialised diff. - * Otherwise, in case where there is no such instance in database, creates and saves new {@link EntityCentreConfig} instance with serialised diff inside. - */ + /// Overrides existing [EntityCentreConfig] instance with new serialised diff. + /// Otherwise, in case where there is no such instance in database, creates and saves new [EntityCentreConfig] instance with serialised diff inside. + /// public static Map saveEntityCentreManager( final Map differences, final Class menuItemType, @@ -231,9 +230,8 @@ public static Optional findConfigOpt(final Class miType, ); } - /** - * Finds optional configuration for {@code model} with predefined fetch model, sufficient for most situations. - */ + /// Finds optional configuration for `model` with predefined fetch model, sufficient for most situations. + /// private static Optional findConfigOptByModel(final ICompoundCondition0 model, final EntityCentreConfigCo eccCompanion) { return eccCompanion.getEntityOptional( from(model.model()) @@ -244,16 +242,14 @@ private static Optional findConfigOptByModel(final ICompound ); } - /** - * Finds optional configuration for {@code uuid}, {@code miType}, {@code device} and {@code surrogateName} with predefined fetch model, sufficient for most situations. - */ + /// Finds optional configuration for `uuid`, `miType`, `device` and `surrogateName` with predefined fetch model, sufficient for most situations. + /// public static Optional findConfigOptByUuid(final String uuid, final Class> miType, final DeviceProfile device, final String surrogateName, final EntityCentreConfigCo eccCompanion) { return findConfigOptByModel(centreConfigQueryFor(uuid, miType, device, surrogateName), eccCompanion); } - /** - * Finds optional configuration for {@code uuid}, {@code user}, {@code miType}, {@code device} and {@code surrogateName} with predefined fetch model, sufficient for most situations. - */ + /// Finds optional configuration for `uuid`, `user`, `miType`, `device` and `surrogateName` with predefined fetch model, sufficient for most situations. + /// public static Optional findConfigOptByUuid(final String uuid, final User user, final Class> miType, final DeviceProfile device, final String surrogateName, final EntityCentreConfigCo eccCompanion) { return findConfigOptByModel(centreConfigQueryFor(uuid, miType, device, surrogateName).and().condition(centreConfigCondFor(user)), eccCompanion); } From ec1af5519b8ba5cc24fb18b06930d5557cc38be8 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 28 Feb 2026 03:28:39 +0200 Subject: [PATCH 43/76] #2224 Further changes to resolve conflicts / compilation errors. --- .../platform/share/ShareActionProducer.java | 23 ++++++++++++++++--- .../resources/webui/CentreResourceUtils.java | 7 ++---- .../web/resources/webui/CriteriaResource.java | 6 ----- .../utils/DefaultEntityCentreProcessor.java | 9 +------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index 6e8ac63b69f..50dba10ef47 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -12,12 +12,14 @@ import ua.com.fielden.platform.tiny.TinyHyperlinkCo; import ua.com.fielden.platform.types.Hyperlink; import ua.com.fielden.platform.web.centre.CentreContext; +import ua.com.fielden.platform.web.utils.EntityCentreProcessor; import java.util.Base64; import java.util.Optional; import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isBlank; +import static ua.com.fielden.platform.entity.AbstractEntity.VERSION; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchIdOnly; import static ua.com.fielden.platform.tiny.TinyHyperlink.*; import static ua.com.fielden.platform.utils.QrCodeUtils.*; @@ -33,13 +35,16 @@ public class ShareActionProducer extends DefaultEntityProducerWithContext { private CentreContextHolder centreContextHolder; + private final EntityCentreProcessor entityCentreProcessor; @Inject ShareActionProducer( final EntityFactory factory, - final ICompanionObjectFinder companionFinder) + final ICompanionObjectFinder companionFinder, + final EntityCentreProcessor entityCentreProcessor) { super(factory, ShareAction.class, companionFinder); + this.entityCentreProcessor = entityCentreProcessor; } @Override @@ -49,10 +54,22 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { ofNullable((String) getContext().getCustomObject().get(CUSTOM_OBJECT_SHARED_URI)).ifPresentOrElse(sharedUri -> { // Create and save a tiny hyperlink that points to the respective entity master. final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); final var tinyUrlHyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(tinyUrlHyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(tinyUrlHyperlink.value, PNG, 512, 512, 24, WHITE, BLACK))); + + final var result = entityCentreProcessor.getResult(sharedUri.substring(sharedUri.lastIndexOf("/") + 1)); + System.out.println("----------- API Execution (...) ------------"); + if (result.isRight()) { + result.asRight().value().forEach(ent -> { + System.out.println(ent); + }); + } + else { + System.out.println(result.asLeft().value().getMessage()); + } + System.out.println("----------- API Execution (end) ------------"); }, () -> { // This action must have been invoked on a master for a new persistent entity or an action entity. if (masterEntityNotEmpty()) { @@ -69,7 +86,7 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { final var savingInfoHolder = (SavingInfoHolder) centreContextHolder.getMasterEntity(); final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); final var hyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(hyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(hyperlink.value, PNG, 512, 512, 12, WHITE, BLACK))); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index 36f603b2f22..d3e1a00b367 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -485,15 +485,12 @@ private static void applyMetaValues(final ICentreDomainTreeManagerAndEnhancer cd final ICompanionObjectFinder companionFinder, final ICriteriaGenerator critGenerator, final IWebUiConfig webUiConfig, - final EntityCentreConfigCo eccCompanion, - final MainMenuItemCo mmiCompanion, - final IUser userCompanion, final ICentreConfigSharingModel sharingModel ) { // Load / update centre manager instance from persistence storage. - final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), surrogateName, configSettings.saveAsName(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), surrogateName, configSettings.saveAsName(), configSettings.device(), webUiConfig, companionFinder); // Construct criteria validation prototype. - final M validationPrototype = createCriteriaValidationPrototype(configSettings.miType(), configSettings.saveAsName(), centreManager, companionFinder, critGenerator, -1L, configSettings.owner(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + final M validationPrototype = createCriteriaValidationPrototype(configSettings.miType(), configSettings.saveAsName(), centreManager, companionFinder, critGenerator, -1L, configSettings.owner(), configSettings.device(), webUiConfig, sharingModel); // Apply meta-state resetting. return resetMetaStateForCriteriaValidationPrototype(validationPrototype, getOriginalManagedType(validationPrototype.getType(), centreManager)); } diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index b80e8a4a098..66e05335933 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -692,9 +692,6 @@ public Representation put(final Representation envelope) { critGenerator, entityFactory, centreContextHolder, - eccCompanion, - mmiCompanion, - userCompanion, sharingModel ); @@ -726,9 +723,6 @@ public static List executeEntityCentreConfiguration( final ICriteriaGenerator critGenerator, final EntityFactory entityFactory, final CentreContextHolder centreContextHolder, - final EntityCentreConfigCo eccCompanion, - final MainMenuItemCo mmiCompanion, - final IUser userCompanion, final ICentreConfigSharingModel sharingModel ) { final EntityCentre> centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 274d124d63b..670c9c81a4a 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -199,12 +199,8 @@ private > Either> entityCentreResult // The only necessary setting is indication that centre should be run (i.e. not a page refresh / navigate). final Map customObject = mapOf(t2(RUN_ACTION_KEY, RUN.toString())); - final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); - final IUser userCompanion = companionFinder.find(User.class); - final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); - // Create criteria entity for "fresh" surrogate configuration. - final EnhancedCentreEntityQueryCriteria, ?> freshCriteriaEntity = createCriteriaValidationPrototype(FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, eccCompanion, mmiCompanion, userCompanion, sharingModel); + final EnhancedCentreEntityQueryCriteria, ?> freshCriteriaEntity = createCriteriaValidationPrototype(FRESH_CENTRE_NAME, configSettings, companionFinder, critGenerator, webUiConfig, sharingModel); // Validate the criteria entity. Stop execution if it is invalid. final Result validationResult = validateCriteriaBeforeRunning(freshCriteriaEntity, authorisationModel, securityTokenProvider); @@ -236,9 +232,6 @@ private > Either> entityCentreResult critGenerator, entityFactory, null, - eccCompanion, - mmiCompanion, - userCompanion, sharingModel ); From 642a7ac2d28950103a303ee87818f03a2c5d8b65 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 28 Feb 2026 16:07:23 +0200 Subject: [PATCH 44/76] #2224 Minor improvement. --- .../web/resources/webui/CriteriaResource.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 66e05335933..b717f044264 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -54,13 +54,13 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; import static java.lang.String.format; import static java.util.Optional.*; import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.toMap; import static ua.com.fielden.platform.data.generator.IGenerator.FORCE_REGENERATION_KEY; import static ua.com.fielden.platform.data.generator.IGenerator.shouldForceRegeneration; import static ua.com.fielden.platform.error.Result.failure; @@ -550,21 +550,25 @@ public static Result generateDataIfNeeded( final boolean isSorting, final Map customObject ) { - final EntityCentre> centre = getEntityCentre(miType.getName(), webUiConfig); + final var centre = getEntityCentre(miType.getName(), webUiConfig); // If the run() invocation warrants data generation (e.g. it has nothing to do with sorting), // then check if a generator was provided for an entity centre DSL configuration. - final boolean createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); - final boolean generationShouldOccur = isRunning && !isSorting && createdByConstraintShouldOccur; + final var createdByConstraintShouldOccur = centre.getGeneratorTypes().isPresent(); + final var generationShouldOccur = isRunning && !isSorting && createdByConstraintShouldOccur; if (generationShouldOccur) { // Obtain the type for entities to be generated. - final Class> generatorEntityType = (Class>) centre.getGeneratorTypes().get().getKey(); + final var generatorEntityType = (Class>) centre.getGeneratorTypes().get().getKey(); // Create and execute a generator instance. final var generator = centre.createGeneratorInstance(centre.getGeneratorTypes().get().getValue()); final Map> params = criteriaEntity.nonProxiedProperties().collect(toLinkedHashMap( - MetaProperty::getName, - (final MetaProperty mp) -> ofNullable(mp.getValue()))); - params.putAll(criteriaEntity.getParameters().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> Optional.ofNullable(entry.getValue())))); + MetaProperty::getName, + mp -> ofNullable(mp.getValue()) + )); + params.putAll(criteriaEntity.getParameters().entrySet().stream().collect(toMap( + Map.Entry::getKey, + entry -> ofNullable(entry.getValue())) + )); if (shouldForceRegeneration(customObject)) { params.put(FORCE_REGENERATION_KEY, of(true)); } From bb385f9ca3e70f005f1fc7538084943df755874f Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 28 Feb 2026 16:33:49 +0200 Subject: [PATCH 45/76] #2224 Slight doc modernisation. --- .../platform/web/centre/CentreUpdater.java | 88 ++++++------------- 1 file changed, 27 insertions(+), 61 deletions(-) diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java index f93eafe6169..f27288993f0 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/centre/CentreUpdater.java @@ -869,17 +869,8 @@ static ConditionModel centreConfigCondFor(final User owner) { return cond().prop("owner").eq().val(owner).model(); } - /** - * Loads centre through the following chain: 'default centre' + 'differences' := 'centre'. - * - * @param user - * @param miType - * @param saveAsName -- user-defined title of 'saveAs' centre configuration or empty {@link Optional} for unnamed centre - * @param updatedDiff -- updated differences - * @param companionFinder - * - * @return - */ + /// Loads centre through the following chain: 'default centre' + 'differences' => 'centre'. + /// private static ICentreDomainTreeManagerAndEnhancer loadCentreFromDefaultAndDiff( final Class> miType, final Map updatedDiff, @@ -890,32 +881,24 @@ private static ICentreDomainTreeManagerAndEnhancer loadCentreFromDefaultAndDiff( return applyDifferences(defaultCentre, updatedDiff, getEntityType(miType), companionFinder); } - /** - * Creates user-specific (!) default centre manager from Centre DSL configuration. - *

- * IMPORTANT: this 'default centre' is used for constructing 'fresh centre', 'previouslyRun centre' and their 'diff centres', that is why it is very important to make it suitable for Web UI default values. - * All other centres will reuse such Web UI specific default values. - *

- * Please note that 'default' centre is specific to the user on current thread ({@link IUserProvider}). All injector-based default values will be user-specific - * if they are defined as user-specific in domain logic. - * - * @param gdtm - * @param miType - * @return - */ + /// Creates user-specific (!) default centre manager from Centre DSL configuration. + /// + /// IMPORTANT: this 'default centre' is used for constructing 'fresh centre', 'previouslyRun centre' and their 'diff centres'. + /// That's why it is very important to make it suitable for Web UI default values. + /// All other centres will reuse such Web UI specific default values. + /// + /// Please note that 'default' centre is specific to the user on current thread ([IUserProvider]). + /// All injector-based default values will be user-specific if they are defined as user-specific in domain logic. + /// public static ICentreDomainTreeManagerAndEnhancer getDefaultCentre(final Class> miType, final IWebUiConfig webUiConfig) { return applyWebUIDefaultValues(createDefaultCentre(miType, webUiConfig), getEntityType(miType)); } - /** - * Returns {@code runAutomatically} parameter for the Centre DSL configuration defined by {@code miType}. - *

- * Centres defined as {@code runAutomatically} not only runs automatically on loading; criteria for such centres will be cleared before auto-running (see {@link CriteriaResource#put} for more details). - * - * @param miType - * @param webUiConfig - * @return - */ + /// Returns `runAutomatically` parameter for the Centre DSL configuration defined by `miType`. + /// + /// Centres defined as `runAutomatically` not only runs automatically on loading. + /// Criteria for such centres will be cleared before auto-running (see `CriteriaResource#put` for more details). + /// public static boolean defaultRunAutomatically(final Class> miType, final IWebUiConfig webUiConfig) { return ofNullable(webUiConfig.getCentres().get(miType)) // additional safety in case if for some reason there is no EntityCentre instance for miType .map(EntityCentre::isRunAutomatically) @@ -1284,15 +1267,10 @@ private static void processValue(final Map diff, final String va } } - /** - * Applies the differences from 'differences centre' on top of 'target centre'. - * - * @param targetCentre - * @param differencesCentre - * @param root - * @param companionFinder -- to process crit-only single entity-typed values - * @return - */ + /// Applies the differences from 'differences centre' on top of 'target centre'. + /// + /// @param companionFinder to process crit-only single entity-typed values + /// static ICentreDomainTreeManagerAndEnhancer applyDifferences(final ICentreDomainTreeManagerAndEnhancer targetCentre, final Map differences, final Class> root, final ICompanionObjectFinder companionFinder) { final Supplier> managedTypeSupplier = () -> targetCentre.getEnhancer().getManagedType(root); final Map> propertiesDiff = (Map>) differences.get(PROPERTIES); @@ -1385,25 +1363,16 @@ static ICentreDomainTreeManagerAndEnhancer applyDifferences(final ICentreDomainT return targetCentre; } - /** - * Takes property differences from diff. Creates empty property differences inside diff if they are empty. - * - * @param property - * @param diff - * @return - */ + /// Takes property differences from `diff`. Creates empty property differences inside `diff` if they are empty. + /// static Map propDiff(final String property, final Map diff) { final Map> propertiesDiff = (Map>) diff.get(PROPERTIES); return diff(property, propertiesDiff); } - /** - * Takes property differences from propertiesDiff part of overall diff. Creates empty property differences inside propertiesDiff if they are empty. - * - * @param property - * @param propertiesDiff - * @return - */ + /// Takes property differences from `propertiesDiff` part of overall diff. + /// Creates empty property differences inside `propertiesDiff` if they are empty. + /// private static Map diff(final String property, final Map> propertiesDiff) { final Map propertyDiff = propertiesDiff.get(property); if (propertyDiff == null) { @@ -1414,11 +1383,8 @@ private static Map diff(final String property, final Map createEmptyDifferences() { final Map diff = new LinkedHashMap<>(); final Map> propertiesDiff = new LinkedHashMap<>(); From 7559bf85194a5c5435cbd4d74ec546e2d2cae26d Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 28 Feb 2026 17:04:00 +0200 Subject: [PATCH 46/76] #2224 Further older code modernisation. --- .../web/resources/webui/CriteriaResource.java | 95 ++++++++++--------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index b717f044264..de90e152598 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -729,73 +729,78 @@ public static List executeEntityCentreConfiguration( final CentreContextHolder centreContextHolder, final ICentreConfigSharingModel sharingModel ) { - final EntityCentre> centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); - final Pair, List>> pair = createCriteriaMetaValuesCustomObjectWithResult( - customObject, - complementCriteriaEntityBeforeRunning( // complements criteriaEntity instance - criteriaEntity, - webUiConfig, - companionFinder, - configSettings.owner(), - critGenerator, - entityFactory, - centreContextHolder, - sharingModel - ) + final var centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); + + final var resultCustomObjectAndEntities = createCriteriaMetaValuesCustomObjectWithResult( + customObject, + complementCriteriaEntityBeforeRunning( // complements criteriaEntity instance + criteriaEntity, + webUiConfig, + companionFinder, + configSettings.owner(), + critGenerator, + entityFactory, + centreContextHolder, + sharingModel + ) ); + final var resultCustomObject = resultCustomObjectAndEntities.getKey(); + final var resultEntities = resultCustomObjectAndEntities.getValue(); + final var skipCustomObjectCalculations = updatedFreshCentreAndPreviouslyRunCentre.isEmpty(); if (!skipCustomObjectCalculations) { if (isRunning) { - final ICentreDomainTreeManagerAndEnhancer updatedFreshCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._1; - final ICentreDomainTreeManagerAndEnhancer previouslyRunCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._2; + final var updatedFreshCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._1; + final var previouslyRunCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._2; final var updatedSavedCentre = updateCentre(configSettings.owner(), configSettings.miType(), SAVED_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), webUiConfig, companionFinder); final var changedCriteriaIndication = createChangedCriteriaIndication(updatedFreshCentre, updatedSavedCentre); - updateResultantCustomObject(criteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), configSettings.miType(), configSettings.saveAsName(), previouslyRunCentre, pair.getKey(), of(changedCriteriaIndication)); + updateResultantCustomObject(criteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), configSettings.miType(), configSettings.saveAsName(), previouslyRunCentre, resultCustomObject, of(changedCriteriaIndication)); } // Running the rendering customiser for result set of entities. - pair.getKey().put("renderingHints", createRenderingHints(pair.getValue(), centre)); + resultCustomObject.put("renderingHints", createRenderingHints(resultEntities, centre)); - // Apply primary and secondary action selectors - pair.getKey().putAll(linkedMapOf(createPrimaryActionIndicesForCentre(pair.getValue(), centre))); - pair.getKey().putAll(linkedMapOf(createSecondaryActionIndicesForCentre(pair.getValue(), centre))); - pair.getKey().putAll(linkedMapOf(createPropertyActionIndicesForCentre(pair.getValue(), centre))); + // Apply primary and secondary action selectors. + resultCustomObject.putAll(linkedMapOf(createPrimaryActionIndicesForCentre(resultEntities, centre))); + resultCustomObject.putAll(linkedMapOf(createSecondaryActionIndicesForCentre(resultEntities, centre))); + resultCustomObject.putAll(linkedMapOf(createPropertyActionIndicesForCentre(resultEntities, centre))); } // Build dynamic properties object - final List>, Optional, ?>>>> resPropsWithContext = getDynamicResultProperties( - centre, - webUiConfig, - companionFinder, - configSettings.owner(), - critGenerator, - entityFactory, - centreContextHolder, - criteriaEntity, - configSettings.device(), - sharingModel); + final var resPropsWithContext = getDynamicResultProperties( + centre, + webUiConfig, + companionFinder, + configSettings.owner(), + critGenerator, + entityFactory, + centreContextHolder, + criteriaEntity, + configSettings.device(), + sharingModel + ); if (!skipCustomObjectCalculations) { - pair.getKey().put("dynamicColumns", createDynamicProperties(resPropsWithContext, centre)); + resultCustomObject.put("dynamicColumns", createDynamicProperties(resPropsWithContext, centre)); } Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( centre, centre.getCustomPropertiesDefinitions(), centre.getCustomPropertiesAsignmentHandler(), - pair.getValue().stream()); + resultEntities.stream()); - //Enhance entities with values defined with consumer in each dynamic property. + // Enhance entities with values defined with consumer in each dynamic property. processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); if (!skipCustomObjectCalculations) { - //Enhance rendering hints with styles for each dynamic column. - processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) pair.getKey().get("renderingHints")); + // Enhance rendering hints with styles for each dynamic column. + processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) resultCustomObject.get("renderingHints")); } final var list = new ArrayList<>(); if (!skipCustomObjectCalculations) { list.add(isRunning ? criteriaEntity : null); - list.add(pair.getKey()); + list.add(resultCustomObject); } // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence @@ -849,9 +854,9 @@ public static Stream> enhanceResultEntitiesWithDynamicProperty final List>, Optional, ?>>>> resPropsWithContext) { return stream.map(entity -> { - resPropsWithContext.forEach(resPropWithContext -> { - resPropWithContext.getKey().entityPreProcessor.get().accept(entity, resPropWithContext.getValue()); - }); + resPropsWithContext.forEach(resPropWithContext -> + resPropWithContext.getKey().entityPreProcessor.get().accept(entity, resPropWithContext.getValue()) + ); return entity; }); } @@ -957,12 +962,12 @@ private static Map updateResultantCustomObject( final ICriteriaGenerator critGenerator, final EntityFactory entityFactory, final CentreContextHolder centreContextHolder, - final Optional, Optional>> queryEnhancerConfig, + final Optional, Optional>> maybeQueryEnhancerAndConfig, final EnhancedCentreEntityQueryCriteria criteriaEntity, final DeviceProfile device, final ICentreConfigSharingModel sharingModel) { - return queryEnhancerConfig.map(iQueryEnhancerOptionalPair -> new Pair<>( - iQueryEnhancerOptionalPair.getKey(), + return maybeQueryEnhancerAndConfig.map(queryEnhancerAndConfig -> new Pair<>( + queryEnhancerAndConfig.getKey(), CentreResourceUtils.createCentreContext( true, // full context, fully-fledged restoration. This means that IQueryEnhancer descendants (centre query enhancers) could use IContextDecomposer for context decomposition on deep levels. webUiConfig, @@ -972,7 +977,7 @@ private static Map updateResultantCustomObject( entityFactory, centreContextHolder, criteriaEntity, - iQueryEnhancerOptionalPair.getValue(), + queryEnhancerAndConfig.getValue(), null, /* chosenProperty is not applicable in queryEnhancer context */ device, sharingModel From da909f0424b63a89cf5d9c72581440783e8a6d47 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 28 Feb 2026 17:09:11 +0200 Subject: [PATCH 47/76] #2224 Revert temporary code. --- .../platform/share/ShareActionProducer.java | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java index 50dba10ef47..6e8ac63b69f 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/share/ShareActionProducer.java @@ -12,14 +12,12 @@ import ua.com.fielden.platform.tiny.TinyHyperlinkCo; import ua.com.fielden.platform.types.Hyperlink; import ua.com.fielden.platform.web.centre.CentreContext; -import ua.com.fielden.platform.web.utils.EntityCentreProcessor; import java.util.Base64; import java.util.Optional; import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isBlank; -import static ua.com.fielden.platform.entity.AbstractEntity.VERSION; import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.fetchIdOnly; import static ua.com.fielden.platform.tiny.TinyHyperlink.*; import static ua.com.fielden.platform.utils.QrCodeUtils.*; @@ -35,16 +33,13 @@ public class ShareActionProducer extends DefaultEntityProducerWithContext { private CentreContextHolder centreContextHolder; - private final EntityCentreProcessor entityCentreProcessor; @Inject ShareActionProducer( final EntityFactory factory, - final ICompanionObjectFinder companionFinder, - final EntityCentreProcessor entityCentreProcessor) + final ICompanionObjectFinder companionFinder) { super(factory, ShareAction.class, companionFinder); - this.entityCentreProcessor = entityCentreProcessor; } @Override @@ -54,22 +49,10 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { ofNullable((String) getContext().getCustomObject().get(CUSTOM_OBJECT_SHARED_URI)).ifPresentOrElse(sharedUri -> { // Create and save a tiny hyperlink that points to the respective entity master. final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.saveWithTarget(new Hyperlink(sharedUri), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); final var tinyUrlHyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(tinyUrlHyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(tinyUrlHyperlink.value, PNG, 512, 512, 24, WHITE, BLACK))); - - final var result = entityCentreProcessor.getResult(sharedUri.substring(sharedUri.lastIndexOf("/") + 1)); - System.out.println("----------- API Execution (...) ------------"); - if (result.isRight()) { - result.asRight().value().forEach(ent -> { - System.out.println(ent); - }); - } - else { - System.out.println(result.asLeft().value().getMessage()); - } - System.out.println("----------- API Execution (end) ------------"); }, () -> { // This action must have been invoked on a master for a new persistent entity or an action entity. if (masterEntityNotEmpty()) { @@ -86,7 +69,7 @@ protected ShareAction provideDefaultValues(final ShareAction entity) { final var savingInfoHolder = (SavingInfoHolder) centreContextHolder.getMasterEntity(); final TinyHyperlinkCo coTinyHyperlink = co(TinyHyperlink.class); - final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH).with(VERSION))).asRight().value(); + final var tinyHyperlink = coTinyHyperlink.save(masterEntity.getType(), savingInfoHolder, IActionIdentifier.of(actionIdentifierName), Optional.of(fetchIdOnly(TinyHyperlink.class).with(HASH))).asRight().value(); final var hyperlink = new Hyperlink(coTinyHyperlink.toURL(tinyHyperlink)); entity.setHyperlink(hyperlink) .setQrCode(Base64.getEncoder().encodeToString(qrCodeImage(hyperlink.value, PNG, 512, 512, 12, WHITE, BLACK))); From f6d653f9867f749c88f2694731117b8c6f62da0c Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 28 Feb 2026 17:24:50 +0200 Subject: [PATCH 48/76] #2224 Fix test after upstream merging. --- .../platform/web/utils/EntityCentreProcessorTest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index c18646cb4dc..ae7e3abde6a 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -360,13 +360,10 @@ private void initTestData(final String uuid, final DeviceProfile device, final R private void initTestData(Consumer enhanceCentreManager, ConfigSettings configSettings) { final IWebUiConfig webUiConfig = getInstance(IWebUiConfig.class); final ICompanionObjectFinder companionFinder = getInstance(ICompanionObjectFinder.class); - final EntityCentreConfigCo eccCompanion = companionFinder.find(EntityCentreConfig.class); - final MainMenuItemCo mmiCompanion = companionFinder.find(MainMenuItem.class); - final IUser userCompanion = companionFinder.find(User.class); - final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), FRESH_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), webUiConfig, eccCompanion, mmiCompanion, userCompanion, companionFinder); + final var centreManager = updateCentre(configSettings.owner(), configSettings.miType(), FRESH_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), webUiConfig, companionFinder); enhanceCentreManager.accept(centreManager); - commitCentreWithoutConflicts(configSettings.owner(), configSettings.miType(), FRESH_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), centreManager, null /* newDesc */, webUiConfig, eccCompanion, mmiCompanion, userCompanion); + commitCentreWithoutConflicts(configSettings.owner(), configSettings.miType(), FRESH_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), centreManager, null /* newDesc */, webUiConfig, companionFinder); } /// Creates Entity Centre persisted configuration for concrete surrogate version of it. From 367e114f0f1def0da1a3bab5dfbf3b2248d02677 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 10 Mar 2026 22:19:50 +0200 Subject: [PATCH 49/76] #2224 a. Improve error reporting / recovery for Entity Centre RUN / DISCARD buttons in case of developer errors. In case of standard iron-ajax promise errors, there is a separate error handling logic. However, in case of some client-side errors, the error wasn't get reported anywhere. Furthermore, in case of RUN button, 'Loading data...' toast would remain indefinitely visible, aka the state of application remains broken. This commit improves the situation by reporting client-side errors in a red toast, which is also propagated to server-side log through tg-global-error-handler. Also, indefinite informational toasts are getting replaced with error one, that would eventually vanish (clearing the broken state). --- .../fielden/platform/web/centre/tg-entity-centre-behavior.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-behavior.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-behavior.js index 938d78e8197..e3e859d9b91 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-behavior.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-behavior.js @@ -1035,6 +1035,7 @@ const TgEntityCentreBehaviorImpl = { slf._actionInProgress = false; }, function (error) { slf._actionInProgress = false; + throw error; } ); }.bind(self); @@ -1113,6 +1114,7 @@ const TgEntityCentreBehaviorImpl = { self._actionInProgress = false; }, function (error) { self._actionInProgress = false; + throw error; }); }).bind(self); From 8ada633dcd4c15fdbf49d1c24276cc68f0a35dcf Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 11 Mar 2026 20:08:26 +0200 Subject: [PATCH 50/76] #2224 b. Temporal workaround to allow 'EntityCentreConfig / MainMenuItem' sub-properties in selection criteria and result-set. --- .../ui/config/EntityCentreConfig.java | 2 +- .../platform/ui/config/MainMenuItem.java | 22 ++++++------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java index 343739ddc58..f931dd1bfe1 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java @@ -40,7 +40,7 @@ @CompanionObject(EntityCentreConfigCo.class) @MapEntityTo("ENTITY_CENTRE_CONFIG") @DescTitle("Description") -@DenyIntrospection +//@DenyIntrospection public class EntityCentreConfig extends AbstractConfiguration { @IsProperty diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java index 7e125e80592..f31fb5e326e 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java @@ -1,25 +1,17 @@ package ua.com.fielden.platform.ui.config; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - import ua.com.fielden.platform.algorithm.search.ITreeNode; import ua.com.fielden.platform.entity.AbstractEntity; -import ua.com.fielden.platform.entity.annotation.CompanionObject; -import ua.com.fielden.platform.entity.annotation.DenyIntrospection; -import ua.com.fielden.platform.entity.annotation.DescTitle; -import ua.com.fielden.platform.entity.annotation.IsProperty; -import ua.com.fielden.platform.entity.annotation.KeyTitle; -import ua.com.fielden.platform.entity.annotation.KeyType; -import ua.com.fielden.platform.entity.annotation.MapEntityTo; -import ua.com.fielden.platform.entity.annotation.MapTo; -import ua.com.fielden.platform.entity.annotation.Observable; +import ua.com.fielden.platform.entity.annotation.*; import ua.com.fielden.platform.error.Result; import ua.com.fielden.platform.reflection.ClassesRetriever; import ua.com.fielden.platform.reflection.PropertyTypeDeterminator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + /** * A type for persisting an individual main menu item. Existence of an instance of this type simply indicates the existence of a corresponding main menu item. *

@@ -37,7 +29,7 @@ @DescTitle("Description") @CompanionObject(MainMenuItemCo.class) @MapEntityTo("MAIN_MENU") -@DenyIntrospection +//@DenyIntrospection public class MainMenuItem extends AbstractEntity implements ITreeNode { @IsProperty From b8185c6269143d979d4e6fb292658e7f1c90e86a Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 11 Mar 2026 22:53:28 +0200 Subject: [PATCH 51/76] #2224 New API method for config validation. --- .../web/utils/EntityCentreProcessor.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 1b31b1d0f55..9ba6956e83b 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -32,7 +32,7 @@ public interface EntityCentreProcessor { /// Takes into account all unsaved changes in that configuration. /// /// Returns [Left] with invalid [Result] for the cases where - /// - UUID is not valid + /// - UUID is blank (e.g. for default configurations) /// - there is no configuration with that UUID (or there are multiple ones for some reason) /// - there are only "orphan" inherited-from-shared configurations with no original one (i.e. if it was deleted) /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001" @@ -65,7 +65,7 @@ > Either> getResult( /// Takes into account all unsaved changes in that configuration. /// /// Returns [Left] with invalid [Result] for the cases where - /// - UUID is not valid + /// - UUID is blank (e.g. for default configurations) /// - there is no configuration with that UUID (or there are multiple ones for some reason) /// - there are only "orphan" inherited-from-shared configurations with no original one (i.e. if it was deleted) /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001" @@ -90,4 +90,31 @@ Either resultExists( String configUuid ); + /// Finds out whether named Entity Centre configuration, defined by UUID, is valid for API execution. + /// + /// Returns [Left] with invalid [Result] for the cases where + /// - UUID is blank (e.g. for default configurations) + /// - there is no configuration with that UUID (or there are multiple ones for some reason) + /// - there are only "orphan" inherited-from-shared configurations with no original one (i.e. if it was deleted) + /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001" + /// - UUID represents configuration with validation errors (e.g. requiredness or others) + /// - UUID represents configuration with authorisation errors (either Can Read or Can Read Property for non-empty criterion) + /// - UUID represents configuration with generator errors. + /// + /// Returns [Right] with [ConfigSettings] in case of valid configuration. + /// + /// Example: + /// ``` + /// final ConfigSettings configSettings = entityCentreProcessor + /// .validate(configUuid) + /// .orElseThrow(Result::throwRuntime); + /// ``` + /// + /// **Important**: running of this method in context of `@SessionRequired` scope may roll back active transaction. + /// This means that API users must exercise caution if [Left] is returned (use [Either#orElseThrow(Function)]). + /// + Either validate( + String configUuid + ); + } From 01551ea405ddf294b7872acdfcc7b2e996444026 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 11 Mar 2026 22:59:04 +0200 Subject: [PATCH 52/76] #2224 Move ConfigSettings to pojo-bl to re-use in EntityCentreProcessor. --- .../ua/com/fielden/platform/web/utils}/ConfigSettings.java | 2 +- .../platform/web/resources/webui/CentreResourceUtils.java | 3 +-- .../fielden/platform/web/resources/webui/CriteriaResource.java | 1 + .../platform/web/utils/DefaultEntityCentreProcessor.java | 3 --- .../fielden/platform/web/utils/EntityCentreProcessorTest.java | 3 --- 5 files changed, 3 insertions(+), 9 deletions(-) rename {platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui => platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils}/ConfigSettings.java (94%) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/ConfigSettings.java similarity index 94% rename from platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java rename to platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/ConfigSettings.java index b63130e83d5..f71bcab4f43 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/ConfigSettings.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/ConfigSettings.java @@ -1,4 +1,4 @@ -package ua.com.fielden.platform.web.resources.webui; +package ua.com.fielden.platform.web.utils; import ua.com.fielden.platform.security.user.User; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index d3e1a00b367..015f3d5603c 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -30,7 +30,6 @@ import ua.com.fielden.platform.reflection.ClassesRetriever; import ua.com.fielden.platform.reflection.Finder; import ua.com.fielden.platform.reflection.PropertyTypeDeterminator; -import ua.com.fielden.platform.security.user.IUser; import ua.com.fielden.platform.security.user.User; import ua.com.fielden.platform.serialisation.jackson.DefaultValueContract; import ua.com.fielden.platform.types.either.Either; @@ -38,7 +37,6 @@ import ua.com.fielden.platform.types.either.Right; import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; -import ua.com.fielden.platform.ui.config.MainMenuItemCo; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; import ua.com.fielden.platform.utils.EntityUtils; import ua.com.fielden.platform.utils.Pair; @@ -48,6 +46,7 @@ import ua.com.fielden.platform.web.centre.api.actions.EntityActionConfig; import ua.com.fielden.platform.web.centre.api.context.CentreContextConfig; import ua.com.fielden.platform.web.interfaces.DeviceProfile; +import ua.com.fielden.platform.web.utils.ConfigSettings; import ua.com.fielden.platform.web.utils.EntityResourceUtils; import java.lang.reflect.Field; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index de90e152598..5847224b35c 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -45,6 +45,7 @@ import ua.com.fielden.platform.web.interfaces.DeviceProfile; import ua.com.fielden.platform.web.interfaces.IDeviceProvider; import ua.com.fielden.platform.web.resources.RestServerUtil; +import ua.com.fielden.platform.web.utils.ConfigSettings; import java.util.*; import java.util.concurrent.ConcurrentHashMap; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 670c9c81a4a..b5b18b938d9 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -18,13 +18,10 @@ import ua.com.fielden.platform.types.either.Left; import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; -import ua.com.fielden.platform.ui.config.MainMenuItem; -import ua.com.fielden.platform.ui.config.MainMenuItemCo; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; import ua.com.fielden.platform.web.interfaces.DeviceProfile; -import ua.com.fielden.platform.web.resources.webui.ConfigSettings; import java.util.ArrayList; import java.util.List; diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index ae7e3abde6a..f115f22bfec 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -15,13 +15,10 @@ import ua.com.fielden.platform.test_config.AbstractDaoTestCase; import ua.com.fielden.platform.test_config.H2OrPostgreSqlOrSqlServerContextSelectorForWebTests; import ua.com.fielden.platform.ui.config.EntityCentreConfig; -import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; import ua.com.fielden.platform.ui.config.MainMenuItem; -import ua.com.fielden.platform.ui.config.MainMenuItemCo; import ua.com.fielden.platform.ui.menu.sample.MiTgGeneratedEntity; import ua.com.fielden.platform.web.app.IWebUiConfig; import ua.com.fielden.platform.web.interfaces.DeviceProfile; -import ua.com.fielden.platform.web.resources.webui.ConfigSettings; import java.util.Map; import java.util.function.Consumer; From 98694575e127d988d43d96b48c8a079885697a21 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 11 Mar 2026 23:12:34 +0200 Subject: [PATCH 53/76] #2224 Default impl for new API method for config validation. --- .../web/utils/DefaultEntityCentreProcessor.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index b5b18b938d9..cee85f6e516 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -58,6 +58,7 @@ public class DefaultEntityCentreProcessor implements EntityCentreProcessor { public static final String ERR_CONFIG_MENU_ITEM_TYPE_CANT_BE_FOUND = ERR_EXECUTION_FAILED_PREFIX + "[%s] config's menu item type [%s] can not be found."; public static final String ERR_LINK_CONFIG_IS_NOT_AVAILABLE_FOR_RUNNING = ERR_EXECUTION_FAILED_PREFIX + "[%s] link config ([%s]) is not available for API running."; public static final String ERR_DEFAULT_CONFIG_WITH_UUID_SHOULD_NOT_EXIST = ERR_EXECUTION_FAILED_PREFIX + "[%s] configuration with blank name [%s] shouldn't exist (default configuration should never have UUID)."; + public static final String ERR_CONFIG_COULD_NOT_BE_EXECUTED = "Entity Centre configuration with [%s] UUID could not be executed."; private static final Function> TITLE_PREFIX_INCL_DEFAULT = surrogateName -> device -> PREFIX_OF.apply(surrogateName).apply(device).replace("[%", "%"); @@ -236,7 +237,7 @@ private > Either> entityCentreResult resultList.forEach(entity -> list.add((T) entity)); return right(list); } catch (final Exception exception) { - return left(failure(new EntityCentreExecutionException("Configuration with [%s] UUID could not be executed.".formatted(configUuid), exception))); + return left(failure(new EntityCentreExecutionException(ERR_CONFIG_COULD_NOT_BE_EXECUTED.formatted(configUuid), exception))); } finally { // Return original user back to user provider. userProvider.setUser(currentUser); @@ -249,4 +250,14 @@ public Either resultExists(final String configUuid) { .map(result -> !result.isEmpty()); } + @Override + public Either validate(String configUuid) { + try { + // Find out the settings for configuration. Stop if the settings can not be determined or inapplicable. + return determineConfigurationSettings(configUuid, companionFinder); + } catch (final Exception exception) { + return left(failure(new EntityCentreExecutionException(ERR_CONFIG_COULD_NOT_BE_EXECUTED.formatted(configUuid), exception))); + } + } + } From 3680a1a919813025484898e55ef9ab6fa193660a Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Thu, 12 Mar 2026 11:27:18 +0200 Subject: [PATCH 54/76] #2224 Expose standard Entity Centre messages. These messages where used in Entity Centre operations, but are now useful for end-application code for Entity Centres. * Also, expose EntityCentreProcessor validation messages for pattern matching from 'Result.getMessage'. This may need to be improved further (error codes or enum?). --- .../ua/com/fielden/platform/web/centre/WebApiUtils.java | 9 ++++++--- .../platform/web/utils/EntityCentreProcessor.java | 7 +++++++ .../web/resources/webui/CentreResourceUtils.java | 4 +--- .../platform/web/resources/webui/CriteriaResource.java | 6 ++---- .../platform/web/utils/DefaultEntityCentreProcessor.java | 7 ------- .../platform/web/utils/EntityCentreProcessorTest.java | 2 +- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/centre/WebApiUtils.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/centre/WebApiUtils.java index 4c5aad89926..a531e7b2d75 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/centre/WebApiUtils.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/centre/WebApiUtils.java @@ -1,11 +1,11 @@ package ua.com.fielden.platform.web.centre; -import static ua.com.fielden.platform.domaintree.ICalculatedProperty.CalculatedPropertyCategory.AGGREGATED_EXPRESSION; -import static ua.com.fielden.platform.domaintree.impl.AbstractDomainTreeRepresentation.isCalculatedAndOfTypes; - import java.util.List; import java.util.stream.Collectors; +import static ua.com.fielden.platform.domaintree.ICalculatedProperty.CalculatedPropertyCategory.AGGREGATED_EXPRESSION; +import static ua.com.fielden.platform.domaintree.impl.AbstractDomainTreeRepresentation.isCalculatedAndOfTypes; + /** * Utilities for Web API (centre, master) and its implementation. * @@ -22,6 +22,9 @@ public class WebApiUtils { * The surrogate title of centre 'link' configuration. This is used when link with centre parameters opens. */ public static final String LINK_CONFIG_TITLE = "_______________________link"; + public static final String CONFIG_DOES_NOT_EXIST = "Configuration does not exist."; + public static final String SAVE_MSG = "Please save and try again."; + public static final String DUPLICATE_SAVE_MSG = "Please duplicate, save and try again."; /** Private default constructor to prevent instantiation. */ private WebApiUtils() { diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 9ba6956e83b..07b65315f50 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -27,6 +27,13 @@ /// - cover operations for embedded Entity Centres with some master context (see [IQueryEnhancer]). /// public interface EntityCentreProcessor { + String ERR_EXECUTION_FAILED_PREFIX = "Entity Centre configuration execution failed. "; + String ERR_DEFAULT_CONFIG_WITH_UUID_SHOULD_NOT_EXIST = ERR_EXECUTION_FAILED_PREFIX + "[%s] configuration with blank name [%s] shouldn't exist (default configuration should never have UUID)."; + String ERR_LINK_CONFIG_IS_NOT_AVAILABLE_FOR_RUNNING = ERR_EXECUTION_FAILED_PREFIX + "[%s] link config ([%s]) is not available for API running."; + String ERR_CONFIG_MENU_ITEM_TYPE_CANT_BE_FOUND = ERR_EXECUTION_FAILED_PREFIX + "[%s] config's menu item type [%s] can not be found."; + String ERR_CONFIG_DOES_NOT_EXIST = ERR_EXECUTION_FAILED_PREFIX + "Config with [%s] UUID does not exist."; + String ERR_CONFIG_UUID_IS_BLANK = ERR_EXECUTION_FAILED_PREFIX + "Config UUID [%s] is blank."; + String ERR_CONFIG_COULD_NOT_BE_EXECUTED = "Entity Centre configuration with [%s] UUID could not be executed."; /// Executes named Entity Centre configuration, defined by UUID, similarly as the owner may have run it through Web UI. /// Takes into account all unsaved changes in that configuration. diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index 015f3d5603c..892a29f2845 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -80,6 +80,7 @@ import static ua.com.fielden.platform.web.centre.CentreConfigUtils.*; import static ua.com.fielden.platform.web.centre.CentreContext.INSTANCEBASEDCONTINUATION_PROPERTY_NAME; import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.*; +import static ua.com.fielden.platform.web.centre.WebApiUtils.*; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.RunActions.*; import static ua.com.fielden.platform.web.resources.webui.CriteriaIndication.CHANGED; import static ua.com.fielden.platform.web.resources.webui.CriteriaIndication.NONE; @@ -91,10 +92,7 @@ /// public class CentreResourceUtils> extends CentreUtils { private static final Logger logger = LogManager.getLogger(CentreResourceUtils.class); - public static final String CONFIG_DOES_NOT_EXIST = "Configuration does not exist."; - private static final String SAVE_MSG = "Please save and try again."; public static final String SAVE_OWN_COPY_MSG = "Only sharing of your own configurations is supported. Please save as your copy and try again."; - private static final String DUPLICATE_SAVE_MSG = "Please duplicate, save and try again."; /// The key for customObject's value containing save-as name. public static final String SAVE_AS_NAME = "saveAsName"; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 5847224b35c..b6b9033b6a7 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -33,10 +33,7 @@ import ua.com.fielden.platform.utils.IDates; import ua.com.fielden.platform.utils.Pair; import ua.com.fielden.platform.web.app.IWebUiConfig; -import ua.com.fielden.platform.web.centre.CentreContext; -import ua.com.fielden.platform.web.centre.EntityCentre; -import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; -import ua.com.fielden.platform.web.centre.IQueryEnhancer; +import ua.com.fielden.platform.web.centre.*; import ua.com.fielden.platform.web.centre.api.EntityCentreConfig.ResultSetProp; import ua.com.fielden.platform.web.centre.api.context.CentreContextConfig; import ua.com.fielden.platform.web.centre.api.resultset.ICustomPropsAssignmentHandler; @@ -80,6 +77,7 @@ import static ua.com.fielden.platform.web.centre.CentreUpdater.removeCentres; import static ua.com.fielden.platform.web.centre.CentreUpdaterUtils.*; import static ua.com.fielden.platform.web.centre.CentreUtils.isFreshCentreChanged; +import static ua.com.fielden.platform.web.centre.WebApiUtils.CONFIG_DOES_NOT_EXIST; import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; import static ua.com.fielden.platform.web.factories.webui.ResourceFactoryUtils.*; import static ua.com.fielden.platform.web.resources.webui.CentreResourceUtils.*; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index cee85f6e516..3013dfc491f 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -52,13 +52,6 @@ /// Default [EntityCentreProcessor] implementation, that uses [IWebUiConfig] Entity Centres registry and persistent storage. /// public class DefaultEntityCentreProcessor implements EntityCentreProcessor { - private static final String ERR_EXECUTION_FAILED_PREFIX = "Entity Centre configuration execution failed. "; - public static final String ERR_CONFIG_UUID_IS_BLANK = ERR_EXECUTION_FAILED_PREFIX + "Config UUID [%s] is blank."; - public static final String ERR_CONFIG_DOES_NOT_EXIST = ERR_EXECUTION_FAILED_PREFIX + "Config with [%s] UUID does not exist."; - public static final String ERR_CONFIG_MENU_ITEM_TYPE_CANT_BE_FOUND = ERR_EXECUTION_FAILED_PREFIX + "[%s] config's menu item type [%s] can not be found."; - public static final String ERR_LINK_CONFIG_IS_NOT_AVAILABLE_FOR_RUNNING = ERR_EXECUTION_FAILED_PREFIX + "[%s] link config ([%s]) is not available for API running."; - public static final String ERR_DEFAULT_CONFIG_WITH_UUID_SHOULD_NOT_EXIST = ERR_EXECUTION_FAILED_PREFIX + "[%s] configuration with blank name [%s] shouldn't exist (default configuration should never have UUID)."; - public static final String ERR_CONFIG_COULD_NOT_BE_EXECUTED = "Entity Centre configuration with [%s] UUID could not be executed."; private static final Function> TITLE_PREFIX_INCL_DEFAULT = surrogateName -> device -> PREFIX_OF.apply(surrogateName).apply(device).replace("[%", "%"); diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index f115f22bfec..144ef314924 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -34,7 +34,7 @@ import static ua.com.fielden.platform.web.centre.WebApiUtils.LINK_CONFIG_TITLE; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.DESKTOP; import static ua.com.fielden.platform.web.interfaces.DeviceProfile.MOBILE; -import static ua.com.fielden.platform.web.utils.DefaultEntityCentreProcessor.*; +import static ua.com.fielden.platform.web.utils.EntityCentreProcessor.*; /// Tests for {@link EntityCentreProcessor}. /// From 9a4e2fa4c6e7eefb5adb937fca6bc610a0d1718f Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 10 Apr 2026 20:58:48 +0300 Subject: [PATCH 55/76] #2224 Improve Web UI-capable tests to be able to use UniversalConstantsForTesting. --- .../DataPopulationConfigForWebTests.java | 1 + .../UniversalConstantsTestIocModule.java | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) rename platform-web-resources/src/{main => test}/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java (97%) create mode 100644 platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/UniversalConstantsTestIocModule.java diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java similarity index 97% rename from platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java rename to platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java index 23502c5c223..a1ba2be23fb 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java @@ -32,6 +32,7 @@ public DataPopulationConfigForWebTests(final Properties props) { injector = new ApplicationInjectorFactory() .add(new TgTestWebApplicationServerIocModule(appDomain, appDomain.domainTypes(), props)) .add(new NewUserEmailNotifierTestIocModule()) + .add(new UniversalConstantsTestIocModule()) .getInjector(); } catch (final Exception e) { throw new IllegalStateException("Could not create data population configuration.", e); diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/UniversalConstantsTestIocModule.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/UniversalConstantsTestIocModule.java new file mode 100644 index 00000000000..d9a98695d0f --- /dev/null +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/UniversalConstantsTestIocModule.java @@ -0,0 +1,20 @@ +package ua.com.fielden.platform.web.test.server; + +import com.google.inject.Binder; +import com.google.inject.Module; +import ua.com.fielden.platform.test.ioc.DatesForTesting; +import ua.com.fielden.platform.test.ioc.UniversalConstantsForTesting; +import ua.com.fielden.platform.utils.IDates; +import ua.com.fielden.platform.utils.IUniversalConstants; + +/// IoC module for [IUniversalConstants] for unit testing purposes. +/// +public class UniversalConstantsTestIocModule implements Module { + + @Override + public void configure(Binder binder) { + binder.bind(IDates.class).to(DatesForTesting.class); + binder.bind(IUniversalConstants.class).to(UniversalConstantsForTesting.class); + } + +} From 3652039cf289ad7ed3fd83b771de0b788302dec5 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 10 Apr 2026 21:01:53 +0300 Subject: [PATCH 56/76] #2224 Simplify EntityCentreProcessorTest to avoid universal constants workaround. --- .../web/utils/EntityCentreProcessorTest.java | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 144ef314924..d2652aaa2c5 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -10,8 +10,9 @@ import ua.com.fielden.platform.sample.domain.TgGeneratedEntity; import ua.com.fielden.platform.sample.domain.compound.TgCompoundEntity; import ua.com.fielden.platform.security.tokens.persistent.TgGeneratedEntity_CanRead_Token; -import ua.com.fielden.platform.security.user.*; -import ua.com.fielden.platform.security.user.User.system_users; +import ua.com.fielden.platform.security.user.SecurityRoleAssociation; +import ua.com.fielden.platform.security.user.SecurityRoleAssociationCo; +import ua.com.fielden.platform.security.user.UserRole; import ua.com.fielden.platform.test_config.AbstractDaoTestCase; import ua.com.fielden.platform.test_config.H2OrPostgreSqlOrSqlServerContextSelectorForWebTests; import ua.com.fielden.platform.ui.config.EntityCentreConfig; @@ -184,8 +185,6 @@ public void executing_resultExists_method_returns_invalid_result_for_non_existin public void executing_resultExists_method_returns_invalid_result_for_orphan_inherited_from_shared_named_configuration() { final var uuid = randomUUID().toString(); - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgCompoundEntity.class); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, null); @@ -203,8 +202,6 @@ public void executing_resultExists_method_returns_invalid_result_for_orphan_inhe public void executing_resultExists_method_returns_invalid_result_for_unusual_named_configuration_with_no_miType() { final var uuid = randomUUID().toString(); - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -222,8 +219,6 @@ public void executing_resultExists_method_returns_invalid_result_for_unusual_nam public void executing_resultExists_method_returns_invalid_result_for_unusual_default_configuration_with_uuid() { final var uuid = randomUUID().toString(); - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - final var configSettings = new ConfigSettings(empty(), getUser(), DESKTOP, MiTgCompoundEntity.class); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -241,8 +236,6 @@ public void executing_resultExists_method_returns_invalid_result_for_unusual_def public void executing_resultExists_method_returns_invalid_result_for_link_configuration() { final var uuid = randomUUID().toString(); - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - final var configSettings = new ConfigSettings(of(LINK_CONFIG_TITLE), getUser(), DESKTOP, MiTgCompoundEntity.class); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -260,8 +253,6 @@ public void executing_resultExists_method_returns_invalid_result_for_link_config public void executing_resultExists_method_returns_invalid_result_for_invalid_configuration() { final var uuid = randomUUID().toString(); - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -281,8 +272,6 @@ public void executing_resultExists_method_returns_invalid_result_for_invalid_con public void executing_resultExists_method_returns_invalid_result_for_unauthorised_configuration() { final var uuid = randomUUID().toString(); - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -290,7 +279,7 @@ public void executing_resultExists_method_returns_invalid_result_for_unauthorise final SecurityRoleAssociationCo co$ = co$(SecurityRoleAssociation.class); co$.removeAssociations(setOf( co$.new_() - .setRole(co(UserRole.class).findByKey(ADMIN)) + .setRole(co(UserRole.class).findByKey(UNIT_TEST_ROLE)) .setSecurityToken(TgGeneratedEntity_CanRead_Token.class) )); @@ -307,8 +296,6 @@ public void executing_resultExists_method_returns_invalid_result_for_unauthorise public void executing_resultExists_method_returns_invalid_result_for_configuration_with_invalid_generation() { final var uuid = randomUUID().toString(); - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -339,8 +326,6 @@ private void initTestData(final String uuid, final Runnable createData, final Co /// @param enhanceCentreManager mutating function for centre manager to provide custom criteria and other configuration parameters /// private void initTestData(final String uuid, final DeviceProfile device, final Runnable createData, final Consumer enhanceCentreManager) { - setupUser(system_users.UNIT_TEST_USER, "example.tg.test"); - createData.run(); final var configSettings = new ConfigSettings(of("saveAs"), getUser(), device, MiTgCompoundEntity.class); @@ -408,9 +393,4 @@ private MainMenuItem getMenuItemForName(final String menuItemTypeName) { .orElseGet(() -> (MainMenuItem) save(new_(MainMenuItem.class).setKey(menuItemTypeName))); } - @Override - protected void populateDomain() { - // Override to use standard IUniversalConstants implementation instead of UniversalConstantsForTesting. - } - } \ No newline at end of file From 46e0f178d3d2e3b7d1f0473413c2675a8a3099d8 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Fri, 10 Apr 2026 23:20:06 +0300 Subject: [PATCH 57/76] #2224 Reuse DataPopulationConfig common logic. --- .../web/test/server/DataPopulationConfig.java | 29 +++++------ .../DataPopulationConfigForWebTests.java | 50 ++++++++----------- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java index 39d0a07586f..278262e72d8 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java @@ -15,13 +15,9 @@ import static ua.com.fielden.platform.audit.AuditingIocModule.AUDIT_MODE; import static ua.com.fielden.platform.utils.MiscUtilities.propertiesUnionLeft; -/** - * Provides Web UI Testing Server specific implementation of {@link IDomainDrivenTestCaseConfiguration} - * to be used for creation and population of the target development database. - * - * @author TG Team - */ -public final class DataPopulationConfig implements IDomainDrivenTestCaseConfiguration { +/// Provides Web UI Testing Server specific implementation of [IDomainDrivenTestCaseConfiguration] to be used for creation and population of the target development database. +/// +public class DataPopulationConfig implements IDomainDrivenTestCaseConfiguration { private final Injector injector; @@ -43,20 +39,21 @@ public DataPopulationConfig(final Properties props) { defaultProps.setProperty("email.smtp", "localhost"); defaultProps.setProperty("email.fromAddress", "tg@localhost"); - final var finalProps = propertiesUnionLeft(props, defaultProps); - - final ApplicationDomain appDomain = new ApplicationDomain(); - injector = new ApplicationInjectorFactory() - .add(new TgTestApplicationServerIocModule(appDomain, appDomain.domainTypes(), finalProps)) - .add(new NewUserEmailNotifierTestIocModule()) - .add(new DataFilterTestIocModule()) - .add(new IocModule()) - .getInjector(); + injector = createFactory(propertiesUnionLeft(props, defaultProps)).getInjector(); } catch (final Exception e) { throw new IllegalStateException("Could not create data population configuration.", e); } } + protected ApplicationInjectorFactory createFactory(final Properties properties) { + final ApplicationDomain appDomain = new ApplicationDomain(); + return new ApplicationInjectorFactory() + .add(new TgTestApplicationServerIocModule(appDomain, appDomain.domainTypes(), properties)) + .add(new NewUserEmailNotifierTestIocModule()) + .add(new DataFilterTestIocModule()) + .add(new IocModule()); + } + @Override public T getInstance(final Class type) { return injector.getInstance(type); diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java index a1ba2be23fb..b13b935f60e 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java @@ -1,6 +1,6 @@ package ua.com.fielden.platform.web.test.server; -import com.google.inject.Injector; +import ua.com.fielden.platform.audit.AuditingMode; import ua.com.fielden.platform.ioc.ApplicationInjectorFactory; import ua.com.fielden.platform.ioc.NewUserEmailNotifierTestIocModule; import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; @@ -8,40 +8,34 @@ import java.util.Properties; +import static ua.com.fielden.platform.audit.AuditingIocModule.AUDIT_MODE; + /// Provides Web UI Testing Server specific implementation of [IDomainDrivenTestCaseConfiguration] to be used for web unit tests. /// -public final class DataPopulationConfigForWebTests implements IDomainDrivenTestCaseConfiguration { - private final Injector injector; +public final class DataPopulationConfigForWebTests extends DataPopulationConfig { + /// Creates a configuration using the provided properties. + /// Some default properties are set by this constructor, but the provided ones take precedence. public DataPopulationConfigForWebTests(final Properties props) { - // instantiate all the factories and Hibernate utility - try { - // application properties - props.setProperty("app.name", "TG Test App"); - props.setProperty("reports.path", ""); - props.setProperty("domain.path", "../platform-pojo-bl/target/classes"); - props.setProperty("domain.package", "ua.com.fielden.platform.sample.domain"); - props.setProperty("tokens.path", "../platform-pojo-bl/target/classes"); - props.setProperty("tokens.package", "ua.com.fielden.platform.security.tokens"); - props.setProperty("workflow", "development"); - props.setProperty("email.smtp", "localhost"); - props.setProperty("email.fromAddress", "tg@localhost"); - props.setProperty("audit.path", "../platform-pojo-bl/target/classes"); - - final ApplicationDomain appDomain = new ApplicationDomain(); - injector = new ApplicationInjectorFactory() - .add(new TgTestWebApplicationServerIocModule(appDomain, appDomain.domainTypes(), props)) - .add(new NewUserEmailNotifierTestIocModule()) - .add(new UniversalConstantsTestIocModule()) - .getInjector(); - } catch (final Exception e) { - throw new IllegalStateException("Could not create data population configuration.", e); - } + super(enableAuditing(props)); + } + + /// Enables auditing, which is required by Web UI in `TgTestWebApplicationServerIocModule`. + /// + private static Properties enableAuditing(final Properties props) { + props.setProperty(AUDIT_MODE, AuditingMode.ENABLED.name()); + props.setProperty("audit.path", "../platform-pojo-bl/target/classes"); + return props; } @Override - public T getInstance(final Class type) { - return injector.getInstance(type); + protected ApplicationInjectorFactory createFactory(final Properties properties) { + final ApplicationDomain appDomain = new ApplicationDomain(); + return new ApplicationInjectorFactory() + .add(new TgTestWebApplicationServerIocModule(appDomain, appDomain.domainTypes(), properties)) + .add(new NewUserEmailNotifierTestIocModule()) + .add(new DataFilterTestIocModule()) + .add(new UniversalConstantsTestIocModule()); } } From 8e7e168fe145d4063b50bfa03b6b94d710c1e3e6 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 11 Apr 2026 00:02:59 +0300 Subject: [PATCH 58/76] #2224 Very minor doc improvement. --- .../web/test/server/DataPopulationConfigForWebTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java index b13b935f60e..f8fd5f8298f 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java @@ -16,6 +16,7 @@ public final class DataPopulationConfigForWebTests extends DataPopulationConfig /// Creates a configuration using the provided properties. /// Some default properties are set by this constructor, but the provided ones take precedence. + /// public DataPopulationConfigForWebTests(final Properties props) { super(enableAuditing(props)); } From 13cad571291b273a88e14b04dc4b2915ed197323 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 15 Apr 2026 23:14:37 +0300 Subject: [PATCH 59/76] #2224 Enhance getResult with IPage result instead of just List of entities. --- .../web/utils/EntityCentreProcessor.java | 8 +- .../resources/webui/CentreResourceUtils.java | 6 +- .../web/resources/webui/CriteriaResource.java | 136 +++++++++++++++--- .../utils/DefaultEntityCentreProcessor.java | 15 +- .../web/utils/EntityCentreProcessorTest.java | 14 +- 5 files changed, 140 insertions(+), 39 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 07b65315f50..67879e8dd84 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -3,13 +3,13 @@ import ua.com.fielden.platform.data.generator.IGenerator; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.error.Result; +import ua.com.fielden.platform.pagination.IPage; import ua.com.fielden.platform.types.either.Either; import ua.com.fielden.platform.types.either.Left; import ua.com.fielden.platform.types.either.Right; import ua.com.fielden.platform.web.centre.IQueryEnhancer; import ua.com.fielden.platform.web.interfaces.DeviceProfile; -import java.util.List; import java.util.function.Function; /// An interface for Entity Centre processing API. @@ -47,14 +47,14 @@ public interface EntityCentreProcessor { /// - UUID represents configuration with authorisation errors (either Can Read or Can Read Property for non-empty criterion) /// - UUID represents configuration with generator errors. /// - /// Returns [Right] with [List] of entities corresponding to configuration criteria, ordering and page capacity. + /// Returns [Right] with [IPage] of entities corresponding to configuration criteria, ordering and page capacity. /// Entity Centre with [IGenerator] performs generation of new data during execution through this API. /// /// Entity Centres with dynamic properties are supported (see `IResultSetBuilderDynamicProps#addProps` API). /// /// Example: /// ``` - /// final List workOrders = entityCentreProcessor + /// final IPage workOrders = entityCentreProcessor /// .getResult(configUuid) /// .orElseThrow(Result::throwRuntime); /// ``` @@ -64,7 +64,7 @@ public interface EntityCentreProcessor { /// /// @param corresponds to the root entity type of the executed Entity Centre configuration. /// - > Either> getResult( + > Either> getResult( String configUuid ); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java index 892a29f2845..45809bb0d98 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CentreResourceUtils.java @@ -35,6 +35,7 @@ import ua.com.fielden.platform.types.either.Either; import ua.com.fielden.platform.types.either.Left; import ua.com.fielden.platform.types.either.Right; +import ua.com.fielden.platform.types.tuples.T3; import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; @@ -74,6 +75,7 @@ import static ua.com.fielden.platform.types.either.Either.left; import static ua.com.fielden.platform.types.either.Either.right; import static ua.com.fielden.platform.types.tuples.T2.t2; +import static ua.com.fielden.platform.types.tuples.T3.t3; import static ua.com.fielden.platform.utils.EntityUtils.areEqual; import static ua.com.fielden.platform.utils.EntityUtils.equalsEx; import static ua.com.fielden.platform.web.centre.AbstractCentreConfigAction.APPLIED_CRITERIA_ENTITY_NAME; @@ -246,7 +248,7 @@ public static boolean isAutoRunning(final Map customObject) { /// /// @param updatedPreviouslyRunCriteriaEntity criteria entity created from PREVIOUSLY_RUN surrogate centre, which was potentially updated from FRESH (in case of "running" action), but not yet actually used for running /// - static , M extends EnhancedCentreEntityQueryCriteria>> Pair, List>> createCriteriaMetaValuesCustomObjectWithResult( + static , M extends EnhancedCentreEntityQueryCriteria>> T3, List>, IPage> createCriteriaMetaValuesCustomObjectWithResult( final Map customObject, final M updatedPreviouslyRunCriteriaEntity) { final Map resultantCustomObject = new LinkedHashMap<>(); @@ -304,7 +306,7 @@ public static boolean isAutoRunning(final Map customObject) { final int pageCount = page != null ? page.numberOfPages() : pageCount(realPageCount(data.size(), secondTick.getPageCapacity())); resultantCustomObject.put("pageCount", pageCount); resultantCustomObject.put("pageNumber", page != null ? page.no() : pageCount <= pageNumber ? pageCount - 1 : pageNumber); - return new Pair<>(resultantCustomObject, (List>) data); + return t3(resultantCustomObject, (List>) data, page); } /// Runs the entity centre with option `retrieveAll`. Returns [Right] with a list of entities of all matching entities and a summary, if `retrieveAll == true`. diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index b6b9033b6a7..8df144bddaa 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -21,6 +21,7 @@ import ua.com.fielden.platform.entity.meta.MetaProperty; import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; import ua.com.fielden.platform.error.Result; +import ua.com.fielden.platform.pagination.IPage; import ua.com.fielden.platform.security.IAuthorisationModel; import ua.com.fielden.platform.security.provider.ISecurityTokenProvider; import ua.com.fielden.platform.security.user.IUserProvider; @@ -33,7 +34,10 @@ import ua.com.fielden.platform.utils.IDates; import ua.com.fielden.platform.utils.Pair; import ua.com.fielden.platform.web.app.IWebUiConfig; -import ua.com.fielden.platform.web.centre.*; +import ua.com.fielden.platform.web.centre.CentreContext; +import ua.com.fielden.platform.web.centre.EntityCentre; +import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; +import ua.com.fielden.platform.web.centre.IQueryEnhancer; import ua.com.fielden.platform.web.centre.api.EntityCentreConfig.ResultSetProp; import ua.com.fielden.platform.web.centre.api.context.CentreContextConfig; import ua.com.fielden.platform.web.centre.api.resultset.ICustomPropsAssignmentHandler; @@ -683,7 +687,7 @@ public Representation put(final Representation envelope) { } // Execute actual Entity Centre configuration run / refresh / navigate / sort logic. - final var resultList = executeEntityCentreConfiguration( + final var resultListAndPage = executeEntityCentreConfiguration( new ConfigSettings(saveAsName, user, device(), miType), of(t2(updatedFreshCentre, previouslyRunCentre)), isRunning, @@ -699,7 +703,7 @@ public Representation put(final Representation envelope) { ); LOGGER.debug("CRITERIA_RESOURCE: run finished."); - return restUtil.rawListJsonRepresentation(resultList.toArray()); + return restUtil.rawListJsonRepresentation(resultListAndPage._1.toArray()); } finally { if (lockAcquired) { // it is necessary to unlock Lock in finally block (exceptions will be handled properly then) @@ -714,7 +718,7 @@ public Representation put(final Representation envelope) { /// @param updatedFreshCentreAndPreviouslyRunCentre an [Optional] pair of centre managers for more comprehensive and complete running; /// if passed, rendering hints / UI action indices / criteria indicator will be computed /// - public static List executeEntityCentreConfiguration( + public static T2, IPage>> executeEntityCentreConfiguration( final ConfigSettings configSettings, final Optional> updatedFreshCentreAndPreviouslyRunCentre, final boolean isRunning, @@ -743,8 +747,8 @@ public static List executeEntityCentreConfiguration( sharingModel ) ); - final var resultCustomObject = resultCustomObjectAndEntities.getKey(); - final var resultEntities = resultCustomObjectAndEntities.getValue(); + final var resultCustomObject = resultCustomObjectAndEntities._1; + final var resultEntities = resultCustomObjectAndEntities._2; final var skipCustomObjectCalculations = updatedFreshCentreAndPreviouslyRunCentre.isEmpty(); if (!skipCustomObjectCalculations) { @@ -783,17 +787,14 @@ public static List executeEntityCentreConfiguration( resultCustomObject.put("dynamicColumns", createDynamicProperties(resPropsWithContext, centre)); } - Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( - centre, - centre.getCustomPropertiesDefinitions(), - centre.getCustomPropertiesAsignmentHandler(), - resultEntities.stream()); - - // Enhance entities with values defined with consumer in each dynamic property. - processedEntities = enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); + final Stream> enhancedEntities = enhanceEntities(resultEntities.stream(), resPropsWithContext, centre); + final Stream> processedEntities; if (!skipCustomObjectCalculations) { // Enhance rendering hints with styles for each dynamic column. - processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(processedEntities, resPropsWithContext, (List) resultCustomObject.get("renderingHints")); + processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(enhancedEntities, resPropsWithContext, (List) resultCustomObject.get("renderingHints")); + } + else { + processedEntities = enhancedEntities; } final var list = new ArrayList<>(); @@ -802,11 +803,112 @@ public static List executeEntityCentreConfiguration( list.add(resultCustomObject); } + final List> data = new ArrayList<>(); // TODO It looks like adding values directly to the list outside the map object leads to proper type/serialiser correspondence // FIXME Need to investigate why this is the case. - processedEntities.forEach(list::add); + processedEntities.forEach(entity -> { + list.add(entity); + data.add(entity); + }); + return t2(list, new Page(resultCustomObjectAndEntities._3, data, resPropsWithContext, centre)); + } + + private static Stream> enhanceEntities( + final Stream> entityStream, + final List>, Optional, ?>>>> resPropsWithContext, + final EntityCentre> centre + ) { + final Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( + centre, + centre.getCustomPropertiesDefinitions(), + centre.getCustomPropertiesAsignmentHandler(), + entityStream); + + // Enhance entities with values defined with consumer in each dynamic property. + return enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); + } + + private static class Page implements IPage> { + private final IPage> backingPage; + private final List> data; + private final List>, Optional, ?>>>> resPropsWithContext; + private final EntityCentre> centre; + + public Page( + final IPage> backingPage, + final List> data, + final List>, Optional, ?>>>> resPropsWithContext, + final EntityCentre> centre + ) { + this.backingPage = backingPage; + this.data = data; + this.resPropsWithContext = resPropsWithContext; + this.centre = centre; + } + + @Override + public List> data() { + return data; + } + + @Override + public int capacity() { + return backingPage.capacity(); + } + + @Override + public int no() { + return backingPage.no(); + } - return list; + @Override + public int numberOfPages() { + return backingPage.numberOfPages(); + } + + @Override + public boolean hasNext() { + return backingPage.hasNext(); + } + + @Override + public boolean hasPrev() { + return backingPage.hasPrev(); + } + + private Page createPage(final IPage> page) { + return new Page( + page, + enhanceEntities(page.data().stream(), resPropsWithContext, centre).toList(), + resPropsWithContext, + centre + ); + } + + @Override + public IPage> next() { + return createPage(backingPage.next()); + } + + @Override + public IPage> prev() { + return createPage(backingPage.prev()); + } + + @Override + public IPage> last() { + return createPage(backingPage.last()); + } + + @Override + public IPage> first() { + return createPage(backingPage.first()); + } + + @Override + public AbstractEntity summary() { + return backingPage.summary(); + } } /// A method to try acquiring a lock for running a centre. diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 3013dfc491f..9dc9153a83c 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -9,6 +9,7 @@ import ua.com.fielden.platform.entity_centre.exceptions.EntityCentreExecutionException; import ua.com.fielden.platform.entity_centre.review.criteria.EnhancedCentreEntityQueryCriteria; import ua.com.fielden.platform.error.Result; +import ua.com.fielden.platform.pagination.IPage; import ua.com.fielden.platform.security.IAuthorisationModel; import ua.com.fielden.platform.security.provider.ISecurityTokenProvider; import ua.com.fielden.platform.security.user.IUser; @@ -23,8 +24,6 @@ import ua.com.fielden.platform.web.centre.ICentreConfigSharingModel; import ua.com.fielden.platform.web.interfaces.DeviceProfile; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -161,7 +160,7 @@ private static Either determineConfigurationSettings(fin } @Override - public > Either> getResult(final String configUuid) { + public > Either> getResult(final String configUuid) { return entityCentreResult(configUuid, empty()); } @@ -169,7 +168,7 @@ public > Either> getResult(final Str /// /// @param maybeCustomPageCapacity optional page capacity, which will override the page capacity of the configuration /// - private > Either> entityCentreResult( + private > Either> entityCentreResult( final String configUuid, final Optional maybeCustomPageCapacity ) { @@ -212,7 +211,7 @@ private > Either> entityCentreResult ); // Perform actual running of `freshCriteriaEntity` with `configSettings`. - final var resultList = executeEntityCentreConfiguration( + final var resultListAndPage = executeEntityCentreConfiguration( configSettings, empty(), true, @@ -226,9 +225,7 @@ private > Either> entityCentreResult sharingModel ); - final List list = new ArrayList<>(); - resultList.forEach(entity -> list.add((T) entity)); - return right(list); + return right((IPage) resultListAndPage._2); } catch (final Exception exception) { return left(failure(new EntityCentreExecutionException(ERR_CONFIG_COULD_NOT_BE_EXECUTED.formatted(configUuid), exception))); } finally { @@ -240,7 +237,7 @@ private > Either> entityCentreResult @Override public Either resultExists(final String configUuid) { return entityCentreResult(configUuid, of(1)) - .map(result -> !result.isEmpty()); + .map(result -> !result.data().isEmpty()); } @Override diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index d2652aaa2c5..78f8f48edeb 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -56,7 +56,7 @@ public void executing_getResult_method_returns_zero_entities_for_named_configura assertNotNull(result); assertTrue(result.isRight()); assertNotNull(result.asRight().value()); - assertEquals(0, result.asRight().value().size()); + assertEquals(0, result.asRight().value().data().size()); } @Test @@ -81,8 +81,8 @@ public void executing_getResult_method_returns_entities_for_named_configuration( assertNotNull(result); assertTrue(result.isRight()); assertNotNull(result.asRight().value()); - assertEquals(1, result.asRight().value().size()); - assertEquals("KEY1", result.asRight().value().getFirst().getKey()); + assertEquals(1, result.asRight().value().data().size()); + assertEquals("KEY1", result.asRight().value().data().getFirst().getKey()); } @Test @@ -110,8 +110,8 @@ public void executing_getResult_method_returns_entities_for_named_configuration_ assertNotNull(result); assertTrue(result.isRight()); assertNotNull(result.asRight().value()); - assertEquals(1, result.asRight().value().size()); - assertEquals("KEY2", result.asRight().value().getFirst().getKey()); + assertEquals(1, result.asRight().value().data().size()); + assertEquals("KEY2", result.asRight().value().data().getFirst().getKey()); } @Test @@ -139,8 +139,8 @@ public void executing_getResult_method_returns_entities_for_named_configuration_ assertNotNull(result); assertTrue(result.isRight()); assertNotNull(result.asRight().value()); - assertEquals(1, result.asRight().value().size()); - assertEquals("KEY1", result.asRight().value().getFirst().getKey()); + assertEquals(1, result.asRight().value().data().size()); + assertEquals("KEY1", result.asRight().value().data().getFirst().getKey()); } @Test From bc21bfdedb7719d1293b92603f1ba09fa403d20e Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 15 Apr 2026 23:41:01 +0300 Subject: [PATCH 60/76] #2224 Cover getResult result page calculations with basic tests. --- .../web/utils/EntityCentreProcessorTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 78f8f48edeb..4b85b874a1e 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -311,6 +311,43 @@ public void executing_resultExists_method_returns_invalid_result_for_configurati assertEquals("Can not generate the instance based on current user [%s], choose another user for that.".formatted(getUser()), result.asLeft().value().getMessage()); } + @Test + public void executing_getResult_method_returns_page_supporting_next_navigation() { + final var uuid = initPaginatedData(8, 3); + final var firstPage = getInstance(EntityCentreProcessor.class).getResult(uuid).asRight().value(); + assertEquals(3, firstPage.data().size()); + final var secondPage = firstPage.next(); + assertEquals(3, secondPage.data().size()); + final var thirdPage = secondPage.next(); + assertEquals(2, thirdPage.data().size()); + } + + @Test + public void executing_getResult_method_returns_page_supporting_prev_navigation() { + final var uuid = initPaginatedData(8, 3); + final var firstPage = getInstance(EntityCentreProcessor.class).getResult(uuid).asRight().value(); + final var secondPage = firstPage.next(); + final var backToFirst = secondPage.prev(); + assertEquals(3, backToFirst.data().size()); + } + + @Test + public void executing_getResult_method_returns_page_supporting_last_navigation() { + final var uuid = initPaginatedData(8, 3); + final var firstPage = getInstance(EntityCentreProcessor.class).getResult(uuid).asRight().value(); + final var lastPage = firstPage.last(); + assertEquals(2, lastPage.data().size()); + } + + @Test + public void executing_getResult_method_returns_page_supporting_first_navigation() { + final var uuid = initPaginatedData(8, 3); + final var firstPage = getInstance(EntityCentreProcessor.class).getResult(uuid).asRight().value(); + final var lastPage = firstPage.last(); + final var backToFirst = lastPage.first(); + assertEquals(3, backToFirst.data().size()); + } + /// Initialise test data for config `uuid` (desktop device profile). /// /// @param createData runnable for custom data creation @@ -320,6 +357,19 @@ private void initTestData(final String uuid, final Runnable createData, final Co initTestData(uuid, DESKTOP, createData, enhanceCentreManager); } + /// Initialise paginated test data: creates `entityCount` entities with the given `pageCapacity`. + /// Returns the config UUID. + /// + private String initPaginatedData(final int entityCount, final int pageCapacity) { + final var uuid = randomUUID().toString(); + initTestData(uuid, () -> { + for (int i = 1; i <= entityCount; i++) { + save(new_(TgCompoundEntity.class, String.format("KEY%02d", i)).setActive(true).setDesc("desc " + i)); + } + }, centreManager -> centreManager.getSecondTick().setPageCapacity(pageCapacity)); + return uuid; + } + /// Initialise test data for config `uuid` and `device` profile. /// /// @param createData runnable for custom data creation From 763fbd4cc376d916b5a8a83e982b02c4855d12bf Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Thu, 16 Apr 2026 01:02:40 +0300 Subject: [PATCH 61/76] #2224 First steps to 'resultCount' API and implementation. --- .../review/criteria/EntityQueryCriteria.java | 6 +++ .../web/utils/EntityCentreProcessor.java | 4 ++ .../utils/DefaultEntityCentreProcessor.java | 54 ++++++++++++------- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteria.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteria.java index 0a116537d03..162135cbadf 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteria.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity_centre/review/criteria/EntityQueryCriteria.java @@ -325,6 +325,12 @@ private Map enhanceQueryParams(final Map buildPa return buildParametersMap; } + /// Executes the configured Entity Centre query to find count of entities. + /// + public int runCount() { + return count(generateQueryWithSummaries().getKey()); + } + /// Executes the configured entity query. /// public final IPage run(final int pageSize) { diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 67879e8dd84..5fce1bb2fa0 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -97,6 +97,10 @@ Either resultExists( String configUuid ); + Either resultCount( + String configUuid + ); + /// Finds out whether named Entity Centre configuration, defined by UUID, is valid for API execution. /// /// Returns [Left] with invalid [Result] for the cases where diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 9dc9153a83c..607499f9ef6 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -17,6 +17,7 @@ import ua.com.fielden.platform.security.user.User; import ua.com.fielden.platform.types.either.Either; import ua.com.fielden.platform.types.either.Left; +import ua.com.fielden.platform.types.tuples.T3; import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.config.EntityCentreConfigCo; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; @@ -38,6 +39,7 @@ import static ua.com.fielden.platform.types.either.Either.left; import static ua.com.fielden.platform.types.either.Either.right; import static ua.com.fielden.platform.types.tuples.T2.t2; +import static ua.com.fielden.platform.types.tuples.T3.t3; import static ua.com.fielden.platform.utils.CollectionUtil.mapOf; import static ua.com.fielden.platform.utils.EntityUtils.fetchWithKeyAndDesc; import static ua.com.fielden.platform.web.centre.CentreUpdater.PREFIX_OF; @@ -161,14 +163,15 @@ private static Either determineConfigurationSettings(fin @Override public > Either> getResult(final String configUuid) { - return entityCentreResult(configUuid, empty()); + return entityCentreResult(configUuid, empty()) + .map(t3 -> executeWithEntities(t3._1, t3._2, t3._3)); // TODO exception handling } /// Executes Entity Centre configuration with `configUuid`. /// /// @param maybeCustomPageCapacity optional page capacity, which will override the page capacity of the configuration /// - private > Either> entityCentreResult( + private Either, EnhancedCentreEntityQueryCriteria, ?>>> entityCentreResult( final String configUuid, final Optional maybeCustomPageCapacity ) { @@ -210,22 +213,7 @@ private > Either> entityCentreResul customPageCapacity -> freshCriteriaEntity.getCentreDomainTreeMangerAndEnhancer().getSecondTick().setPageCapacity(customPageCapacity) ); - // Perform actual running of `freshCriteriaEntity` with `configSettings`. - final var resultListAndPage = executeEntityCentreConfiguration( - configSettings, - empty(), - true, - customObject, - freshCriteriaEntity, - webUiConfig, - companionFinder, - critGenerator, - entityFactory, - null, - sharingModel - ); - - return right((IPage) resultListAndPage._2); + return right(t3(configSettings, customObject, freshCriteriaEntity)); } catch (final Exception exception) { return left(failure(new EntityCentreExecutionException(ERR_CONFIG_COULD_NOT_BE_EXECUTED.formatted(configUuid), exception))); } finally { @@ -234,10 +222,38 @@ private > Either> entityCentreResul } } + private > IPage executeWithEntities( + final ConfigSettings configSettings, + final Map customObject, + final EnhancedCentreEntityQueryCriteria, ?> freshCriteriaEntity + ) { + // Perform actual running of `freshCriteriaEntity` with `configSettings`. + final var resultListAndPage = executeEntityCentreConfiguration( + configSettings, + empty(), + true, + customObject, + freshCriteriaEntity, + webUiConfig, + companionFinder, + critGenerator, + entityFactory, + null, + sharingModel + ); + return (IPage) resultListAndPage._2; + } + + @Override + public Either resultCount(final String configUuid) { + return entityCentreResult(configUuid, empty()) + .map(t3 -> t3._3.runCount()); // TODO exception handling + } + @Override public Either resultExists(final String configUuid) { return entityCentreResult(configUuid, of(1)) - .map(result -> !result.data().isEmpty()); + .map(t3 -> !executeWithEntities(t3._1, t3._2, t3._3).data().isEmpty()); // TODO exception handling } @Override From 26503f8263dd1edf71bc346801e044e84b86095b Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 00:03:05 +0300 Subject: [PATCH 62/76] #2224 Correct 'resultCount' API to use all proper conditions for generated Entity Centres. The generator was properly generating the entities, but user condition was missing. Also, query enhancer conditions were missing too. --- .../web/resources/webui/CriteriaResource.java | 27 ++++++++++--------- .../utils/DefaultEntityCentreProcessor.java | 13 +++++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 8df144bddaa..dcaf9fc25b7 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -686,6 +686,19 @@ public Representation put(final Representation envelope) { } } + // Complements criteria entity with IGenerator-related user condition, query enhancer, fetch providers etc. + // I.e. prepares criteria entity for running. + complementCriteriaEntityBeforeRunning( + previouslyRunCriteriaEntity, + webUiConfig, + companionFinder, + user, + critGenerator, + entityFactory, + centreContextHolder, + sharingModel + ); + // Execute actual Entity Centre configuration run / refresh / navigate / sort logic. final var resultListAndPage = executeEntityCentreConfiguration( new ConfigSettings(saveAsName, user, device(), miType), @@ -734,19 +747,7 @@ public static T2, IPage>> executeEntityCentreConf ) { final var centre = getEntityCentre(criteriaEntity.miType().getName(), webUiConfig); - final var resultCustomObjectAndEntities = createCriteriaMetaValuesCustomObjectWithResult( - customObject, - complementCriteriaEntityBeforeRunning( // complements criteriaEntity instance - criteriaEntity, - webUiConfig, - companionFinder, - configSettings.owner(), - critGenerator, - entityFactory, - centreContextHolder, - sharingModel - ) - ); + final var resultCustomObjectAndEntities = createCriteriaMetaValuesCustomObjectWithResult(customObject, criteriaEntity); final var resultCustomObject = resultCustomObjectAndEntities._1; final var resultEntities = resultCustomObjectAndEntities._2; diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 607499f9ef6..abb711aac22 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -213,6 +213,19 @@ public > Either> getResult(final St customPageCapacity -> freshCriteriaEntity.getCentreDomainTreeMangerAndEnhancer().getSecondTick().setPageCapacity(customPageCapacity) ); + // Complements criteria entity with IGenerator-related user condition, query enhancer, fetch providers etc. + // I.e. prepares criteria entity for running. + complementCriteriaEntityBeforeRunning( + freshCriteriaEntity, + webUiConfig, + companionFinder, + configSettings.owner(), + critGenerator, + entityFactory, + null, + sharingModel + ); + return right(t3(configSettings, customObject, freshCriteriaEntity)); } catch (final Exception exception) { return left(failure(new EntityCentreExecutionException(ERR_CONFIG_COULD_NOT_BE_EXECUTED.formatted(configUuid), exception))); From f60a3b86a791d9fecf8bc1a69de0addb80b88b64 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 00:56:37 +0300 Subject: [PATCH 63/76] #2224 Cover actual execution of resultExists/Count and getResult with exception handling. --- .../utils/DefaultEntityCentreProcessor.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index abb711aac22..b0cf7f76687 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -163,17 +163,20 @@ private static Either determineConfigurationSettings(fin @Override public > Either> getResult(final String configUuid) { - return entityCentreResult(configUuid, empty()) - .map(t3 -> executeWithEntities(t3._1, t3._2, t3._3)); // TODO exception handling + return entityCentreResult(configUuid, empty(), t3 -> executeWithEntities(t3._1, t3._2, t3._3)); } - /// Executes Entity Centre configuration with `configUuid`. + /// Prepares Entity Centre configuration with `configUuid` for running and applies `executor` to the prepared state. + /// `executor` runs inside the same `try/catch/finally` that handles user-provider swap and exception wrapping, + /// so callers need not duplicate exception handling for each kind of execution (running, counting, existence check). /// /// @param maybeCustomPageCapacity optional page capacity, which will override the page capacity of the configuration + /// @param executor operation to perform on the prepared configuration state; its result becomes the right value /// - private Either, EnhancedCentreEntityQueryCriteria, ?>>> entityCentreResult( + private Either entityCentreResult( final String configUuid, - final Optional maybeCustomPageCapacity + final Optional maybeCustomPageCapacity, + final Function, EnhancedCentreEntityQueryCriteria, ?>>, R> executor ) { // Determine current user to be returned back into the user provider once the execution has been performed. final User currentUser = userProvider.getUser(); @@ -226,7 +229,7 @@ public > Either> getResult(final St sharingModel ); - return right(t3(configSettings, customObject, freshCriteriaEntity)); + return right(executor.apply(t3(configSettings, customObject, freshCriteriaEntity))); } catch (final Exception exception) { return left(failure(new EntityCentreExecutionException(ERR_CONFIG_COULD_NOT_BE_EXECUTED.formatted(configUuid), exception))); } finally { @@ -259,14 +262,12 @@ private > IPage executeWithEntities( @Override public Either resultCount(final String configUuid) { - return entityCentreResult(configUuid, empty()) - .map(t3 -> t3._3.runCount()); // TODO exception handling + return entityCentreResult(configUuid, empty(), t3 -> t3._3.runCount()); } @Override public Either resultExists(final String configUuid) { - return entityCentreResult(configUuid, of(1)) - .map(t3 -> !executeWithEntities(t3._1, t3._2, t3._3).data().isEmpty()); // TODO exception handling + return entityCentreResult(configUuid, of(1), t3 -> !executeWithEntities(t3._1, t3._2, t3._3).data().isEmpty()); } @Override From 85f88e7755545b2edc9bc63ad6969f8bb3bf7711 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 01:06:52 +0300 Subject: [PATCH 64/76] #2224 Add docs to the API method. --- .../web/utils/EntityCentreProcessor.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 5fce1bb2fa0..3ea37155513 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -97,6 +97,32 @@ Either resultExists( String configUuid ); + + /// Finds out a count of entities in named Entity Centre configuration, defined by UUID (similarly to Web UI running). + /// Takes into account all unsaved changes in that configuration. + /// + /// Returns [Left] with invalid [Result] for the cases where + /// - UUID is blank (e.g. for default configurations) + /// - there is no configuration with that UUID (or there are multiple ones for some reason) + /// - there are only "orphan" inherited-from-shared configurations with no original one (i.e. if it was deleted) + /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001" + /// - UUID represents configuration with validation errors (e.g. requiredness or others) + /// - UUID represents configuration with authorisation errors (either Can Read or Can Read Property for non-empty criterion) + /// - UUID represents configuration with generator errors. + /// + /// Returns [Right] with [Integer] for a count of entities corresponding to configuration criteria. + /// Entity Centre with [IGenerator] still performs fresh data generation during execution through this API. + /// + /// Example: + /// ``` + /// final int workOrdersCount = entityCentreProcessor + /// .resultCount(configUuid) + /// .orElseThrow(Result::throwRuntime); + /// ``` + /// + /// **Important**: running of this method in context of `@SessionRequired` scope may roll back active transaction. + /// This means that API users must exercise caution if [Left] is returned (use [Either#orElseThrow(Function)]). + /// Either resultCount( String configUuid ); From 2d1ad66c1ac4edc89f5b0295f8753996079fb764 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 01:29:43 +0300 Subject: [PATCH 65/76] #2224 Correct / extend docs. --- .../platform/web/utils/EntityCentreProcessor.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 3ea37155513..8d16d314ba7 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -133,10 +133,11 @@ Either resultCount( /// - UUID is blank (e.g. for default configurations) /// - there is no configuration with that UUID (or there are multiple ones for some reason) /// - there are only "orphan" inherited-from-shared configurations with no original one (i.e. if it was deleted) - /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001" - /// - UUID represents configuration with validation errors (e.g. requiredness or others) - /// - UUID represents configuration with authorisation errors (either Can Read or Can Read Property for non-empty criterion) - /// - UUID represents configuration with generator errors. + /// - UUID represents so-called "link" configuration that originates from parameters like "?poCrit=PO001". + /// + /// Other unlikely cases where this method returns [Left] with invalid [Result] are if + /// - miType for some reason does not exist, but configuration still refers to it (perhaps, renamed or deleted) + /// - default configuration was found with UUID (default configs should never have UUIDs). /// /// Returns [Right] with [ConfigSettings] in case of valid configuration. /// From bddb29a8af7cce49579de32c760897a24e3dc926 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 01:57:01 +0300 Subject: [PATCH 66/76] #2224 Provide docs and minor re-factoring. --- .../web/utils/EntityCentreProcessor.java | 1 - .../web/resources/webui/CriteriaResource.java | 52 ++++++++----------- .../utils/DefaultEntityCentreProcessor.java | 8 +-- 3 files changed, 27 insertions(+), 34 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java index 8d16d314ba7..73f3c93fd04 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/EntityCentreProcessor.java @@ -97,7 +97,6 @@ Either resultExists( String configUuid ); - /// Finds out a count of entities in named Entity Centre configuration, defined by UUID (similarly to Web UI running). /// Takes into account all unsaved changes in that configuration. /// diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index dcaf9fc25b7..8e3591de876 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -788,7 +788,8 @@ public static T2, IPage>> executeEntityCentreConf resultCustomObject.put("dynamicColumns", createDynamicProperties(resPropsWithContext, centre)); } - final Stream> enhancedEntities = enhanceEntities(resultEntities.stream(), resPropsWithContext, centre); + // Enhance entities with custom / dynamic property values. + final Stream> enhancedEntities = enhanceEntitiesWithValues(resultEntities.stream(), resPropsWithContext, centre); final Stream> processedEntities; if (!skipCustomObjectCalculations) { // Enhance rendering hints with styles for each dynamic column. @@ -814,43 +815,34 @@ public static T2, IPage>> executeEntityCentreConf return t2(list, new Page(resultCustomObjectAndEntities._3, data, resPropsWithContext, centre)); } - private static Stream> enhanceEntities( + /// Enhances entities with custom / dynamic property values. + /// + private static Stream> enhanceEntitiesWithValues( final Stream> entityStream, final List>, Optional, ?>>>> resPropsWithContext, final EntityCentre> centre ) { - final Stream> processedEntities = enhanceResultEntitiesWithCustomPropertyValues( + // Enhance entities with values defined with consumer in each dynamic property. + return enhanceResultEntitiesWithDynamicPropertyValues( + // Enhance entities with custom values. + enhanceResultEntitiesWithCustomPropertyValues( centre, centre.getCustomPropertiesDefinitions(), centre.getCustomPropertiesAsignmentHandler(), - entityStream); - - // Enhance entities with values defined with consumer in each dynamic property. - return enhanceResultEntitiesWithDynamicPropertyValues(processedEntities, resPropsWithContext); + entityStream + ), + resPropsWithContext + ); } - private static class Page implements IPage> { - private final IPage> backingPage; - private final List> data; - private final List>, Optional, ?>>>> resPropsWithContext; - private final EntityCentre> centre; - - public Page( - final IPage> backingPage, - final List> data, - final List>, Optional, ?>>>> resPropsWithContext, - final EntityCentre> centre - ) { - this.backingPage = backingPage; - this.data = data; - this.resPropsWithContext = resPropsWithContext; - this.centre = centre; - } - - @Override - public List> data() { - return data; - } + /// A page implementation that takes into account the need to [#enhanceEntitiesWithValues(Stream, List, EntityCentre)]. + /// + private record Page( + IPage> backingPage, + List> data, + List>, Optional, ?>>>> resPropsWithContext, + EntityCentre> centre + ) implements IPage> { @Override public int capacity() { @@ -880,7 +872,7 @@ public boolean hasPrev() { private Page createPage(final IPage> page) { return new Page( page, - enhanceEntities(page.data().stream(), resPropsWithContext, centre).toList(), + enhanceEntitiesWithValues(page.data().stream(), resPropsWithContext, centre).toList(), resPropsWithContext, centre ); diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index b0cf7f76687..55ab5fcc217 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -238,18 +238,20 @@ private Either entityCentreResult( } } + /// Executes Entity Centre configuration with previously determined `configSettings` and `criteriaEntity`. + /// private > IPage executeWithEntities( final ConfigSettings configSettings, final Map customObject, - final EnhancedCentreEntityQueryCriteria, ?> freshCriteriaEntity + final EnhancedCentreEntityQueryCriteria, ?> criteriaEntity ) { - // Perform actual running of `freshCriteriaEntity` with `configSettings`. + // Perform actual running of `criteriaEntity` with `configSettings`. final var resultListAndPage = executeEntityCentreConfiguration( configSettings, empty(), true, customObject, - freshCriteriaEntity, + criteriaEntity, webUiConfig, companionFinder, critGenerator, From e4e7fece8ad9828db42685687f8f36408bfc85cb Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 02:32:17 +0300 Subject: [PATCH 67/76] #2224 Basic tests for resultCount. --- .../web/utils/EntityCentreProcessorTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 4b85b874a1e..5edff940f89 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -348,6 +348,20 @@ public void executing_getResult_method_returns_page_supporting_first_navigation( assertEquals(3, backToFirst.data().size()); } + @Test + public void executing_resultCount_method_returns_entity_count_below_page_capacity() { + final var uuid = initPaginatedData(2, 3); + final var result = getInstance(EntityCentreProcessor.class).resultCount(uuid); + assertEquals(Integer.valueOf(2), result.asRight().value()); + } + + @Test + public void executing_resultCount_method_returns_entity_count_above_page_capacity() { + final var uuid = initPaginatedData(8, 3); + final var result = getInstance(EntityCentreProcessor.class).resultCount(uuid); + assertEquals(Integer.valueOf(8), result.asRight().value()); + } + /// Initialise test data for config `uuid` (desktop device profile). /// /// @param createData runnable for custom data creation From 0a3924c1c66bba449b7e812a5a778616c0238b47 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 03:21:44 +0300 Subject: [PATCH 68/76] #2224 Additional tests for resultCount to cover createdBy condition for generated centres. --- .../web/utils/EntityCentreProcessorTest.java | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 5edff940f89..7652123ca8d 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -10,9 +10,7 @@ import ua.com.fielden.platform.sample.domain.TgGeneratedEntity; import ua.com.fielden.platform.sample.domain.compound.TgCompoundEntity; import ua.com.fielden.platform.security.tokens.persistent.TgGeneratedEntity_CanRead_Token; -import ua.com.fielden.platform.security.user.SecurityRoleAssociation; -import ua.com.fielden.platform.security.user.SecurityRoleAssociationCo; -import ua.com.fielden.platform.security.user.UserRole; +import ua.com.fielden.platform.security.user.*; import ua.com.fielden.platform.test_config.AbstractDaoTestCase; import ua.com.fielden.platform.test_config.H2OrPostgreSqlOrSqlServerContextSelectorForWebTests; import ua.com.fielden.platform.ui.config.EntityCentreConfig; @@ -29,6 +27,7 @@ import static java.util.UUID.randomUUID; import static org.junit.Assert.*; import static ua.com.fielden.platform.entity.meta.MetaProperty.ERR_REQUIRED; +import static ua.com.fielden.platform.entity.query.fluent.EntityQueryUtils.select; import static ua.com.fielden.platform.types.tuples.T2.t2; import static ua.com.fielden.platform.utils.CollectionUtil.*; import static ua.com.fielden.platform.web.centre.CentreUpdater.*; @@ -362,6 +361,52 @@ public void executing_resultCount_method_returns_entity_count_above_page_capacit assertEquals(Integer.valueOf(8), result.asRight().value()); } + @Test + public void executing_resultCount_method_returns_invalid_result_for_configuration_with_invalid_generation() { + final var uuid = randomUUID().toString(); + + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "critOnlySingleProp", getUser()), configSettings); + + final var result = getInstance(EntityCentreProcessor.class).resultCount(uuid); + + assertNotNull(result); + assertTrue(result.isLeft()); + assertNotNull(result.asLeft().value()); + assertFalse(result.asLeft().value().isSuccessful()); + assertEquals("Can not generate the instance based on current user [%s], choose another user for that.".formatted(getUser()), result.asLeft().value().getMessage()); + } + + @Test + public void executing_resultCount_method_returns_valid_result_for_configuration_with_valid_generation_and_does_not_include_entities_generated_by_other_users() { + final var uuid = randomUUID().toString(); + final var user = save(new_(User.class, "USER").setBase(true).setEmail("USER@unit-test.software").setActive(true)); + save(new_composite(UserAndRoleAssociation.class, user, co(UserRole.class).findByKey(UNIT_TEST_ROLE))); + final var configSettings = new ConfigSettings(of("saveAs"), user, DESKTOP, MiTgGeneratedEntity.class); + createConfig(configSettings, FRESH_CENTRE_NAME, uuid); + createConfig(configSettings, SAVED_CENTRE_NAME, uuid); + initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "critOnlySingleProp", getUser()), configSettings); + getInstance(EntityCentreProcessor.class).resultCount(uuid); + assertEquals(10, co(TgGeneratedEntity.class).count(select(TgGeneratedEntity.class).model())); + + final var otherUuid = randomUUID().toString(); + final var otherUser = save(new_(User.class, "OTHER_USER").setBase(true).setEmail("OTHER_USER@unit-test.software").setActive(true)); + save(new_composite(UserAndRoleAssociation.class, otherUser, co(UserRole.class).findByKey(UNIT_TEST_ROLE))); + final var otherUserConfigSettings = new ConfigSettings(of("otherUserSaveAs"), otherUser, DESKTOP, MiTgGeneratedEntity.class); + createConfig(otherUserConfigSettings, FRESH_CENTRE_NAME, otherUuid); + createConfig(otherUserConfigSettings, SAVED_CENTRE_NAME, otherUuid); + initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "critOnlySingleProp", getUser()), otherUserConfigSettings); + final var result = getInstance(EntityCentreProcessor.class).resultCount(otherUuid); + assertEquals(20, co(TgGeneratedEntity.class).count(select(TgGeneratedEntity.class).model())); + + assertNotNull(result); + assertTrue(result.isRight()); + assertNotNull(result.asRight().value()); + assertEquals(Integer.valueOf(10), result.asRight().value()); + } + /// Initialise test data for config `uuid` (desktop device profile). /// /// @param createData runnable for custom data creation From be58b4108b992e6c650388c1e316fcd6093789e8 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 20:19:41 +0300 Subject: [PATCH 69/76] #2224 Explicitly exclude transport non-persistent entity-typed properties from introspection. --- .../centre/CentreContextHolder.java | 12 ++++---- .../functional/centre/SavingInfoHolder.java | 30 ++++++++----------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/CentreContextHolder.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/CentreContextHolder.java index 841750d00b4..79261bd072d 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/CentreContextHolder.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/CentreContextHolder.java @@ -11,15 +11,15 @@ import static java.util.Collections.unmodifiableList; import static java.util.Collections.unmodifiableMap; -/** - * The entity holder for centre context and criteria entity's modified props. - * - * @author TG Team - * - */ +/// The entity holder for centre context and criteria entity's modified props. +/// @KeyType(String.class) @KeyTitle(value = "Key", desc = "Some key description") @CompanionObject(ICentreContextHolder.class) +// GraphQL Web API schema may not find `CentreContextHolder` type in case if it is used as property in allowed for introspection types +// (e.g. those that are persistent / synthetic and have no @DenyIntrospection). +// That's why it is explicitly denied for introspection and such properties become `AbstractDomainTreeRepresentation.isExcluded`. +@DenyIntrospection public class CentreContextHolder extends AbstractEntity { @IsProperty(Object.class) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/SavingInfoHolder.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/SavingInfoHolder.java index 4265eb22d55..de78e5c7632 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/SavingInfoHolder.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/entity/functional/centre/SavingInfoHolder.java @@ -1,31 +1,27 @@ package ua.com.fielden.platform.entity.functional.centre; -import static java.util.Collections.unmodifiableList; -import static java.util.Collections.unmodifiableMap; -import static ua.com.fielden.platform.entity.NoKey.NO_KEY; +import ua.com.fielden.platform.entity.AbstractEntity; +import ua.com.fielden.platform.entity.IContinuationData; +import ua.com.fielden.platform.entity.NoKey; +import ua.com.fielden.platform.entity.annotation.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import ua.com.fielden.platform.entity.AbstractEntity; -import ua.com.fielden.platform.entity.IContinuationData; -import ua.com.fielden.platform.entity.NoKey; -import ua.com.fielden.platform.entity.annotation.CompanionObject; -import ua.com.fielden.platform.entity.annotation.IsProperty; -import ua.com.fielden.platform.entity.annotation.KeyType; -import ua.com.fielden.platform.entity.annotation.Observable; -import ua.com.fielden.platform.entity.annotation.Title; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; +import static ua.com.fielden.platform.entity.NoKey.NO_KEY; -/** - * The entity holder for saving information: entity's modified props + centre context, if any. - * - * @author TG Team - * - */ +/// The entity holder for saving information: entity's modified props + centre context, if any. +/// @KeyType(NoKey.class) @CompanionObject(ISavingInfoHolder.class) +// GraphQL Web API schema may not find `CentreContextHolder` type in case if it is used as property in allowed for introspection types +// (e.g. those that are persistent / synthetic and have no @DenyIntrospection). +// That's why it is explicitly denied for introspection and such properties become `AbstractDomainTreeRepresentation.isExcluded`. +@DenyIntrospection public class SavingInfoHolder extends AbstractEntity { @IsProperty(Object.class) From 71a57ca0a88c935d16f645c0a5d591d2bf6cf544 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Sat, 18 Apr 2026 22:03:49 +0300 Subject: [PATCH 70/76] Revert "#2224 b. Temporal workaround to allow 'EntityCentreConfig / MainMenuItem' sub-properties in selection criteria and result-set." This reverts commit 8ada633d --- .../ua/com/fielden/platform/ui/config/EntityCentreConfig.java | 2 +- .../java/ua/com/fielden/platform/ui/config/MainMenuItem.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java index f931dd1bfe1..343739ddc58 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/EntityCentreConfig.java @@ -40,7 +40,7 @@ @CompanionObject(EntityCentreConfigCo.class) @MapEntityTo("ENTITY_CENTRE_CONFIG") @DescTitle("Description") -//@DenyIntrospection +@DenyIntrospection public class EntityCentreConfig extends AbstractConfiguration { @IsProperty diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java index c4ec52964b6..766ae34dbdc 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/ui/config/MainMenuItem.java @@ -3,7 +3,6 @@ import ua.com.fielden.platform.algorithm.search.ITreeNode; import ua.com.fielden.platform.entity.AbstractEntity; import ua.com.fielden.platform.entity.annotation.*; -import ua.com.fielden.platform.error.Result; import ua.com.fielden.platform.reflection.ClassesRetriever; import ua.com.fielden.platform.reflection.PropertyTypeDeterminator; @@ -31,7 +30,7 @@ @DescTitle("Description") @CompanionObject(MainMenuItemCo.class) @MapEntityTo("MAIN_MENU") -//@DenyIntrospection +@DenyIntrospection public class MainMenuItem extends AbstractEntity implements ITreeNode { @IsProperty From 44b1d4dd6c6cc4cb2a5963239fcc01ede0137dda Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 4 May 2026 21:23:18 +0300 Subject: [PATCH 71/76] #2224 Expose getParentAnd API method to pre/postActions on Entity Centres. See more modern approach in #2407, see section 'Change overview'. --- .../fielden/platform/web/centre/tg-entity-centre-template.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js index 1cc14813e54..b6aa40a8eec 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template.js @@ -28,6 +28,9 @@ import '/resources/centre/tg-selection-criteria-styles.js'; import { TgEntityCentreTemplateBehavior } from '/resources/centre/tg-entity-centre-template-behavior.js'; import '/resources/centre/tg-entity-centre-insertion-point.js'; import { TgReflector } from '/app/tg-reflector.js'; +// The following import statement are useful for pre/PostActions on Entity Centre actions. +// This will be eliminated by pre/PostActions Imports API in #2407 (>= 3.0.0). +import { getParentAnd } from '/resources/reflection/tg-polymer-utils.js'; const selectionCritTemplate = html` From 77147075a1bfa966f218ac9aac7ada1ba9653b58 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 4 May 2026 23:07:04 +0300 Subject: [PATCH 72/76] #2224 Enhance 'tg-ui-action' with context customisation API. --- .../ua/com/fielden/platform/web/actions/tg-ui-action.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js index 68701ffb6b3..e48d9b0713d 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js @@ -767,6 +767,13 @@ Polymer({ self._reflector.setCustomProperty(context, '@@sharedUri', self._sharedUri); } } + // Customly enhance context by applying `customiseContext` function, that can be implemented, e.g., in `preAction`. + self.customiseContext?.( + context, // The `context` to be customised. + self, // `tg-ui-action` itself. + // Convenient `setCustomProperty` function. + (customPropertyName, customPropertyValue) => self._reflector.setCustomProperty(context, customPropertyName, customPropertyValue) + ); return context; }, From 13f5a295c86d9765ed0fc6e905d881b22255d636 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Mon, 4 May 2026 23:40:10 +0300 Subject: [PATCH 73/76] #2224 Capture centre config entity as part of ConfigSettings for 'EntityCentreProcessor.validate' API. This may be useful for the API clients, e.g. if foreign key reference is mandatory on the config instance being executed. --- .../fielden/platform/web/utils/ConfigSettings.java | 13 ++++++++----- .../web/resources/webui/CriteriaResource.java | 2 +- .../web/utils/DefaultEntityCentreProcessor.java | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/ConfigSettings.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/ConfigSettings.java index f71bcab4f43..83838f35ee3 100644 --- a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/ConfigSettings.java +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/ConfigSettings.java @@ -1,6 +1,7 @@ package ua.com.fielden.platform.web.utils; import ua.com.fielden.platform.security.user.User; +import ua.com.fielden.platform.ui.config.EntityCentreConfig; import ua.com.fielden.platform.ui.menu.MiWithConfigurationSupport; import ua.com.fielden.platform.web.interfaces.DeviceProfile; @@ -8,14 +9,16 @@ /// Convenient record holding Entity Centre configuration settings. /// -/// @param saveAsName optional "save-as" name for named configuration or empty [Optional] for default one -/// @param owner a [User] that created the configuration (or own it through inheritance process) -/// @param device indicates whether the configuration belongs to [DeviceProfile#DESKTOP] namespace or [DeviceProfile#MOBILE] -/// @param miType menu item type for the configuration +/// @param saveAsName optional "save-as" name for named configuration or empty [Optional] for default one +/// @param owner a [User] that created the configuration (or own it through inheritance process) +/// @param device indicates whether the configuration belongs to [DeviceProfile#DESKTOP] namespace or [DeviceProfile#MOBILE] +/// @param miType menu item type for the configuration +/// @param centreConfig Entity Centre configuration entity instance /// public record ConfigSettings( Optional saveAsName, User owner, DeviceProfile device, - Class> miType + Class> miType, + EntityCentreConfig centreConfig ) {} diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 8e3591de876..7e3d357148a 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -701,7 +701,7 @@ public Representation put(final Representation envelope) { // Execute actual Entity Centre configuration run / refresh / navigate / sort logic. final var resultListAndPage = executeEntityCentreConfiguration( - new ConfigSettings(saveAsName, user, device(), miType), + new ConfigSettings(saveAsName, user, device(), miType, null), of(t2(updatedFreshCentre, previouslyRunCentre)), isRunning, diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index 55ab5fcc217..cc947c65e75 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -158,7 +158,7 @@ private static Either determineConfigurationSettings(fin if (LINK_CONFIG_TITLE.equals(saveAsNameString)) { return left(failure(ERR_LINK_CONFIG_IS_NOT_AVAILABLE_FOR_RUNNING.formatted(configUuid, saveAsNameString))); } - return right(new ConfigSettings(of(saveAsNameString), owner, device, miType)); + return right(new ConfigSettings(of(saveAsNameString), owner, device, miType, freshConfig)); } @Override From 86621ac822f2f788aad2407e4d3cd1b7f6fd7bf7 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 5 May 2026 17:41:14 +0300 Subject: [PATCH 74/76] #2224 De-duplicate date population config logic. --- .../web/test/server/DataPopulationConfig.java | 19 +++++++++++++++++-- .../DataPopulationConfigForWebTests.java | 17 ++++++++--------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java index 278262e72d8..3f67a55f134 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/test/server/DataPopulationConfig.java @@ -1,6 +1,7 @@ package ua.com.fielden.platform.web.test.server; import com.google.inject.Injector; +import com.google.inject.Module; import ua.com.fielden.platform.audit.AuditingMode; import ua.com.fielden.platform.ioc.AbstractPlatformIocModule; import ua.com.fielden.platform.ioc.ApplicationInjectorFactory; @@ -48,10 +49,24 @@ public DataPopulationConfig(final Properties props) { protected ApplicationInjectorFactory createFactory(final Properties properties) { final ApplicationDomain appDomain = new ApplicationDomain(); return new ApplicationInjectorFactory() - .add(new TgTestApplicationServerIocModule(appDomain, appDomain.domainTypes(), properties)) + .add(createApplicationServerModule(appDomain, properties)) .add(new NewUserEmailNotifierTestIocModule()) .add(new DataFilterTestIocModule()) - .add(new IocModule()); + .add(createAdditionalIocModule()); + } + + /// Creates the application-server [Module] used by [#createFactory]. + /// Subclasses can override to substitute a different (e.g. web-aware) server module. + /// + protected TgTestApplicationServerIocModule createApplicationServerModule(final ApplicationDomain appDomain, final Properties properties) { + return new TgTestApplicationServerIocModule(appDomain, appDomain.domainTypes(), properties); + } + + /// Creates an additional [Module] appended at the end of the factory chain. + /// Subclasses can override to substitute a module appropriate for their context (e.g. one that does not duplicate bindings already provided by their server module). + /// + protected Module createAdditionalIocModule() { + return new IocModule(); } @Override diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java index f8fd5f8298f..cdac3508a16 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/test/server/DataPopulationConfigForWebTests.java @@ -1,8 +1,7 @@ package ua.com.fielden.platform.web.test.server; +import com.google.inject.Module; import ua.com.fielden.platform.audit.AuditingMode; -import ua.com.fielden.platform.ioc.ApplicationInjectorFactory; -import ua.com.fielden.platform.ioc.NewUserEmailNotifierTestIocModule; import ua.com.fielden.platform.test.IDomainDrivenTestCaseConfiguration; import ua.com.fielden.platform.web.test.config.ApplicationDomain; @@ -30,13 +29,13 @@ private static Properties enableAuditing(final Properties props) { } @Override - protected ApplicationInjectorFactory createFactory(final Properties properties) { - final ApplicationDomain appDomain = new ApplicationDomain(); - return new ApplicationInjectorFactory() - .add(new TgTestWebApplicationServerIocModule(appDomain, appDomain.domainTypes(), properties)) - .add(new NewUserEmailNotifierTestIocModule()) - .add(new DataFilterTestIocModule()) - .add(new UniversalConstantsTestIocModule()); + protected TgTestApplicationServerIocModule createApplicationServerModule(final ApplicationDomain appDomain, final Properties properties) { + return new TgTestWebApplicationServerIocModule(appDomain, appDomain.domainTypes(), properties); + } + + @Override + protected Module createAdditionalIocModule() { + return new UniversalConstantsTestIocModule(); } } From 7780a38e175dd82e46ce1c4d06bfae6b036d788f Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Tue, 5 May 2026 18:33:29 +0300 Subject: [PATCH 75/76] #2224 Adjust tests with CentreConfig change. --- .../web/utils/EntityCentreProcessorTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java index 7652123ca8d..c97f130cabf 100644 --- a/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java +++ b/platform-web-resources/src/test/java/ua/com/fielden/platform/web/utils/EntityCentreProcessorTest.java @@ -184,7 +184,7 @@ public void executing_resultExists_method_returns_invalid_result_for_non_existin public void executing_resultExists_method_returns_invalid_result_for_orphan_inherited_from_shared_named_configuration() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgCompoundEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgCompoundEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, null); @@ -201,7 +201,7 @@ public void executing_resultExists_method_returns_invalid_result_for_orphan_inhe public void executing_resultExists_method_returns_invalid_result_for_unusual_named_configuration_with_no_miType() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, null); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, null, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -218,7 +218,7 @@ public void executing_resultExists_method_returns_invalid_result_for_unusual_nam public void executing_resultExists_method_returns_invalid_result_for_unusual_default_configuration_with_uuid() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(empty(), getUser(), DESKTOP, MiTgCompoundEntity.class); + final var configSettings = new ConfigSettings(empty(), getUser(), DESKTOP, MiTgCompoundEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -235,7 +235,7 @@ public void executing_resultExists_method_returns_invalid_result_for_unusual_def public void executing_resultExists_method_returns_invalid_result_for_link_configuration() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(of(LINK_CONFIG_TITLE), getUser(), DESKTOP, MiTgCompoundEntity.class); + final var configSettings = new ConfigSettings(of(LINK_CONFIG_TITLE), getUser(), DESKTOP, MiTgCompoundEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -252,7 +252,7 @@ public void executing_resultExists_method_returns_invalid_result_for_link_config public void executing_resultExists_method_returns_invalid_result_for_invalid_configuration() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -271,7 +271,7 @@ public void executing_resultExists_method_returns_invalid_result_for_invalid_con public void executing_resultExists_method_returns_invalid_result_for_unauthorised_configuration() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -295,7 +295,7 @@ public void executing_resultExists_method_returns_invalid_result_for_unauthorise public void executing_resultExists_method_returns_invalid_result_for_configuration_with_invalid_generation() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); @@ -365,7 +365,7 @@ public void executing_resultCount_method_returns_entity_count_above_page_capacit public void executing_resultCount_method_returns_invalid_result_for_configuration_with_invalid_generation() { final var uuid = randomUUID().toString(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), DESKTOP, MiTgGeneratedEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "critOnlySingleProp", getUser()), configSettings); @@ -384,7 +384,7 @@ public void executing_resultCount_method_returns_valid_result_for_configuration_ final var uuid = randomUUID().toString(); final var user = save(new_(User.class, "USER").setBase(true).setEmail("USER@unit-test.software").setActive(true)); save(new_composite(UserAndRoleAssociation.class, user, co(UserRole.class).findByKey(UNIT_TEST_ROLE))); - final var configSettings = new ConfigSettings(of("saveAs"), user, DESKTOP, MiTgGeneratedEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), user, DESKTOP, MiTgGeneratedEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "critOnlySingleProp", getUser()), configSettings); @@ -394,7 +394,7 @@ public void executing_resultCount_method_returns_valid_result_for_configuration_ final var otherUuid = randomUUID().toString(); final var otherUser = save(new_(User.class, "OTHER_USER").setBase(true).setEmail("OTHER_USER@unit-test.software").setActive(true)); save(new_composite(UserAndRoleAssociation.class, otherUser, co(UserRole.class).findByKey(UNIT_TEST_ROLE))); - final var otherUserConfigSettings = new ConfigSettings(of("otherUserSaveAs"), otherUser, DESKTOP, MiTgGeneratedEntity.class); + final var otherUserConfigSettings = new ConfigSettings(of("otherUserSaveAs"), otherUser, DESKTOP, MiTgGeneratedEntity.class, null); createConfig(otherUserConfigSettings, FRESH_CENTRE_NAME, otherUuid); createConfig(otherUserConfigSettings, SAVED_CENTRE_NAME, otherUuid); initTestData(centreManager -> centreManager.getFirstTick().setValue(TgGeneratedEntity.class, "critOnlySingleProp", getUser()), otherUserConfigSettings); @@ -437,7 +437,7 @@ private String initPaginatedData(final int entityCount, final int pageCapacity) private void initTestData(final String uuid, final DeviceProfile device, final Runnable createData, final Consumer enhanceCentreManager) { createData.run(); - final var configSettings = new ConfigSettings(of("saveAs"), getUser(), device, MiTgCompoundEntity.class); + final var configSettings = new ConfigSettings(of("saveAs"), getUser(), device, MiTgCompoundEntity.class, null); createConfig(configSettings, FRESH_CENTRE_NAME, uuid); createConfig(configSettings, SAVED_CENTRE_NAME, uuid); From 84547c68b9ce6e1209bbf83b11a98ce197a68821 Mon Sep 17 00:00:00 2001 From: jhou-pro Date: Wed, 6 May 2026 00:05:11 +0300 Subject: [PATCH 76/76] #2224 More clear intent for the parameter preserving all existing invariants. --- .../web/utils/CentreExecutionMode.java | 15 ++++++++++ .../web/utils/HeadlessCentreExecution.java | 8 +++++ .../platform/web/utils/UiCentreExecution.java | 14 +++++++++ .../web/resources/webui/CriteriaResource.java | 29 ++++++++++--------- .../utils/DefaultEntityCentreProcessor.java | 2 +- 5 files changed, 54 insertions(+), 14 deletions(-) create mode 100644 platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/CentreExecutionMode.java create mode 100644 platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/HeadlessCentreExecution.java create mode 100644 platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/UiCentreExecution.java diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/CentreExecutionMode.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/CentreExecutionMode.java new file mode 100644 index 00000000000..45a44378380 --- /dev/null +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/CentreExecutionMode.java @@ -0,0 +1,15 @@ +package ua.com.fielden.platform.web.utils; + +/// Distinguishes the two execution modes of `CriteriaResource#executeEntityCentreConfiguration`. +/// +/// Sealed deliberately so that any future Entity Centre evolution that introduces a new mode forces +/// reconciliation with the central execution method (the compiler will flag a non-exhaustive switch). +/// This protects programmatic callers (e.g., `DefaultEntityCentreProcessor`) from breaking when the +/// UI flow changes. +/// +/// Permitted implementations: +/// - [HeadlessCentreExecution] -- programmatic API; data only, no UI augmentation. +/// - [UiCentreExecution] -- UI-driven; rendering hints, action indices, dynamic-column metadata, +/// and (when running) the criteria-changed indication are produced. +/// +public sealed interface CentreExecutionMode permits HeadlessCentreExecution, UiCentreExecution {} \ No newline at end of file diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/HeadlessCentreExecution.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/HeadlessCentreExecution.java new file mode 100644 index 00000000000..34787c328ac --- /dev/null +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/HeadlessCentreExecution.java @@ -0,0 +1,8 @@ +package ua.com.fielden.platform.web.utils; + +/// Headless execution invoked from `DefaultEntityCentreProcessor` for the programmatic API. +/// UI-only output (rendering hints, primary/secondary/property action indices, dynamic-column metadata, +/// criteria-changed indication, leading custom-object entry in the result list) is suppressed. +/// The result list contains only entities. +/// +public record HeadlessCentreExecution() implements CentreExecutionMode {} \ No newline at end of file diff --git a/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/UiCentreExecution.java b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/UiCentreExecution.java new file mode 100644 index 00000000000..58427bdadb0 --- /dev/null +++ b/platform-pojo-bl/src/main/java/ua/com/fielden/platform/web/utils/UiCentreExecution.java @@ -0,0 +1,14 @@ +package ua.com.fielden.platform.web.utils; + +import ua.com.fielden.platform.domaintree.centre.ICentreDomainTreeManager.ICentreDomainTreeManagerAndEnhancer; + +/// UI-driven execution from `CriteriaResource`. All UI-specific output is produced. +/// +/// `updatedFreshCentre` and `previouslyRunCentre` are consumed only when `isRunning` is `true`, +/// to compute the criteria-changed indication. They are unused when `isRunning` is `false` +/// (sort / navigate / refresh) but must still be supplied for symmetry with the UI request flow. +/// +public record UiCentreExecution( + ICentreDomainTreeManagerAndEnhancer updatedFreshCentre, + ICentreDomainTreeManagerAndEnhancer previouslyRunCentre +) implements CentreExecutionMode {} \ No newline at end of file diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java index 7e3d357148a..e052ee40715 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/resources/webui/CriteriaResource.java @@ -46,7 +46,9 @@ import ua.com.fielden.platform.web.interfaces.DeviceProfile; import ua.com.fielden.platform.web.interfaces.IDeviceProvider; import ua.com.fielden.platform.web.resources.RestServerUtil; +import ua.com.fielden.platform.web.utils.CentreExecutionMode; import ua.com.fielden.platform.web.utils.ConfigSettings; +import ua.com.fielden.platform.web.utils.UiCentreExecution; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -702,7 +704,7 @@ public Representation put(final Representation envelope) { // Execute actual Entity Centre configuration run / refresh / navigate / sort logic. final var resultListAndPage = executeEntityCentreConfiguration( new ConfigSettings(saveAsName, user, device(), miType, null), - of(t2(updatedFreshCentre, previouslyRunCentre)), + new UiCentreExecution(updatedFreshCentre, previouslyRunCentre), isRunning, customObject, @@ -728,12 +730,13 @@ public Representation put(final Representation envelope) { /// Executes Entity Centre configuration with [ConfigSettings]. Uses `criteriaEntity` for execution. /// - /// @param updatedFreshCentreAndPreviouslyRunCentre an [Optional] pair of centre managers for more comprehensive and complete running; - /// if passed, rendering hints / UI action indices / criteria indicator will be computed + /// @param mode distinguishes UI-driven execution (computes rendering hints, action indices, + /// dynamic-column metadata, and -- when `isRunning` -- the criteria-changed indication) + /// from headless execution (data only). See [CentreExecutionMode]. /// public static T2, IPage>> executeEntityCentreConfiguration( final ConfigSettings configSettings, - final Optional> updatedFreshCentreAndPreviouslyRunCentre, + final CentreExecutionMode mode, final boolean isRunning, final Map customObject, final EnhancedCentreEntityQueryCriteria, ?> criteriaEntity, @@ -751,14 +754,14 @@ public static T2, IPage>> executeEntityCentreConf final var resultCustomObject = resultCustomObjectAndEntities._1; final var resultEntities = resultCustomObjectAndEntities._2; - final var skipCustomObjectCalculations = updatedFreshCentreAndPreviouslyRunCentre.isEmpty(); - if (!skipCustomObjectCalculations) { + // Pattern-bind once for use across the four UI-mode gates below. + // Equivalent in shape to the previous single-boolean gate, so the original computation order is preserved exactly. + final var ui = (mode instanceof UiCentreExecution u) ? u : null; + if (ui != null) { if (isRunning) { - final var updatedFreshCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._1; - final var previouslyRunCentre = updatedFreshCentreAndPreviouslyRunCentre.get()._2; final var updatedSavedCentre = updateCentre(configSettings.owner(), configSettings.miType(), SAVED_CENTRE_NAME, configSettings.saveAsName(), configSettings.device(), webUiConfig, companionFinder); - final var changedCriteriaIndication = createChangedCriteriaIndication(updatedFreshCentre, updatedSavedCentre); - updateResultantCustomObject(criteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), configSettings.miType(), configSettings.saveAsName(), previouslyRunCentre, resultCustomObject, of(changedCriteriaIndication)); + final var changedCriteriaIndication = createChangedCriteriaIndication(ui.updatedFreshCentre(), updatedSavedCentre); + updateResultantCustomObject(criteriaEntity.centreDirtyCalculatorWithSavedSupplier().apply(() -> updatedSavedCentre), configSettings.miType(), configSettings.saveAsName(), ui.previouslyRunCentre(), resultCustomObject, of(changedCriteriaIndication)); } // Running the rendering customiser for result set of entities. @@ -784,14 +787,14 @@ public static T2, IPage>> executeEntityCentreConf sharingModel ); - if (!skipCustomObjectCalculations) { + if (ui != null) { resultCustomObject.put("dynamicColumns", createDynamicProperties(resPropsWithContext, centre)); } // Enhance entities with custom / dynamic property values. final Stream> enhancedEntities = enhanceEntitiesWithValues(resultEntities.stream(), resPropsWithContext, centre); final Stream> processedEntities; - if (!skipCustomObjectCalculations) { + if (ui != null) { // Enhance rendering hints with styles for each dynamic column. processedEntities = enhanceResultEntitiesWithDynamicPropertyRenderingHints(enhancedEntities, resPropsWithContext, (List) resultCustomObject.get("renderingHints")); } @@ -800,7 +803,7 @@ public static T2, IPage>> executeEntityCentreConf } final var list = new ArrayList<>(); - if (!skipCustomObjectCalculations) { + if (ui != null) { list.add(isRunning ? criteriaEntity : null); list.add(resultCustomObject); } diff --git a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java index cc947c65e75..3fd53b9089f 100644 --- a/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java +++ b/platform-web-resources/src/main/java/ua/com/fielden/platform/web/utils/DefaultEntityCentreProcessor.java @@ -248,7 +248,7 @@ private > IPage executeWithEntities( // Perform actual running of `criteriaEntity` with `configSettings`. final var resultListAndPage = executeEntityCentreConfiguration( configSettings, - empty(), + new HeadlessCentreExecution(), true, customObject, criteriaEntity,