EQL critCondition operator - fieldenms/tg GitHub Wiki
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.
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.
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.
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 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");
}
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.
@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;
}
@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;
}
"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:
@Override
protected Map<String, Object> fillParamsBasedOnContext(final CentreContext<Entity, ?> context) {
return getContext().getSelectionCrit().getParameters();
}
@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;
}