/* * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.model.impl.lens; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.function.Consumer; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import com.evolveum.midpoint.common.ActivationComputer; import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; import com.evolveum.midpoint.model.api.context.EvaluationOrder; import com.evolveum.midpoint.model.api.util.DeputyUtils; import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.model.common.expression.ItemDeltaItem; import com.evolveum.midpoint.model.common.expression.ObjectDeltaObject; import com.evolveum.midpoint.model.common.mapping.Mapping; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.model.impl.lens.projector.MappingEvaluator; import com.evolveum.midpoint.model.impl.util.Utils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.PlusMinusZero; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.marshaller.QueryConvertor; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.FocusTypeUtil; import com.evolveum.midpoint.schema.util.ObjectResolver; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.security.api.Authorization; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * An engine that creates EvaluatedAssignment from an assignment IDI. It collects induced roles, constructions, * authorizations, policy rules, and so on. * * @author semancik */ public class AssignmentEvaluator<F extends FocusType> { private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); private static final Trace LOGGER = TraceManager.getTrace(AssignmentEvaluator.class); // "Configuration parameters" private final RepositoryService repository; private final ObjectDeltaObject<F> focusOdo; private final LensContext<F> lensContext; private final String channel; private final ObjectResolver objectResolver; private final SystemObjectCache systemObjectCache; private final PrismContext prismContext; private final MappingFactory mappingFactory; private final ActivationComputer activationComputer; private final XMLGregorianCalendar now; private final boolean loginMode; // restricted mode, evaluating only authorizations and gui config (TODO name) private final PrismObject<SystemConfigurationType> systemConfiguration; private final MappingEvaluator mappingEvaluator; private AssignmentEvaluator(Builder<F> builder) { repository = builder.repository; focusOdo = builder.focusOdo; lensContext = builder.lensContext; channel = builder.channel; objectResolver = builder.objectResolver; systemObjectCache = builder.systemObjectCache; prismContext = builder.prismContext; mappingFactory = builder.mappingFactory; activationComputer = builder.activationComputer; now = builder.now; loginMode = builder.loginMode; systemConfiguration = builder.systemConfiguration; mappingEvaluator = builder.mappingEvaluator; } public RepositoryService getRepository() { return repository; } @SuppressWarnings("unused") public ObjectDeltaObject<F> getFocusOdo() { return focusOdo; } public LensContext<F> getLensContext() { return lensContext; } public String getChannel() { return channel; } public ObjectResolver getObjectResolver() { return objectResolver; } public SystemObjectCache getSystemObjectCache() { return systemObjectCache; } public PrismContext getPrismContext() { return prismContext; } public MappingFactory getMappingFactory() { return mappingFactory; } public ActivationComputer getActivationComputer() { return activationComputer; } public XMLGregorianCalendar getNow() { return now; } @SuppressWarnings("unused") public boolean isLoginMode() { return loginMode; } public PrismObject<SystemConfigurationType> getSystemConfiguration() { return systemConfiguration; } @SuppressWarnings("unused") public MappingEvaluator getMappingEvaluator() { return mappingEvaluator; } // This is to reduce the number of parameters passed between methods in this class. // Moreover, it highlights the fact that identity of objects referenced here is fixed for any invocation of the evaluate() method. // (There is single EvaluationContext instance for any call to evaluate().) private class EvaluationContext { private final EvaluatedAssignmentImpl<F> evalAssignment; private final AssignmentPathImpl assignmentPath; private final boolean evaluateOld; private final Task task; private final OperationResult result; public EvaluationContext(EvaluatedAssignmentImpl<F> evalAssignment, AssignmentPathImpl assignmentPath, boolean evaluateOld, Task task, OperationResult result) { this.evalAssignment = evalAssignment; this.assignmentPath = assignmentPath; this.evaluateOld = evaluateOld; this.task = task; this.result = result; } } public EvaluatedAssignmentImpl<F> evaluate( ItemDeltaItem<PrismContainerValue<AssignmentType>,PrismContainerDefinition<AssignmentType>> assignmentIdi, boolean evaluateOld, ObjectType source, String sourceDescription, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { assertSourceNotNull(source, assignmentIdi); EvaluationContext ctx = new EvaluationContext( new EvaluatedAssignmentImpl<>(assignmentIdi), new AssignmentPathImpl(), evaluateOld, task, result); AssignmentPathSegmentImpl segment = new AssignmentPathSegmentImpl(source, sourceDescription, assignmentIdi, true); segment.setEvaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)); segment.setEvaluationOrderForTarget(EvaluationOrderImpl.ZERO); segment.setValidityOverride(true); segment.setPathToSourceValid(true); segment.setProcessMembership(true); segment.setRelation(getRelation(getAssignmentType(segment, ctx))); evaluateFromSegment(segment, PlusMinusZero.ZERO, ctx); LOGGER.trace("Assignment evaluation finished:\n{}", ctx.evalAssignment.debugDumpLazily()); return ctx.evalAssignment; } private EvaluationOrder getInitialEvaluationOrder( ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> assignmentIdi, EvaluationContext ctx) { AssignmentType assignmentType = LensUtil.getAssignmentType(assignmentIdi, ctx.evaluateOld); return EvaluationOrderImpl.ZERO.advance(getRelation(assignmentType)); } /** * @param mode * * Where to put constructions and target roles/orgs/services (PLUS/MINUS/ZERO/null; null means "nowhere"). * * This depends on the status of conditions. E.g. if condition evaluates 'false -> true' (i.e. in old * state the value is false, and in new state the value is true), then the mode is PLUS. * * This "triples algebra" is based on the following two methods: * * @see ExpressionUtil#computeConditionResultMode(boolean, boolean) - Based on condition values "old+new" determines * into what set (PLUS/MINUS/ZERO/none) should the result be placed. Irrespective of what is the current mode. So, * in order to determine "real" place where to put it (i.e. the new mode) the following method is used. * * @see PlusMinusZero#compute(PlusMinusZero, PlusMinusZero) - Takes original mode and the mode from recent condition * and determines the new mode (commutatively): * * PLUS + PLUS/ZERO = PLUS * MINUS + MINUS/ZERO = MINUS * ZERO + ZERO = ZERO * PLUS + MINUS = none * * This is quite straightforward, although the last rule deserves a note. If we have an assignment that was originally * disabled and becomes enabled by the current delta (i.e. PLUS), and that assignment contains an inducement that was originally * enabled and becomes disabled (i.e. MINUS), the result is that the (e.g.) constructions within the inducement were not * present in the old state (because assignment was disabled) and are not present in the new state (because inducement is disabled). * * Note: this parameter could be perhaps renamed to "tripleMode" or "destination" or something like that. */ private void evaluateFromSegment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, EvaluationContext ctx) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { if (LOGGER.isTraceEnabled()) { LOGGER.trace("*** Evaluate from segment: {}", segment); LOGGER.trace("*** Evaluation order - standard: {}, matching: {}", segment.getEvaluationOrder(), segment.isMatchingOrder()); LOGGER.trace("*** Evaluation order - for target: {}, matching: {}", segment.getEvaluationOrderForTarget(), segment.isMatchingOrderForTarget()); LOGGER.trace("*** mode: {}, process membership: {}", mode, segment.isProcessMembership()); LOGGER.trace("*** path to source valid: {}, validity override: {}", segment.isPathToSourceValid(), segment.isValidityOverride()); } assertSourceNotNull(segment.source, ctx.evalAssignment); checkSchema(segment, ctx); ctx.assignmentPath.add(segment); if (LOGGER.isTraceEnabled()) { LOGGER.trace("*** Path (with current segment already added):\n{}", ctx.assignmentPath.debugDump()); } boolean evaluateContent = true; AssignmentType assignmentType = getAssignmentType(segment, ctx); MappingType assignmentCondition = assignmentType.getCondition(); if (assignmentCondition != null) { AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> conditionTriple = evaluateCondition(assignmentCondition, assignmentType, segment.source, assignmentPathVariables, ctx); boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); if (modeFromCondition == null) { // removed "|| (condMode == PlusMinusZero.ZERO && !condNew)" as it is always false if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: {})", FocusTypeUtil.dumpAssignment(assignmentType), condOld, condNew, null); } evaluateContent = false; } else { PlusMinusZero origMode = mode; mode = PlusMinusZero.compute(mode, modeFromCondition); LOGGER.trace("Evaluated condition in assignment {} -> {}: {} + {} = {}", condOld, condNew, origMode, modeFromCondition, mode); } } boolean isValid = evaluateContent && evaluateSegmentContent(segment, mode, ctx); ctx.assignmentPath.removeLast(segment); if (ctx.assignmentPath.isEmpty()) { // direct assignment ctx.evalAssignment.setValid(isValid); } } // "content" means "payload + targets" here private <O extends ObjectType> boolean evaluateSegmentContent(AssignmentPathSegmentImpl segment, PlusMinusZero mode, EvaluationContext ctx) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, PolicyViolationException { assert ctx.assignmentPath.last() == segment; final boolean isDirectAssignment = ctx.assignmentPath.size() == 1; AssignmentType assignmentType = getAssignmentType(segment, ctx); boolean isAssignmentValid = LensUtil.isAssignmentValid(focusOdo.getNewObject().asObjectable(), assignmentType, now, activationComputer); if (isAssignmentValid || segment.isValidityOverride()) { // Note: validityOverride is currently the same as "isDirectAssignment" - which is very probably OK. // Direct assignments are visited even if they are not valid (i.e. effectively disabled). // It is because we need to collect e.g. assignment policy rules for them. // Also because we could have deltas that disable/enable these assignments. boolean reallyValid = segment.isPathToSourceValid() && isAssignmentValid; if (!loginMode && segment.isMatchingOrder()) { if (assignmentType.getConstruction() != null) { collectConstruction(segment, mode, reallyValid, ctx); } if (assignmentType.getPersonaConstruction() != null) { collectPersonaConstruction(segment, mode, reallyValid, ctx); } if (assignmentType.getFocusMappings() != null) { // Here we ignore "reallyValid". It is OK, because reallyValid can be false here only when // evaluating direct assignments; and invalid ones are marked as such via EvaluatedAssignment.isValid. // (This is currently ignored by downstream processing, but that's another story. Will be fixed soon.) if (isNonNegative(mode)) { evaluateFocusMappings(segment, ctx); } } } if (assignmentType.getPolicyRule() != null && !loginMode) { // We can ignore "reallyValid" for the same reason as for focus mappings. if (isNonNegative(mode)) { if (segment.isMatchingOrder()) { collectPolicyRule(true, segment, ctx); } if (segment.isMatchingOrderForTarget()) { collectPolicyRule(false, segment, ctx); } } } if (assignmentType.getTarget() != null || assignmentType.getTargetRef() != null) { List<PrismObject<O>> targets = getTargets(segment, ctx); LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, assignmentType.getId(), targets); if (isDirectAssignment) { setEvaluatedAssignmentTarget(segment, targets, ctx); } QName relation = getRelation(assignmentType); for (PrismObject<O> target : targets) { checkCycle(segment, target, ctx); if (isDelegationToNonDelegableTarget(assignmentType, target, ctx)) { continue; } evaluateSegmentTarget(segment, mode, reallyValid, (FocusType)target.asObjectable(), relation, ctx); } } } else { LOGGER.trace("Skipping evaluation of assignment {} because it is not valid", assignmentType); } return isAssignmentValid; } private <O extends ObjectType> boolean isDelegationToNonDelegableTarget(AssignmentType assignmentType, @NotNull PrismObject<O> target, EvaluationContext ctx) { AssignmentPathSegment previousSegment = ctx.assignmentPath.beforeLast(1); if (previousSegment == null || !previousSegment.isDelegation() || !target.canRepresent(AbstractRoleType.class)) { return false; } if (!Boolean.TRUE.equals(((AbstractRoleType)target.asObjectable()).isDelegable())) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping evaluation of {} because it delegates to a non-delegable target {}", FocusTypeUtil.dumpAssignment(assignmentType), target); } return true; } else { return false; } } private <O extends ObjectType> void checkCycle(AssignmentPathSegmentImpl segment, @NotNull PrismObject<O> target, EvaluationContext ctx) throws PolicyViolationException { if (target.getOid().equals(segment.source.getOid())) { throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); } // removed condition "&& segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())" // as currently it is always true // TODO reconsider this if (ctx.assignmentPath.containsTarget(target.asObjectable())) { LOGGER.debug("Role cycle detected for target {} in {}", ObjectTypeUtil.toShortString(target), ctx.assignmentPath); throw new PolicyViolationException("Attempt to assign "+target+" creates a role cycle"); } } private void collectConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { assertSourceNotNull(segment.source, ctx.evalAssignment); // TODO why "assignmentTypeNew" when we retrieve old one in some situations? AssignmentType assignmentTypeNew = LensUtil.getAssignmentType(segment.getAssignmentIdi(), ctx.evaluateOld); ConstructionType constructionType = assignmentTypeNew.getConstruction(); LOGGER.trace("Preparing construction '{}' in {}", constructionType.getDescription(), segment.source); Construction<F> construction = new Construction<>(constructionType, segment.source); // We have to clone here as the path is constantly changing during evaluation construction.setAssignmentPath(ctx.assignmentPath.clone()); construction.setFocusOdo(focusOdo); construction.setLensContext(lensContext); construction.setObjectResolver(objectResolver); construction.setPrismContext(prismContext); construction.setMappingFactory(mappingFactory); construction.setMappingEvaluator(mappingEvaluator); construction.setOriginType(OriginType.ASSIGNMENTS); construction.setChannel(channel); construction.setOrderOneObject(segment.getOrderOneObject()); construction.setValid(isValid); // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. if (mode == null) { return; // null mode (i.e. plus + minus) means 'ignore the payload' } ctx.evalAssignment.addConstruction(construction, mode); } private void collectPersonaConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { assertSourceNotNull(segment.source, ctx.evalAssignment); if (mode == null) { return; // null mode (i.e. plus + minus) means 'ignore the payload' } // TODO why "assignmentTypeNew" when we retrieve old one in some situations? AssignmentType assignmentTypeNew = LensUtil.getAssignmentType(segment.getAssignmentIdi(), ctx.evaluateOld); PersonaConstructionType constructionType = assignmentTypeNew.getPersonaConstruction(); LOGGER.trace("Preparing persona construction '{}' in {}", constructionType.getDescription(), segment.source); PersonaConstruction<F> construction = new PersonaConstruction<>(constructionType, segment.source); // We have to clone here as the path is constantly changing during evaluation construction.setAssignmentPath(ctx.assignmentPath.clone()); construction.setFocusOdo(focusOdo); construction.setLensContext(lensContext); construction.setObjectResolver(objectResolver); construction.setPrismContext(prismContext); construction.setOriginType(OriginType.ASSIGNMENTS); construction.setChannel(channel); construction.setValid(isValid); ctx.evalAssignment.addPersonaConstruction(construction, mode); } private void evaluateFocusMappings(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { assertSourceNotNull(segment.source, ctx.evalAssignment); // TODO why "assignmentTypeNew" when we consider also old values? AssignmentType assignmentTypeNew = LensUtil.getAssignmentType(segment.getAssignmentIdi(), ctx.evaluateOld); MappingsType mappingsType = assignmentTypeNew.getFocusMappings(); LOGGER.trace("Evaluate focus mappings '{}' in {} ({} mappings)", mappingsType.getDescription(), segment.source, mappingsType.getMapping().size()); AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); for (MappingType mappingType: mappingsType.getMapping()) { Mapping mapping = mappingEvaluator.createFocusMapping(mappingFactory, lensContext, mappingType, segment.source, focusOdo, assignmentPathVariables, systemConfiguration, now, segment.sourceDescription, ctx.task, ctx.result); if (mapping == null) { continue; } // TODO: time constratins? mappingEvaluator.evaluateMapping(mapping, lensContext, ctx.task, ctx.result); ctx.evalAssignment.addFocusMapping(mapping); } } private void collectPolicyRule(boolean focusRule, AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException { assertSourceNotNull(segment.source, ctx.evalAssignment); AssignmentType assignmentTypeNew = LensUtil.getAssignmentType(segment.getAssignmentIdi(), ctx.evaluateOld); PolicyRuleType policyRuleType = assignmentTypeNew.getPolicyRule(); LOGGER.trace("Collecting {} policy rule '{}' in {}", focusRule ? "focus" : "target", policyRuleType.getName(), segment.source); EvaluatedPolicyRuleImpl policyRule = new EvaluatedPolicyRuleImpl(policyRuleType, ctx.assignmentPath.clone()); if (focusRule) { ctx.evalAssignment.addFocusPolicyRule(policyRule); } else { if (appliesDirectly(ctx.assignmentPath)) { ctx.evalAssignment.addThisTargetPolicyRule(policyRule); } else { ctx.evalAssignment.addOtherTargetPolicyRule(policyRule); } } } private boolean appliesDirectly(AssignmentPathImpl assignmentPath) { assert !assignmentPath.isEmpty(); // TODO what about deputy relation which does not increase summaryOrder? long zeroOrderCount = assignmentPath.getSegments().stream() .filter(seg -> seg.getEvaluationOrderForTarget().getSummaryOrder() == 0) .count(); return zeroOrderCount == 1; } @NotNull private <O extends ObjectType> List<PrismObject<O>> getTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException, ExpressionEvaluationException { AssignmentType assignmentType = getAssignmentType(segment, ctx); if (assignmentType.getTarget() != null) { return Collections.singletonList((PrismObject<O>) assignmentType.getTarget().asPrismObject()); } else if (assignmentType.getTargetRef() != null) { try { return resolveTargets(segment, ctx); } catch (ObjectNotFoundException ex) { // Do not throw an exception. We don't have referential integrity. Therefore if a role is deleted then throwing // an exception would prohibit any operations with the users that have the role, including removal of the reference. // The failure is recorded in the result and we will log it. It should be enough. LOGGER.error(ex.getMessage()+" in assignment target reference in "+segment.sourceDescription,ex); // For OrgType references we trigger the reconciliation (see MID-2242) ctx.evalAssignment.setForceRecon(true); return Collections.emptyList(); } } else { throw new IllegalStateException("Both target and targetRef are null. We should not be here. Assignment: " + assignmentType); } } @NotNull private <O extends ObjectType> List<PrismObject<O>> resolveTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException { AssignmentType assignmentType = getAssignmentType(segment, ctx); ObjectReferenceType targetRef = assignmentType.getTargetRef(); String oid = targetRef.getOid(); // Target is referenced, need to fetch it Class<O> targetClass; if (targetRef.getType() != null) { targetClass = prismContext.getSchemaRegistry().determineCompileTimeClass(targetRef.getType()); if (targetClass == null) { throw new SchemaException("Cannot determine type from " + targetRef.getType() + " in target reference in " + assignmentType + " in " + segment.sourceDescription); } } else { throw new SchemaException("Missing type in target reference in " + assignmentType + " in " + segment.sourceDescription); } if (oid == null) { LOGGER.trace("Resolving dynamic target ref"); if (targetRef.getFilter() == null){ throw new SchemaException("The OID and filter are both null in assignment targetRef in "+segment.source); } return resolveTargetsFromFilter(targetClass, targetRef.getFilter(), segment, ctx); } else { LOGGER.trace("Resolving target {}:{} from repository", targetClass.getSimpleName(), oid); PrismObject<O> target; try { target = repository.getObject(targetClass, oid, null, ctx.result); } catch (SchemaException e) { throw new SchemaException(e.getMessage() + " in " + segment.sourceDescription, e); } // Not handling object not found exception here. Caller will handle that. if (target == null) { throw new IllegalArgumentException("Got null target from repository, oid:"+oid+", class:"+targetClass+" (should not happen, probably a bug) in "+segment.sourceDescription); } return Collections.singletonList(target); } } @NotNull private <O extends ObjectType> List<PrismObject<O>> resolveTargetsFromFilter(Class<O> targetClass, SearchFilterType filter, AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException{ ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(lensContext, null, ctx.task, ctx.result)); try { PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache.getSystemConfiguration(ctx.result); ExpressionVariables variables = Utils.getDefaultExpressionVariables(segment.source, null, null, systemConfiguration.asObjectable()); variables.addVariableDefinition(ExpressionConstants.VAR_SOURCE, segment.getOrderOneObject()); AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); if (assignmentPathVariables != null) { Utils.addAssignmentPathVariables(assignmentPathVariables, variables); } ObjectFilter origFilter = QueryConvertor.parseFilter(filter, targetClass, prismContext); ObjectFilter evaluatedFilter = ExpressionUtil.evaluateFilterExpressions(origFilter, variables, getMappingFactory().getExpressionFactory(), prismContext, " evaluating resource filter expression ", ctx.task, ctx.result); if (evaluatedFilter == null) { throw new SchemaException("The OID is null and filter could not be evaluated in assignment targetRef in "+segment.source); } return repository.searchObjects(targetClass, ObjectQuery.createObjectQuery(evaluatedFilter), null, ctx.result); // we don't check for no targets here; as we don't care for referential integrity } finally { ModelExpressionThreadLocalHolder.popExpressionEnvironment(); } } private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, FocusType targetType, QName relation, EvaluationContext ctx) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { assertSourceNotNull(segment.source, ctx.evalAssignment); assert ctx.assignmentPath.last() == segment; segment.setTarget(targetType); segment.setRelation(relation); // probably not needed if (LOGGER.isTraceEnabled()) { LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDump(1)); } checkRelationWithTarget(segment, targetType, relation); if (!LensUtil.isFocusValid(targetType, now, activationComputer)) { LOGGER.trace("Skipping evaluation of {} because it is not valid", targetType); return; } if (targetType instanceof AbstractRoleType) { MappingType roleCondition = ((AbstractRoleType)targetType).getCondition(); if (roleCondition != null) { AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> conditionTriple = evaluateCondition(roleCondition, null, segment.source, assignmentPathVariables, ctx); boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); if (modeFromCondition == null) { // removed "|| (condMode == PlusMinusZero.ZERO && !condNew)" because it's always false LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: null)", targetType, condOld, condNew); return; } PlusMinusZero origMode = mode; mode = PlusMinusZero.compute(mode, modeFromCondition); LOGGER.trace("Evaluated condition in {}: {} -> {}: {} + {} = {}", targetType, condOld, condNew, origMode, modeFromCondition, mode); } } EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( targetType.asPrismObject(), segment.isMatchingOrder(), // evaluateConstructions: exact meaning of this is to be revised ctx.assignmentPath.clone(), segment.getAssignment(), isValid); ctx.evalAssignment.addRole(evalAssignmentTarget, mode); if ((isNonNegative(mode)) && segment.isProcessMembership()) { collectMembership(targetType, relation, ctx); } // We continue evaluation even if the relation is non-membership and non-delegation. // Computation of isMatchingOrder will ensure that we won't collect any unwanted content. if (targetType instanceof AbstractRoleType) { for (AssignmentType roleInducement : ((AbstractRoleType)targetType).getInducement()) { evaluateInducement(segment, mode, isValid, ctx, targetType, roleInducement); } } for (AssignmentType roleAssignment : targetType.getAssignment()) { evaluateAssignment(segment, mode, isValid, ctx, targetType, relation, roleAssignment); } //boolean matchesOrder = AssignmentPathSegmentImpl.computeMatchingOrder(segment.getEvaluationOrder(), 1, Collections.emptyList()); if (segment.isMatchingOrder() && targetType instanceof AbstractRoleType && isNonNegative(mode)) { for (AuthorizationType authorizationType: ((AbstractRoleType)targetType).getAuthorization()) { Authorization authorization = createAuthorization(authorizationType, targetType.toString()); if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { ctx.evalAssignment.addAuthorization(authorization); } } AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) targetType).getAdminGuiConfiguration(); if (adminGuiConfiguration != null && !ctx.evalAssignment.getAdminGuiConfigurations().contains(adminGuiConfiguration)) { ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); } PolicyConstraintsType policyConstraints = ((AbstractRoleType)targetType).getPolicyConstraints(); if (policyConstraints != null) { ctx.evalAssignment.addLegacyPolicyConstraints(policyConstraints, ctx.assignmentPath.clone(), targetType); } } LOGGER.trace("Evaluating segment target DONE for {}", segment); } // TODO revisit this private ObjectType getOrderOneObject(AssignmentPathSegmentImpl segment) { EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); if (evaluationOrder.getSummaryOrder() == 1) { return segment.getTarget(); } else { if (segment.getSource() != null) { // should be always the case... return segment.getSource(); } else { return segment.getTarget(); } } } private void evaluateAssignment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, FocusType targetType, QName relation, AssignmentType roleAssignment) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { ObjectType orderOneObject = getOrderOneObject(segment); if (ObjectTypeUtil.isDelegationRelation(relation)) { // We have to handle assignments as though they were inducements here. if (!isInducementAllowedByLimitations(segment, roleAssignment)) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", FocusTypeUtil.dumpAssignment(roleAssignment)); } return; } } QName nextRelation = getRelation(roleAssignment); EvaluationOrder nextEvaluationOrder = segment.getEvaluationOrder().advance(nextRelation); EvaluationOrder nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().advance(nextRelation); if (LOGGER.isTraceEnabled()) { LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", segment.getEvaluationOrder().shortDump(), targetType, FocusTypeUtil.dumpAssignment(roleAssignment), nextEvaluationOrder); } ItemDeltaItem<PrismContainerValue<AssignmentType>,PrismContainerDefinition<AssignmentType>> roleAssignmentIdi = new ItemDeltaItem<>(); roleAssignmentIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(roleAssignment)); roleAssignmentIdi.recompute(); String nextSourceDescription = targetType+" in "+segment.sourceDescription; AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(targetType, nextSourceDescription, roleAssignmentIdi, true); nextSegment.setRelation(nextRelation); nextSegment.setEvaluationOrder(nextEvaluationOrder); nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget); nextSegment.setOrderOneObject(orderOneObject); // TODO why??? this should depend on evaluation order if (targetType instanceof AbstractRoleType) { nextSegment.setProcessMembership(false); // evaluation order of an assignment is probably too high (TODO but not in case of inducements going back into zero or negative orders!) } else { // We want to process membership in case of deputy and similar user->user assignments nextSegment.setProcessMembership(true); } nextSegment.setPathToSourceValid(isValid); assert !ctx.assignmentPath.isEmpty(); evaluateFromSegment(nextSegment, mode, ctx); } private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, FocusType targetType, AssignmentType inducement) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { ObjectType orderOneObject = getOrderOneObject(segment); if (!isInducementApplicableToFocusType(inducement.getFocusType())) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", FocusTypeUtil.dumpAssignment(inducement), inducement.getFocusType(), targetType.getClass().getSimpleName()); } return; } if (!isInducementAllowedByLimitations(segment, inducement)) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignment(inducement)); } return; } ItemDeltaItem<PrismContainerValue<AssignmentType>,PrismContainerDefinition<AssignmentType>> roleInducementIdi = new ItemDeltaItem<>(); roleInducementIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(inducement)); roleInducementIdi.recompute(); String subSourceDescription = targetType+" in "+segment.sourceDescription; AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(targetType, subSourceDescription, roleInducementIdi, false); boolean nextIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( segment.getEvaluationOrder(), nextSegment.getAssignment()); boolean nextIsMatchingOrderForTarget = AssignmentPathSegmentImpl.computeMatchingOrder( segment.getEvaluationOrderForTarget(), nextSegment.getAssignment()); Holder<EvaluationOrder> nextEvaluationOrderHolder = new Holder<>(segment.getEvaluationOrder().clone()); Holder<EvaluationOrder> nextEvaluationOrderForTargetHolder = new Holder<>(segment.getEvaluationOrderForTarget().clone()); adjustOrder(nextEvaluationOrderHolder, nextEvaluationOrderForTargetHolder, inducement.getOrderConstraint(), inducement.getOrder(), ctx.assignmentPath, nextSegment); nextSegment.setEvaluationOrder(nextEvaluationOrderHolder.getValue(), nextIsMatchingOrder); nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTargetHolder.getValue(), nextIsMatchingOrderForTarget); nextSegment.setOrderOneObject(orderOneObject); nextSegment.setPathToSourceValid(isValid); nextSegment.setProcessMembership(nextIsMatchingOrder); nextSegment.setRelation(getRelation(inducement)); // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into // inducements with non-matching order: for example because we need to extract target-related policy rules // (these are stored with order of one less than orders for focus-related policy rules). // // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. // processMembership attribute to false for these inducements. if (LOGGER.isTraceEnabled()) { LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", segment.getEvaluationOrder().shortDump(), targetType, FocusTypeUtil.dumpInducementConstraints(inducement), FocusTypeUtil.dumpAssignment(inducement), nextEvaluationOrderHolder.getValue().shortDump()); } assert !ctx.assignmentPath.isEmpty(); evaluateFromSegment(nextSegment, mode, ctx); } private void adjustOrder(Holder<EvaluationOrder> evaluationOrderHolder, Holder<EvaluationOrder> targetEvaluationOrderHolder, List<OrderConstraintsType> constraints, Integer order, AssignmentPathImpl assignmentPath, AssignmentPathSegmentImpl nextSegment) { if (constraints.isEmpty()) { if (order == null || order == 1) { return; } else if (order <= 0) { throw new IllegalStateException("Wrong inducement order: it must be positive but it is " + order + " instead"); } // converting legacy -> new specification int currentOrder = evaluationOrderHolder.getValue().getSummaryOrder(); if (order > currentOrder) { LOGGER.trace("order of the inducement ({}) is greater than the current evaluation order ({}), marking as undefined", order, currentOrder); makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); return; } // i.e. currentOrder >= order, i.e. currentOrder > order-1 int newOrder = currentOrder - (order - 1); assert newOrder > 0; constraints = Collections.singletonList(new OrderConstraintsType(prismContext) .order(order) .resetOrder(newOrder)); } OrderConstraintsType summaryConstraints = ObjectTypeUtil.getConstraintFor(constraints, null); Integer resetSummaryTo = summaryConstraints != null && summaryConstraints.getResetOrder() != null ? summaryConstraints.getResetOrder() : null; if (resetSummaryTo != null) { int summaryBackwards = evaluationOrderHolder.getValue().getSummaryOrder() - resetSummaryTo; if (summaryBackwards < 0) { // or should we throw an exception? LOGGER.warn("Cannot move summary order backwards to a negative value ({}). Current order: {}, requested order: {}", summaryBackwards, evaluationOrderHolder.getValue().getSummaryOrder(), resetSummaryTo); makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); return; } else if (summaryBackwards > 0) { // MultiSet<QName> backRelations = new HashMultiSet<>(); int assignmentsSeen = 0; int i = assignmentPath.size()-1; while (assignmentsSeen < summaryBackwards) { if (i < 0) { LOGGER.trace("Cannot move summary order backwards by {}; only {} assignments segment seen: {}", summaryBackwards, assignmentsSeen, assignmentPath); makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); return; } AssignmentPathSegmentImpl segment = assignmentPath.getSegments().get(i); if (segment.isAssignment()) { if (!ObjectTypeUtil.isDelegationRelation(segment.getRelation())) { // backRelations.add(segment.getRelation()); assignmentsSeen++; LOGGER.trace("Going back {}: relation at assignment -{} (position -{}): {}", summaryBackwards, assignmentsSeen, assignmentPath.size() - i, segment.getRelation()); } } else { AssignmentType inducement = segment.getAssignment(); for (OrderConstraintsType constraint : inducement.getOrderConstraint()) { if (constraint.getResetOrder() != null && constraint.getRelation() != null) { LOGGER.debug("Going back {}: an inducement with non-summary resetting constraint found" + " in the chain (at position -{}): {} in {}", summaryBackwards, assignmentPath.size()-i, constraint, segment); makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); return; } } if (segment.getLastEqualOrderSegmentIndex() != null) { i = segment.getLastEqualOrderSegmentIndex(); continue; } } i--; } nextSegment.setLastEqualOrderSegmentIndex(i); evaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrder()); targetEvaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrderForTarget()); } else { // summaryBackwards is 0 - nothing to change } for (OrderConstraintsType constraint : constraints) { if (constraint.getRelation() != null && constraint.getResetOrder() != null) { LOGGER.warn("Ignoring resetOrder (with a value of {} for {}) because summary order was already moved backwards by {} to {}: {}", constraint.getResetOrder(), constraint.getRelation(), summaryBackwards, evaluationOrderHolder.getValue().getSummaryOrder(), constraint); } } } else { EvaluationOrder beforeChange = evaluationOrderHolder.getValue().clone(); for (OrderConstraintsType constraint : constraints) { if (constraint.getResetOrder() != null) { assert constraint.getRelation() != null; // already processed above int currentOrder = evaluationOrderHolder.getValue().getMatchingRelationOrder(constraint.getRelation()); int newOrder = constraint.getResetOrder(); if (newOrder > currentOrder) { LOGGER.warn("Cannot increase evaluation order for {} from {} to {}: {}", constraint.getRelation(), currentOrder, newOrder, constraint); } else if (newOrder < currentOrder) { evaluationOrderHolder.setValue(evaluationOrderHolder.getValue().resetOrder(constraint.getRelation(), newOrder)); LOGGER.trace("Reset order for {} from {} to {} -> {}", constraint.getRelation(), currentOrder, newOrder, evaluationOrderHolder.getValue()); } else { LOGGER.trace("Keeping order for {} at {} -> {}", constraint.getRelation(), currentOrder, evaluationOrderHolder.getValue()); } } } Map<QName, Integer> difference = beforeChange.diff(evaluationOrderHolder.getValue()); targetEvaluationOrderHolder.setValue(targetEvaluationOrderHolder.getValue().applyDifference(difference)); } if (evaluationOrderHolder.getValue().getSummaryOrder() <= 0) { makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); } if (!targetEvaluationOrderHolder.getValue().isValid()) { // some extreme cases like the one described in TestAssignmentProcessor2.test520 makeUndefined(targetEvaluationOrderHolder); } if (!evaluationOrderHolder.getValue().isValid()) { throw new AssertionError("Resulting evaluation order path is invalid: " + evaluationOrderHolder.getValue()); } } @SafeVarargs private final void makeUndefined(Holder<EvaluationOrder>... holders) { for (Holder<EvaluationOrder> holder : holders) { holder.setValue(EvaluationOrderImpl.UNDEFINED); } } private void collectMembership(FocusType targetType, QName relation, EvaluationContext ctx) { PrismReferenceValue refVal = new PrismReferenceValue(); refVal.setObject(targetType.asPrismObject()); refVal.setTargetType(ObjectTypes.getObjectType(targetType.getClass()).getTypeQName()); refVal.setRelation(relation); refVal.setTargetName(targetType.getName().toPolyString()); if (ctx.assignmentPath.getSegments().stream().anyMatch(aps -> DeputyUtils.isDelegationAssignment(aps.getAssignment()))) { addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), ctx.evalAssignment::addDelegationRefVal, refVal, "delegationRef", targetType); } else { if (targetType instanceof AbstractRoleType) { addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), ctx.evalAssignment::addMembershipRefVal, refVal, "membershipRef", targetType); } } if (targetType instanceof OrgType && (ObjectTypeUtil.isDefaultRelation(relation) || ObjectTypeUtil.isManagerRelation(relation))) { addIfNotThere(ctx.evalAssignment.getOrgRefVals(), ctx.evalAssignment::addOrgRefVal, refVal, "orgRef", targetType); } } private void addIfNotThere(Collection<PrismReferenceValue> collection, Consumer<PrismReferenceValue> setter, PrismReferenceValue refVal, String collectionName, FocusType targetType) { if (!collection.contains(refVal)) { LOGGER.trace("Adding target {} to {}", targetType, collectionName); setter.accept(refVal); } else { LOGGER.trace("Would add target {} to {}, but it's already there", targetType, collectionName); } } private boolean isNonNegative(PlusMinusZero mode) { // mode == null is also considered negative, because it is a combination of PLUS and MINUS; // so the net result is that for both old and new state there exists an unsatisfied condition on the path. return mode == PlusMinusZero.ZERO || mode == PlusMinusZero.PLUS; } private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, FocusType targetType, QName relation) throws SchemaException { if (targetType instanceof AbstractRoleType) { // OK, just go on } else if (targetType instanceof UserType) { if (!ObjectTypeUtil.isDelegationRelation(relation)) { throw new SchemaException("Unsupported relation " + relation + " for assignment of target type " + targetType + " in " + segment.sourceDescription); } } else { throw new SchemaException("Unknown assignment target type " + targetType + " in " + segment.sourceDescription); } } private boolean isInducementApplicableToFocusType(QName inducementFocusType) throws SchemaException { if (inducementFocusType == null) { return true; } Class<?> inducementFocusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); if (inducementFocusClass == null) { throw new SchemaException("Could not determine class for " + inducementFocusType); } if (lensContext.getFocusClass() == null) { // should not occur; it would be probably safe to throw an exception here LOGGER.error("No focus class in lens context; inducement targeted at focus type {} will not be applied:\n{}", inducementFocusType, lensContext.debugDump()); return false; } return inducementFocusClass.isAssignableFrom(lensContext.getFocusClass()); } private boolean isInducementAllowedByLimitations(AssignmentPathSegment segment, AssignmentType roleInducement) { AssignmentSelectorType limitation = segment.getAssignment().getLimitTargetContent(); return limitation == null || FocusTypeUtil.selectorMatches(limitation, roleInducement); } private Authorization createAuthorization(AuthorizationType authorizationType, String sourceDesc) { Authorization authorization = new Authorization(authorizationType); authorization.setSourceDescription(sourceDesc); return authorization; } private void assertSourceNotNull(ObjectType source, EvaluatedAssignment<F> assignment) { if (source == null) { throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignment+")"); } } private void assertSourceNotNull(ObjectType source, ItemDeltaItem<PrismContainerValue<AssignmentType>,PrismContainerDefinition<AssignmentType>> assignmentIdi) { if (source == null) { throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignmentIdi.getAnyItem()+")"); } } private AssignmentType getAssignmentType(AssignmentPathSegmentImpl segment, EvaluationContext ctx) { return LensUtil.getAssignmentType(segment.getAssignmentIdi(), ctx.evaluateOld); } private void checkSchema(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException { AssignmentType assignmentType = getAssignmentType(segment, ctx); PrismContainerValue<AssignmentType> assignmentContainerValue = assignmentType.asPrismContainerValue(); PrismContainerable<AssignmentType> assignmentContainer = assignmentContainerValue.getParent(); if (assignmentContainer == null) { throw new SchemaException("The assignment "+assignmentType+" does not have a parent in "+segment.sourceDescription); } if (assignmentContainer.getDefinition() == null) { throw new SchemaException("The assignment "+assignmentType+" does not have definition in "+segment.sourceDescription); } PrismContainer<Containerable> extensionContainer = assignmentContainerValue.findContainer(AssignmentType.F_EXTENSION); if (extensionContainer != null) { if (extensionContainer.getDefinition() == null) { throw new SchemaException("Extension does not have a definition in assignment "+assignmentType+" in "+segment.sourceDescription); } for (Item<?,?> item: extensionContainer.getValue().getItems()) { if (item == null) { throw new SchemaException("Null item in extension in assignment "+assignmentType+" in "+segment.sourceDescription); } if (item.getDefinition() == null) { throw new SchemaException("Item "+item+" has no definition in extension in assignment "+assignmentType+" in "+segment.sourceDescription); } } } } private <O extends ObjectType> void setEvaluatedAssignmentTarget(AssignmentPathSegmentImpl segment, @NotNull List<PrismObject<O>> targets, EvaluationContext ctx) { assert ctx.evalAssignment.getTarget() == null; if (targets.size() > 1) { throw new UnsupportedOperationException("Multiple targets for direct focus assignment are not supported: " + segment.getAssignment()); } else if (!targets.isEmpty()) { ctx.evalAssignment.setTarget(targets.get(0)); } } public PrismValueDeltaSetTriple<PrismPropertyValue<Boolean>> evaluateCondition(MappingType condition, AssignmentType sourceAssignment, ObjectType source, AssignmentPathVariables assignmentPathVariables, EvaluationContext ctx) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { String desc; if (sourceAssignment == null) { desc = "condition in " + source; } else { desc = "condition in assignment in " + source; } Mapping.Builder<PrismPropertyValue<Boolean>,PrismPropertyDefinition<Boolean>> builder = mappingFactory.createMappingBuilder(); builder = builder.mappingType(condition) .contextDescription(desc) .sourceContext(focusOdo) .originType(OriginType.ASSIGNMENTS) .originObject(source) .defaultTargetDefinition(new PrismPropertyDefinitionImpl<>(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN, prismContext)) .addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo) .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo) .addVariableDefinition(ExpressionConstants.VAR_SOURCE, source) .rootNode(focusOdo); builder = LensUtil.addAssignmentPathVariables(builder, assignmentPathVariables); Mapping<PrismPropertyValue<Boolean>, PrismPropertyDefinition<Boolean>> mapping = builder.build(); mappingEvaluator.evaluateMapping(mapping, lensContext, ctx.task, ctx.result); return mapping.getOutputTriple(); } @Nullable private QName getRelation(AssignmentType assignmentType) { return assignmentType.getTargetRef() != null ? ObjectTypeUtil.normalizeRelation(assignmentType.getTargetRef().getRelation()) : null; } public static final class Builder<F extends FocusType> { private RepositoryService repository; private ObjectDeltaObject<F> focusOdo; private LensContext<F> lensContext; private String channel; private ObjectResolver objectResolver; private SystemObjectCache systemObjectCache; private PrismContext prismContext; private MappingFactory mappingFactory; private ActivationComputer activationComputer; private XMLGregorianCalendar now; private boolean loginMode = false; private PrismObject<SystemConfigurationType> systemConfiguration; private MappingEvaluator mappingEvaluator; public Builder() { } public Builder<F> repository(RepositoryService val) { repository = val; return this; } public Builder<F> focusOdo(ObjectDeltaObject<F> val) { focusOdo = val; return this; } public Builder<F> lensContext(LensContext<F> val) { lensContext = val; return this; } public Builder<F> channel(String val) { channel = val; return this; } public Builder<F> objectResolver(ObjectResolver val) { objectResolver = val; return this; } public Builder<F> systemObjectCache(SystemObjectCache val) { systemObjectCache = val; return this; } public Builder<F> prismContext(PrismContext val) { prismContext = val; return this; } public Builder<F> mappingFactory(MappingFactory val) { mappingFactory = val; return this; } public Builder<F> activationComputer(ActivationComputer val) { activationComputer = val; return this; } public Builder<F> now(XMLGregorianCalendar val) { now = val; return this; } public Builder<F> loginMode(boolean val) { loginMode = val; return this; } public Builder<F> systemConfiguration(PrismObject<SystemConfigurationType> val) { systemConfiguration = val; return this; } public Builder<F> mappingEvaluator(MappingEvaluator val) { mappingEvaluator = val; return this; } public AssignmentEvaluator<F> build() { return new AssignmentEvaluator<>(this); } } }