Skip to content

Commit

Permalink
Fix associationTargetSearch with multiple target types
Browse files Browse the repository at this point in the history
This is a cherry-pick of ec5e25c,
adapted to the "new" associations. As part of it, the
ShadowAssociationDefinition#createTargetObjectsFilter() method
was improved - as much as currently possible.

Work in progress: there are some limitations remaining.

Related to MID-9561 and MID-9565.
  • Loading branch information
mederly committed Apr 26, 2024
1 parent 20c8abd commit 9155f5c
Show file tree
Hide file tree
Showing 14 changed files with 286 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public class ExpressionConstants {
public static final String VAR_ADMINISTRATIVE_STATUS = "administrativeStatus";
public static final ItemName VAR_ADMINISTRATIVE_STATUS_QNAME = new ItemName(SchemaConstants.NS_C, VAR_ADMINISTRATIVE_STATUS);

public static final String VAR_ASSOCIATION_TARGET_OBJECT_CLASS_DEFINITION = "associationTargetObjectClassDefinition";
public static final String VAR_ASSOCIATION_DEFINITION = "associationDefinition";

/**
* Numeric value describing the current iteration. It starts with 0 and increments on every iteration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ public interface ShadowAssociationDefinition
PrismContainerDefinition<ShadowAssociationValueType>,
ShadowItemDefinition<ShadowAssociation, ShadowAssociationValueType> {

/** Creates a filter that provides all shadows eligible as the target value for this association. */
/**
* Creates a filter that provides all shadows eligible as the target value for this association.
*
* FIXME resolve limitations:
* - single object class is allowed for given association
* - if multiple object types are there, then the filter is for the whole class
* - if type type is the default object type, then it's used as such (even if the whole OC should be returned)
*/
ObjectFilter createTargetObjectsFilter();

ResourceObjectDefinition getTargetObjectDefinition();
Expand All @@ -47,4 +54,8 @@ ShadowAssociationValue instantiateFromIdentifierRealValue(@NotNull QName identif
ShadowAssociationTypeDefinitionNew getAssociationTypeDefinition();

boolean isEntitlement();

default String getResourceOid() {
return getTargetObjectDefinition().getResourceOid();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
import static com.evolveum.midpoint.util.MiscUtil.stateNonNull;

import java.io.Serial;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.PrismContainerDefinition.PrismContainerDefinitionMutator;
import com.evolveum.midpoint.prism.annotation.ItemDiagramSpecification;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.AssociationsCapabilityType;
import com.google.common.base.Preconditions;
import org.jetbrains.annotations.NotNull;
Expand All @@ -29,8 +30,6 @@
import com.evolveum.midpoint.prism.path.ItemName;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.OrFilter;
import com.evolveum.midpoint.prism.query.builder.S_FilterEntryOrEmpty;
import com.evolveum.midpoint.prism.util.CloneUtil;
import com.evolveum.midpoint.schema.config.ResourceObjectAssociationConfigItem;
import com.evolveum.midpoint.util.QNameUtil;
Expand Down Expand Up @@ -427,23 +426,29 @@ public String getHumanReadableDescription() {
}

public @NotNull ObjectFilter createTargetObjectsFilter() {
var resourceOid = stateNonNull(getTargetObjectDefinition().getResourceOid(), "No resource OID in %s", this);
var objectDefinitions = getAssociationClassDefinition().getObjectObjectDefinitions();
assertCheck(!objectDefinitions.isEmpty(), "No object type definitions (already checked)");
S_FilterEntryOrEmpty atomicFilter = PrismContext.get().queryFor(ShadowType.class);
List<ObjectFilter> orFilterClauses = new ArrayList<>();
objectDefinitions.stream()
.map(def -> def.getTypeIdentification())
.forEach(typeId -> orFilterClauses.add(
atomicFilter
.item(ShadowType.F_KIND).eq(typeId.getKind()) // FIXME treat also class definitions
.and().item(ShadowType.F_INTENT).eq(typeId.getIntent())
.buildFilter()));
OrFilter intentFilter = PrismContext.get().queryFactory().createOr(orFilterClauses);

var resourceOid = stateNonNull(getTargetObjectDefinition().getResourceOid(), "No resource OID in %s", this);
return atomicFilter.item(ShadowType.F_RESOURCE_REF).ref(resourceOid, ResourceType.COMPLEX_TYPE)
.and().filter(intentFilter)
.buildFilter();
var firstObjectType = objectDefinitions.iterator().next().getTypeIdentification();
if (objectDefinitions.size() > 1 || firstObjectType == null) { // FIXME what if it's "default type def" for given class?
var objectClassNames = objectDefinitions.stream()
.map(def -> def.getObjectClassName())
.collect(Collectors.toSet());
var objectClassName = MiscUtil.extractSingletonRequired(
objectClassNames,
() -> new UnsupportedOperationException("Multiple object class names in " + this),
() -> new IllegalStateException("No object class names in " + this));
return PrismContext.get().queryFor(ShadowType.class)
.item(ShadowType.F_RESOURCE_REF).ref(resourceOid, ResourceType.COMPLEX_TYPE)
.and().item(ShadowType.F_OBJECT_CLASS).eq(objectClassName)
.buildFilter();
} else {
return PrismContext.get().queryFor(ShadowType.class)
.item(ShadowType.F_RESOURCE_REF).ref(resourceOid, ResourceType.COMPLEX_TYPE)
.and().item(ShadowType.F_KIND).eq(firstObjectType.getKind())
.and().item(ShadowType.F_INTENT).eq(firstObjectType.getIntent())
.buildFilter();
}
}

public @Nullable ShadowAssociationClassSimulationDefinition getSimulationDefinition() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
package com.evolveum.midpoint.model.common.expression.evaluator;

import static com.evolveum.midpoint.model.common.expression.evaluator.AssociationRelatedEvaluatorUtil.getAssociationDefinition;
import static com.evolveum.midpoint.schema.GetOperationOptions.createNoFetchReadOnlyCollection;
import static com.evolveum.midpoint.util.MiscUtil.configNonNull;

Expand Down Expand Up @@ -33,12 +34,11 @@
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.expression.TypedValue;
import com.evolveum.midpoint.schema.processor.ResourceObjectDefinition;
import com.evolveum.midpoint.schema.processor.ShadowAssociation;
import com.evolveum.midpoint.schema.processor.ShadowAssociationDefinition;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.AbstractShadow;
import com.evolveum.midpoint.schema.util.FocusTypeUtil;
import com.evolveum.midpoint.schema.processor.ShadowAssociation;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
Expand Down Expand Up @@ -87,13 +87,7 @@ public PrismValueDeltaSetTriple<PrismContainerValue<ShadowAssociationValueType>>
AbstractRoleType thisRole = getRelevantRole(context);
LOGGER.trace("Evaluating association from link {} on: {}", expressionEvaluatorBean.getDescription(), thisRole);

//noinspection unchecked
var rAssocTargetDefTypedValue = (TypedValue<ResourceObjectDefinition>)
context.getVariables().get(ExpressionConstants.VAR_ASSOCIATION_TARGET_OBJECT_CLASS_DEFINITION);
if (rAssocTargetDefTypedValue == null || rAssocTargetDefTypedValue.getValue() == null) {
throw new ExpressionEvaluationException("No association target object definition variable in "+desc+"; the expression may be used in a wrong place. It is only supposed to create an association.");
}
ResourceObjectDefinition associationTargetDef = (ResourceObjectDefinition) rAssocTargetDefTypedValue.getValue();
var associationDefinition = getAssociationDefinition(context);

ShadowDiscriminatorType projectionDiscriminator = expressionEvaluatorBean.getProjectionDiscriminator();
if (projectionDiscriminator == null) {
Expand All @@ -110,7 +104,7 @@ public PrismValueDeltaSetTriple<PrismContainerValue<ShadowAssociationValueType>>

var outputAssociation = createAssociationFromMatchingValues(
candidateShadowOidList,
associationTargetDef.getResourceOid(),
associationDefinition.getResourceOid(),
configNonNull(projectionDiscriminator.getKind(), "No kind in projectionDiscriminator in %s", desc),
projectionDiscriminator.getIntent(),
context,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2010-2024 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.model.common.expression.evaluator;

import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.processor.ShadowAssociationDefinition;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;

import org.jetbrains.annotations.NotNull;

class AssociationRelatedEvaluatorUtil {

static @NotNull ShadowAssociationDefinition getAssociationDefinition(ExpressionEvaluationContext context)
throws ExpressionEvaluationException {
var associationDefinitionTypedValue = context.getVariables().get(ExpressionConstants.VAR_ASSOCIATION_DEFINITION);
var associationDefinition = associationDefinitionTypedValue != null ?
(ShadowAssociationDefinition) associationDefinitionTypedValue.getValue() : null;
if (associationDefinition == null) {
throw new ExpressionEvaluationException(
("No association definition variable in %s; the expression may be used in a wrong place. "
+ "It is only supposed to create an association.").formatted(
context.getContextDescription()));
}
return associationDefinition;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,27 @@
*/
package com.evolveum.midpoint.model.common.expression.evaluator;

import static com.evolveum.midpoint.model.common.expression.evaluator.AssociationRelatedEvaluatorUtil.getAssociationDefinition;

import java.util.List;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.model.common.expression.evaluator.transformation.ValueTransformationContext;
import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.common.LocalizationService;
import com.evolveum.midpoint.model.common.expression.evaluator.caching.AssociationSearchExpressionEvaluatorCache;
import com.evolveum.midpoint.model.common.expression.evaluator.transformation.ValueTransformationContext;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ItemDeltaCollectionsUtil;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.repo.common.ObjectResolver;
import com.evolveum.midpoint.schema.GetOperationOptionsBuilder;
import com.evolveum.midpoint.schema.cache.CacheType;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.expression.TypedValue;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.processor.ResourceObjectTypeDefinition;
import com.evolveum.midpoint.schema.processor.ShadowAssociationDefinition;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
Expand All @@ -40,6 +36,7 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.SearchObjectExpressionEvaluatorType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAssociationValueType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType;

/**
* Creates an association (or associations) based on specified condition for the associated object.
Expand Down Expand Up @@ -104,37 +101,25 @@ protected QName getDefaultTargetType() {
return newAssociationValue.clone(); // It needs to be parent-less when included in the output triple
}

private ResourceObjectTypeDefinition associationTargetDef()
@Override
protected ObjectQuery extendQuery(ObjectQuery query)
throws ExpressionEvaluationException {
@SuppressWarnings("unchecked")
var rAssocTargetDefTypedValue = (TypedValue<ResourceObjectTypeDefinition>)
this.vtCtx.getVariablesMap().get(ExpressionConstants.VAR_ASSOCIATION_TARGET_OBJECT_CLASS_DEFINITION);
if (rAssocTargetDefTypedValue == null || rAssocTargetDefTypedValue.getValue() == null) {
throw new ExpressionEvaluationException(
String.format("No association target object definition variable in %s; the expression may be used in"
+ " a wrong place. It is only supposed to create an association.",
vtCtx));
}
return (ResourceObjectTypeDefinition) rAssocTargetDefTypedValue.getValue();
}

@Override
protected ObjectQuery extendQuery(ObjectQuery query) throws ExpressionEvaluationException {
var rAssocTargetDef = associationTargetDef();
ObjectFilter coordinatesFilter = prismContext.queryFor(ShadowType.class)
.item(ShadowType.F_RESOURCE_REF).ref(rAssocTargetDef.getResourceOid())
.and().item(ShadowType.F_KIND).eq(rAssocTargetDef.getKind())
.and().item(ShadowType.F_INTENT).eq(rAssocTargetDef.getIntent())
.buildFilter();
var associationDefinition = getAssociationDefinition(vtCtx.getExpressionEvaluationContext());
query.setFilter(
prismContext.queryFactory()
.createAnd(coordinatesFilter, query.getFilter()));
.createAnd(
associationDefinition.createTargetObjectsFilter(),
query.getFilter()));
return query;
}

@Override
protected ObjectQuery createRawQuery(SearchFilterType filter) throws SchemaException, ExpressionEvaluationException {
var concreteShadowDef = associationTargetDef().getPrismObjectDefinition();
var concreteShadowDef =
getAssociationDefinition(vtCtx.getExpressionEvaluationContext())
.getTargetObjectDefinition()
.getPrismObjectDefinition();
var objFilter = prismContext.getQueryConverter().createObjectFilter(concreteShadowDef, filter);
return prismContext.queryFactory().createQuery(objFilter);
}
Expand All @@ -149,6 +134,7 @@ protected void extendOptions(GetOperationOptionsBuilder builder, boolean searchO

@Override
protected boolean isAcceptable(@NotNull PrismObject<ShadowType> object) {
// FIXME do additional filtering for the targets (if there are multiple types for them)
return ShadowUtil.isNotDead(object.asObjectable());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import com.evolveum.midpoint.prism.OriginType;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.schema.config.MappingConfigItem;
import com.evolveum.midpoint.schema.processor.ResourceObjectDefinition;
import com.evolveum.midpoint.schema.processor.ShadowAssociationDefinition;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingKindType;
Expand Down Expand Up @@ -51,7 +50,7 @@ String getLifecycleState() {
}

@Override
ResourceObjectDefinition getAssociationTargetObjectDefinition() {
return itemDefinition.getTargetObjectDefinition();
ShadowAssociationDefinition getAssociationDefinition() {
return itemDefinition;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@

package com.evolveum.midpoint.model.impl.lens.construction;

import java.util.Collection;
import java.util.Collections;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.OriginType;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.schema.config.MappingConfigItem;
import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition;
import com.evolveum.midpoint.schema.processor.ResourceObjectTypeDefinition;
import com.evolveum.midpoint.schema.processor.ShadowAssociationDefinition;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingKindType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
Expand Down Expand Up @@ -54,7 +50,7 @@ String getLifecycleState() {
}

@Override
ResourceObjectTypeDefinition getAssociationTargetObjectDefinition() {
ShadowAssociationDefinition getAssociationDefinition() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import com.evolveum.midpoint.repo.common.expression.ConfigurableValuePolicySupplier;
import com.evolveum.midpoint.schema.config.MappingConfigItem;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.processor.ResourceObjectDefinition;
import com.evolveum.midpoint.schema.processor.ShadowAssociationDefinition;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.SimulationUtil;
import com.evolveum.midpoint.task.api.Task;
Expand Down Expand Up @@ -152,7 +152,7 @@ private MappingImpl<V, D> evaluateMapping()

mappingBuilder = construction.initializeMappingBuilder(
mappingBuilder, itemPath, itemName, itemDefinition,
getAssociationTargetObjectDefinition(), constructionEvaluation.task);
getAssociationDefinition(), constructionEvaluation.task);

if (mappingBuilder == null) {
return null;
Expand Down Expand Up @@ -192,7 +192,7 @@ private MappingImpl<V, D> evaluateMapping()
return mapping;
}

abstract ResourceObjectDefinition getAssociationTargetObjectDefinition();
abstract ShadowAssociationDefinition getAssociationDefinition();

private ConfigurableValuePolicySupplier createValuePolicySupplier() {
return new ConfigurableValuePolicySupplier() {
Expand Down

0 comments on commit 9155f5c

Please sign in to comment.