Skip to content

EQL critCondition operator

01es edited this page Mar 21, 2022 · 7 revisions

Abstract

This wiki discusses how EQL operator critCondition (issue #947) can and should be used, where is it applicable and what advantages does it provide to developers and end users.

Table of contents

  1. Mnemonics for @CritOnly properties
  2. Generated entities
  3. Synthetic entities
    1. With @CritOnly properties
      1. In application to non-collectional properties
      2. In application to non-collectional properties for one-2-many associations
    2. Without @CritOnly properties
      1. Using @CritOnly properties of master entity
      2. Using regular properties of master entity
  4. Value matchers
    1. Using @CritOnly properties
    2. Using regular properties

Mnemonics for @CritOnly properties

Mnemonics were not previously supported for @CritOnly properties. However, following the implementation of #947 mnemonics are supported for @CritOnly properties and their sub-properties.

By default, @CritOnly properties of type RANGE and MULTI have mnemonics enabled, while SINGLE -- disabled. These defaults can be overridden using mnemonics attribute, e.g.

    @IsProperty
    @CritOnly(value = MULTI, mnemonics = WITHOUT)
    @Title("Search Word(s), comma separated")
    private String searchWord;

Attributes of @CritOnly are applied in cases where subproperties are of a @CritOnly property are used. This includes configurations WITH / WITHOUT. The use of subproperties can be very convenient for providing additional selection criteria without actually creating additional @CritOnly properties.

Generated entities

Entity generation logic was previously using @CritOnly properties in the following manner (very much the same as for synthetic entities):

    .where().prop("workActivity.waType.key").iLike().anyOfIParams("waTypeCrit")

This was an imperative approach that required developers to express condition explicitly and did not have the ability to take into account any mnemonics.

The new API enables developers to simply write:

    .where().critCondition("workActivity.waType.key", "waTypeCrit")

Operator critCondition is responsible for expanding the query automatically, taking into account values and mnemonics entered by users or specified by execution parameters programatically (see further below).

N.B. Please note that the use of .key is required to correctly generate and apply crit-conditions. It may represent a real or a virtual property key, which transforms key values to String for use in crit-conditions.

This operator applicable to @CritOnly properties of any Type (attribute value) -- SINGLE, MULTI and RANGE, any Mnemonics (attribute mnemonics) -- WITH and WITHOUT, and any property type.

EQL models that use operator critCondition should be executed .with(queryParams), where queryParams are obtained via the following transformation of params passed into gen():

    final Map<String, Object> queryParams = EntityQueryUtils.extractExactParams(params);

This transformation is required to include parameters to capture mnemonics.

Synthetic entities

With @CritOnly properties

In application to non-collectional properties

It is believed that @CritOnly properties are not applicable for non-collectional properties of an entity itself. Non-collectional properties include persistent and calculated properties, and also properties representing one-2-one associations.

Simply exposing non-collectional properties or their subproperties as selection criteria, automatically enables support for mnemonics.

In application to non-collectional properties for one-2-many associations

In this case we're talking about @CritOnly properties that are used to introduce conditions in application to one-2-many (aka collectional) associations.

Before critCondition we had to write something like that:

    begin().
        exists(select(KeyNumber.class).where().allOfIParams("waDetailCommentCrit").isNull().model()). 
        or().exists(select(WorkActivityDetail.class).where().prop("comment").iLike().anyOfIParams("waDetailCommentCrit").and().prop("workActivity").eq().extProp("id").model()).
    end().

The use of the first exists was to support the situation where no values for waDetailsCommnetCrit was entered (i.e. no need to any condition). Otherwise, the second exists would get evaluated to false for cases where there was no records in the one-2-many association, and the result would incorrectly exclude those master entities (WorkAcivity in this example) that had no such associations. Of course, mnemonics also were not supported.

The new API enable to simply write:

    .critCondition(select(WorkActivityDetail.class).where().prop("workActivity").eq().extProp("id"), "comment", "waDetailCommentCrit")

Developers are still required to provide a subquery to link entities from the many side to the master entity in the one side of the one-2-many association. But the rest is taken care of automatically, including support for mnemonics.

More complex cases my involve several one-2-many associations with the same condition being applied across all of them. For example, where the model for a synthetic entity was previously using @CritOnly properties in the following manner:

    where().
    begin().
        existsAnyOf(
            select(KeyNumber.class).where().allOfIParams("workActivityCrit").isNull().model(), 
            select(DcPoExpendableLine.class).where().prop("workActivity.key").iLike().anyOfIParams("workActivityCrit").and().prop("purchaseOrder").eq().extProp("id").model(), 
            select(DcPoPurchaseRotableLine.class).where().prop("workActivity.key").iLike().anyOfIParams("workActivityCrit").and().prop("purchaseOrder").eq().extProp("id").model(), 
            select(DcPoRepairRotableLine.class).where().prop("workActivity.key").iLike().anyOfIParams("workActivityCrit").and().prop("purchaseOrder").eq().extProp("id").model(), 
            select(DcPoServiceLine.class).where().prop("workActivity.key").iLike().anyOfIParams("workActivityCrit").and().prop("purchaseOrder").eq().extProp("id").model()). 
    end().

With the new API, the developer would write

            where().
                    critCondition(makeUnifiedModel("workActivity", DcPoExpendableLine.class, DcPoPurchaseRotableLine.class, DcPoRepairRotableLine.class, DcPoServiceLine.class, PoFreightLine.class), "workActivity.key", "workActivityCrit").

where makeUnifiedModel() is defined as:

    private static ICompoundCondition0<EntityAggregates> makeUnifiedModel(final String propName, final Class<? extends AbstractPoLine<?, ?>>... lineTypes) {
        final List<AggregatedResultQueryModel> queryModels = new ArrayList<>();
        for (final Class<? extends AbstractPoLine<?, ?>> lineType : lineTypes) {
            queryModels.add(select(lineType)
                    .yield().prop("purchaseOrder").as("purchaseOrder")
                    .yield().prop(propName).as(propName)
                    .modelAsAggregate());
        }

        return select(queryModels.toArray(new AggregatedResultQueryModel[0])).where().prop("purchaseOrder").eq().extProp("id");
    }

Without @CritOnly properties

In some cases critCondition may be required in the model for a synthetic entity, which does not have @CritOnly properties itself. This would be typically required in the drill-down centres, which rely on selection criteria from the master entity.

In those cases the developer is required to construct model applying critCondition as if there were @CritOnly properties. The values for the query params will then need to be passed via a query enhancer, which will depend on whether @CritOnly or regular properties of the master entity are used.

Using @CritOnly properties of master entity

        @Override
        public ICompleted<DetailEntity> enhanceQuery(final IWhere0<DetailEntity> where, final Optional<CentreContext<DetailEntity, ?>> context) {
            return where.val(1).eq().val(1);
        }

        @Override
        public Map<String, Object> enhanceQueryParams(final Map<String, Object> queryParams, final Optional<CentreContext<MasterEntity, ?>> context) {
            // Need to extract the selection criteria. There two different contexts:
            // 1. selection criteria can be retrieved from master entity if the action was invoked from entity centre.
            // 2. selection criteria can be retrieved from master entity of master entity if the action was invoked from a chart
            final EnhancedCentreEntityQueryCriteria<?, ?> selectionCrit;
            if (decompose(context).ofMasterEntity().selectionCrit() == null) {
                selectionCrit = decompose(context).ofMasterEntity().selectionCritOfMasterEntity();
            } else {
                selectionCrit = decompose(context).ofMasterEntity().selectionCrit();
            }
            final Map<String, Object> parameters = selectionCrit.getParameters();
            queryParams.putAll(parameters);
            return queryParams;
        }

Using regular properties of master entity

        @Override
        public ICompleted<DetailEntity> enhanceQuery(final IWhere0<DetailEntity> where, final Optional<CentreContext<DetailEntity, ?>> context) {
            return where.val(1).eq().val(1);
        }

        @Override
        public Map<String, Object> enhanceQueryParams(final Map<String, Object> queryParams, final Optional<CentreContext<MasterEntity, ?>> context) {
            final EnhancedCentreEntityQueryCriteria<?, ?> selectionCrit = decompose(context).ofMasterEntity().selectionCrit();
            final Map<String, Pair<Object, Object>> paramMap = createParamValuesMap(selectionCrit.getEntityClass(), selectionCrit.getManagedType(), selectionCrit.getCentreDomainTreeMangerAndEnhancer().getFirstTick());
            final Map<String, Object> parameters = buildParametersMap(selectionCrit.getManagedType(), paramMap); 
            queryParams.putAll(parameters);
            final Map<String, Object> critParams = selectionCrit.createQueryProperties().stream().collect(toMap(entry -> entry.getPropertyName(), entry -> entry));
            queryParams.put(queryPropertyParamName("property1Crit"), critParams.get("property1"));
            queryParams.put(queryPropertyParamName("property2Crit"), critParams.get("property1.property2"));
            ...
            return queryParams;
        }

Value matchers

"Cascading" value matchers (i.e. value matcher that depend on values entered in other selection criteria) can now also take advantage of critCondition API to automatically support mnemonics.

In such cases the developer is required to override fillParamsBasedOnContext() and provide the params using one of the following ways:

Using @CritOnly properties

    @Override
    protected Map<String, Object> fillParamsBasedOnContext(final CentreContext<Entity, ?> context) {
        return getContext().getSelectionCrit().getParameters();
    }

Using regular properties

    @Override
    protected Map<String, Object> fillParamsBasedOnContext(final CentreContext<Entity, ?> context) {
        final EnhancedCentreEntityQueryCriteria<?, ?> selectionCrit = context.getSelectionCrit();
        final Map<String, Object> critParams = selectionCrit.createQueryProperties().stream().collect(toMap(entry -> entry.getPropertyName(), entry -> entry));

        final Map<String, Object> queryParams = new HashMap<>();
        queryParams.put(queryPropertyParamName("property1Crit"), critParams.get("property1"));
        queryParams.put(queryPropertyParamName("property2Crit"), critParams.get("property1.property2"));
        ...
        return queryParams;
    }
Clone this wiki locally