diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd index 6e05bbd8261..c748e6066a3 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd @@ -1597,10 +1597,77 @@ - - - - + + + + Selected values from individual sources. Variable values are not stored here. + + + + + + + Does this tuple contain a value that is present in plus set of its source? + (This is an internal implementation information. So this item can change in later versions.) + + + 4.2 + + + + + + + Does this tuple contain a value that is present in minus set of its source? + (This is an internal implementation information. So this item can change in later versions.) + + + 4.2 + + + + + + + Does this tuple contain a value that is present in zero set of its source? + (This is an internal implementation information. So this item can change in later versions.) + + + 4.2 + + + + + + + To what set should the output go? (Named "outputSet" in the algorithm since 4.2.) + + + + + + + Result of the condition evaluation. + + + 4.2 + + + + + + + Result of the transformation. (Named "transformationResult" in the algorithm since 4.2.) + + + + + + + Free-form comment from the algorithm. + + + diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java index f07fddb68dd..eb00c8f6769 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/GenerateExpressionEvaluator.java @@ -98,7 +98,7 @@ public PrismValueDeltaSetTriple evaluate(ExpressionEvaluationContext context, if (valuePolicyType == null) { ValuePolicyResolver valuePolicyResolver = context.getValuePolicyResolver(); if (valuePolicyResolver != null) { - valuePolicyType = valuePolicyResolver.resolve(); + valuePolicyType = valuePolicyResolver.resolve(result); } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/transformation/ValueTupleTransformation.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/transformation/ValueTupleTransformation.java index 0c7c9b78994..566672510d1 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/transformation/ValueTupleTransformation.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/evaluator/transformation/ValueTupleTransformation.java @@ -152,14 +152,11 @@ void evaluate() { try { if (!combinatorialEvaluation.evaluator.isIncludeNullInputs() && MiscUtil.isAllNull(valuesTuple)) { // The case that all the sources are null. There is no point executing the expression. - setTraceComment("All sources are null and includeNullInputs is true"); + setTraceComment("All sources are null and includeNullInputs is true."); return; } ExpressionVariables staticVariables = createStaticVariablesFromSources(); - - LOGGER.trace("Processing value combination {} in {}\n hasPlus={}, hasZero={}, hasMinus={}, skipEvaluationPlus={}, skipEvaluationMinus={}", - dumpValueTupleLazily(), context.getContextDescription(), hasPlus, hasZero, hasMinus, - context.isSkipEvaluationPlus(), context.isSkipEvaluationMinus()); + recordBeforeTransformation(); if (isApplicableRegardingPlusMinusSetPresence()) { @@ -289,7 +286,7 @@ private void evaluateConditionAndTransformation(ExpressionVariables staticVariab if (conditionResult) { transformationResult = evaluateTransformation(staticVariables); } else { - LOGGER.trace("Skipping value transformation because condition evaluated to false in {}", context.getContextDescription()); + setTraceComment("Skipping value transformation because condition evaluated to false."); transformationResult = emptySet(); } } catch (ExpressionEvaluationException e) { @@ -330,7 +327,7 @@ private List evaluateTransformation(ExpressionVariables staticVariables) thro } private void setTraceComment(String comment) { - LOGGER.trace(comment); + LOGGER.trace("{} In {}.", comment, context.getContextDescription()); if (trace != null) { trace.setComment(comment); } @@ -363,6 +360,17 @@ private void dumpValueCombinationToTrace() { } } + private void recordBeforeTransformation() { + LOGGER.trace("Processing value combination {} in {}\n hasPlus={}, hasZero={}, hasMinus={}, skipEvaluationPlus={}, skipEvaluationMinus={}", + dumpValueTupleLazily(), context.getContextDescription(), hasPlus, hasZero, hasMinus, + context.isSkipEvaluationPlus(), context.isSkipEvaluationMinus()); + if (trace != null) { + trace.setHasPlus(hasPlus); + trace.setHasMinus(hasMinus); + trace.setHasZero(hasZero); + } + } + private void recordTransformationResult() { LOGGER.trace("Processed value tuple {} in {}\n valueDestination: {}\n scriptResults:{}{}", dumpValueTupleLazily(), context.getContextDescription(), outputSet, transformationResult, @@ -370,6 +378,7 @@ private void recordTransformationResult() { if (trace != null) { trace.setDestination(PlusMinusZeroType.fromValue(outputSet)); + trace.setConditionResult(conditionResult); trace.getOutput().addAll(TraceUtil.toAnyValueTypeList(transformationResult, combinatorialEvaluation.prismContext)); } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingBuilder.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingBuilder.java new file mode 100644 index 00000000000..2eb5986c1c8 --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingBuilder.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2020 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.mapping; + +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.*; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.expression.VariablesMap; +import com.evolveum.midpoint.security.api.SecurityContextManager; +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 org.jetbrains.annotations.Nullable; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Builder is used to construct a configuration of Mapping object, which - after building - becomes + * immutable. + *

+ * In order to provide backward-compatibility with existing use of Mapping object, the builder has + * also traditional setter methods. Both setters and "builder-style" methods MODIFY existing Builder + * object (i.e. they do not create a new one). + *

+ * TODO decide on which style of setters to keep (setters vs builder-style). + */ +@SuppressWarnings({ "unused", "BooleanMethodIsAlwaysInverted", "UnusedReturnValue", "WeakerAccess" }) +public final class MappingBuilder { + + private static final Trace LOGGER = TraceManager.getTrace(MappingImpl.class); + + private ExpressionFactory expressionFactory; + private final ExpressionVariables variables = new ExpressionVariables(); + private MappingType mappingBean; + private MappingKindType mappingKind; + private ItemPath implicitSourcePath; // for tracing purposes + private ItemPath implicitTargetPath; // for tracing purposes + private ObjectResolver objectResolver; + private SecurityContextManager securityContextManager; + private Source defaultSource; + private final List> additionalSources = new ArrayList<>(); + private D defaultTargetDefinition; + private ExpressionProfile expressionProfile; + private ItemPath defaultTargetPath; + private Collection originalTargetValues; + private ObjectDeltaObject sourceContext; + private PrismObjectDefinition targetContext; + private OriginType originType; + private ObjectType originObject; + private ConfigurableValuePolicyResolver valuePolicyResolver; + private VariableProducer variableProducer; + private MappingPreExpression mappingPreExpression; + private boolean conditionMaskOld = true; + private boolean conditionMaskNew = true; + private XMLGregorianCalendar now; + private XMLGregorianCalendar defaultReferenceTime; + private boolean profiling; + private String contextDescription; + private QName mappingQName; + private RefinedObjectClassDefinition refinedObjectClassDefinition; + private PrismContext prismContext; + + public MappingImpl build() { + return new MappingImpl<>(this); + } + + //region Plain setters + public MappingBuilder expressionFactory(ExpressionFactory val) { + expressionFactory = val; + return this; + } + + public MappingBuilder variablesFrom(ExpressionVariables val) { + variables.addVariableDefinitions(val); + return this; + } + + public MappingBuilder mappingBean(MappingType val) { + mappingBean = val; + return this; + } + + public MappingBuilder mappingKind(MappingKindType val) { + mappingKind = val; + return this; + } + + public MappingBuilder implicitSourcePath(ItemPath val) { + implicitSourcePath = val; + return this; + } + + public MappingBuilder implicitTargetPath(ItemPath val) { + implicitTargetPath = val; + return this; + } + + public MappingBuilder objectResolver(ObjectResolver val) { + objectResolver = val; + return this; + } + + public MappingBuilder securityContextManager(SecurityContextManager val) { + securityContextManager = val; + return this; + } + + public MappingBuilder defaultSource(Source val) { + defaultSource = val; + return this; + } + + public MappingBuilder defaultTargetDefinition(D val) { + defaultTargetDefinition = val; + return this; + } + + public MappingBuilder expressionProfile(ExpressionProfile val) { + expressionProfile = val; + return this; + } + + public MappingBuilder defaultTargetPath(ItemPath val) { + defaultTargetPath = val; + return this; + } + + public MappingBuilder originalTargetValues(Collection values) { + originalTargetValues = values; + return this; + } + + public MappingBuilder sourceContext(ObjectDeltaObject val) { + if (val.getDefinition() == null) { + throw new IllegalArgumentException("Attempt to set mapping source context without a definition"); + } + sourceContext = val; + return this; + } + + public MappingBuilder targetContext(PrismObjectDefinition val) { + targetContext = val; + return this; + } + + public MappingBuilder originType(OriginType val) { + originType = val; + return this; + } + + public MappingBuilder originObject(ObjectType val) { + originObject = val; + return this; + } + + public MappingBuilder valuePolicyResolver(ConfigurableValuePolicyResolver val) { + valuePolicyResolver = val; + return this; + } + + public MappingBuilder variableResolver(VariableProducer variableProducer) { + this.variableProducer = variableProducer; + return this; + } + + public MappingBuilder mappingPreExpression(MappingPreExpression mappingPreExpression) { + this.mappingPreExpression = mappingPreExpression; + return this; + } + + public MappingBuilder conditionMaskOld(boolean val) { + conditionMaskOld = val; + return this; + } + + public MappingBuilder conditionMaskNew(boolean val) { + conditionMaskNew = val; + return this; + } + + public MappingBuilder now(XMLGregorianCalendar val) { + now = val; + return this; + } + + public MappingBuilder defaultReferenceTime(XMLGregorianCalendar val) { + defaultReferenceTime = val; + return this; + } + + public MappingBuilder profiling(boolean val) { + profiling = val; + return this; + } + + public MappingBuilder contextDescription(String val) { + contextDescription = val; + return this; + } + + public MappingBuilder mappingQName(QName val) { + mappingQName = val; + return this; + } + + public MappingBuilder refinedObjectClassDefinition(RefinedObjectClassDefinition val) { + refinedObjectClassDefinition = val; + return this; + } + + public MappingBuilder prismContext(PrismContext val) { + prismContext = val; + return this; + } + //endregion + + public MappingBuilder rootNode(ObjectReferenceType objectRef) { + return addVariableDefinition(null, objectRef); + } + + public MappingBuilder rootNode(ObjectDeltaObject odo) { + return addVariableDefinition(null, odo); + } + + public MappingBuilder rootNode(O objectType, PrismObjectDefinition definition) { + variables.put(null, objectType, definition); + return this; + } + + public MappingBuilder rootNode(PrismObject mpObject, PrismObjectDefinition definition) { + variables.put(null, mpObject, definition); + return this; + } + + public MappingBuilder addVariableDefinition(ExpressionVariableDefinitionType varDef) throws SchemaException { + if (varDef.getObjectRef() != null) { + ObjectReferenceType ref = varDef.getObjectRef(); + ref.setType(getPrismContext().getSchemaRegistry().qualifyTypeName(ref.getType())); + return addVariableDefinition(varDef.getName().getLocalPart(), ref); + } else if (varDef.getValue() != null) { + // This is raw value. We do have definition here. The best we can do is autodetect. + // Expression evaluation code will do that as a fallback behavior. + return addVariableDefinition(varDef.getName().getLocalPart(), varDef.getValue(), Object.class); + } else { + LOGGER.warn("Empty definition of variable {} in {}, ignoring it", varDef.getName(), getContextDescription()); + return this; + } + } + + public MappingBuilder addVariableDefinition(String name, ObjectReferenceType objectRef) { + return addVariableDefinition(name, objectRef, objectRef.asReferenceValue().getDefinition()); + } + + public MappingBuilder addVariableDefinition(String name, O objectType, Class expectedClass) { + // Maybe determine definition from schema registry here in case that object is null. We can do that here. + variables.putObject(name, objectType, expectedClass); + return this; + } + + public MappingBuilder addVariableDefinition(String name, PrismObject midpointObject, Class expectedClass) { + // Maybe determine definition from schema registry here in case that object is null. We can do that here. + variables.putObject(name, midpointObject, expectedClass); + return this; + } + + public MappingBuilder addVariableDefinition(String name, String value) { + MutablePrismPropertyDefinition def = prismContext.definitionFactory().createPropertyDefinition( + new QName(SchemaConstants.NS_C, name), PrimitiveType.STRING.getQname()); + return addVariableDefinition(name, value, def); + } + + public MappingBuilder addVariableDefinition(String name, boolean value) { + MutablePrismPropertyDefinition def = prismContext.definitionFactory().createPropertyDefinition( + new QName(SchemaConstants.NS_C, name), PrimitiveType.BOOLEAN.getQname()); + return addVariableDefinition(name, value, def); + } + + public MappingBuilder addVariableDefinition(String name, int value) { + MutablePrismPropertyDefinition def = prismContext.definitionFactory().createPropertyDefinition( + new QName(SchemaConstants.NS_C, name), PrimitiveType.INT.getQname()); + return addVariableDefinition(name, value, def); + } + + public MappingBuilder addVariableDefinition(String name, PrismValue value) { + return addVariableDefinition(name, value, value.getParent().getDefinition()); + } + + public MappingBuilder addVariableDefinition(String name, ObjectDeltaObject value) { + PrismObjectDefinition definition = value.getDefinition(); + if (definition == null) { + throw new IllegalArgumentException("Attempt to set variable '" + name + "' as ODO without a definition: " + value); + } + return addVariableDefinition(name, value, definition); + } + + // mainVariable of "null" means the default source + public MappingBuilder addAliasRegistration(String alias, @Nullable String mainVariable) { + variables.registerAlias(alias, mainVariable); + return this; + } + + public MappingBuilder addVariableDefinitions(VariablesMap extraVariables) { + variables.putAll(extraVariables); + return this; + } + + public MappingBuilder addVariableDefinition(String name, Object value, ItemDefinition definition) { + variables.put(name, value, definition); + return this; + } + + public MappingBuilder addVariableDefinition(String name, Object value, Class typeClass) { + variables.put(name, value, typeClass); + return this; + } + + public MappingBuilder stringPolicyResolver(ConfigurableValuePolicyResolver stringPolicyResolver) { + this.valuePolicyResolver = stringPolicyResolver; + return this; + } + + public boolean hasVariableDefinition(String varName) { + return variables.containsKey(varName); + } + + public boolean isApplicableToChannel(String channel) { + return MappingImpl.isApplicableToChannel(mappingBean, channel); + } + + public MappingBuilder additionalSource(Source source) { + additionalSources.add(source); + return this; + } + + public MappingStrengthType getStrength() { + return MappingImpl.getStrength(mappingBean); + } + + //region Plain getters + public PrismContext getPrismContext() { + return prismContext; + } + + public ExpressionFactory getExpressionFactory() { + return expressionFactory; + } + + public ExpressionVariables getVariables() { + return variables; + } + + public MappingType getMappingBean() { + return mappingBean; + } + + public MappingKindType getMappingKind() { + return mappingKind; + } + + public ItemPath getImplicitSourcePath() { + return implicitSourcePath; + } + + public ItemPath getImplicitTargetPath() { + return implicitTargetPath; + } + + public ObjectResolver getObjectResolver() { + return objectResolver; + } + + public SecurityContextManager getSecurityContextManager() { + return securityContextManager; + } + + public Source getDefaultSource() { + return defaultSource; + } + + public List> getAdditionalSources() { + return additionalSources; + } + + public D getDefaultTargetDefinition() { + return defaultTargetDefinition; + } + + public ExpressionProfile getExpressionProfile() { + return expressionProfile; + } + + public ItemPath getDefaultTargetPath() { + return defaultTargetPath; + } + + public Collection getOriginalTargetValues() { + return originalTargetValues; + } + + public ObjectDeltaObject getSourceContext() { + return sourceContext; + } + + public PrismObjectDefinition getTargetContext() { + return targetContext; + } + + public OriginType getOriginType() { + return originType; + } + + public ObjectType getOriginObject() { + return originObject; + } + + public ConfigurableValuePolicyResolver getValuePolicyResolver() { + return valuePolicyResolver; + } + + public VariableProducer getVariableProducer() { + return variableProducer; + } + + public MappingPreExpression getMappingPreExpression() { + return mappingPreExpression; + } + + public boolean isConditionMaskOld() { + return conditionMaskOld; + } + + public boolean isConditionMaskNew() { + return conditionMaskNew; + } + + public XMLGregorianCalendar getNow() { + return now; + } + + public XMLGregorianCalendar getDefaultReferenceTime() { + return defaultReferenceTime; + } + + public boolean isProfiling() { + return profiling; + } + + public String getContextDescription() { + return contextDescription; + } + + public QName getMappingQName() { + return mappingQName; + } + + public RefinedObjectClassDefinition getRefinedObjectClassDefinition() { + return refinedObjectClassDefinition; + } + //endregion +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingFactory.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingFactory.java index 71eb4431489..77d3c8c8537 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingFactory.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingFactory.java @@ -12,7 +12,6 @@ import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; import com.evolveum.midpoint.security.api.SecurityContextManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; @@ -71,18 +70,17 @@ public void setProfiling(boolean profiling) { this.profiling = profiling; } - public MappingImpl.Builder createMappingBuilder() { - return new MappingImpl.Builder() + public MappingBuilder createMappingBuilder() { + return new MappingBuilder() .prismContext(prismContext) .expressionFactory(expressionFactory) .securityContextManager(securityContextManager) - .variables(new ExpressionVariables()) .objectResolver(objectResolver) .profiling(profiling); } - public MappingImpl.Builder createMappingBuilder(MappingType mappingType, String shortDesc) { - return this.createMappingBuilder().mappingType(mappingType) + public MappingBuilder createMappingBuilder(MappingType mappingType, String shortDesc) { + return this.createMappingBuilder().mappingBean(mappingType) .contextDescription(shortDesc) .objectResolver(objectResolver); } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java index 5f8c7ee9a50..5dfe8cc8c49 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java @@ -6,39 +6,35 @@ */ package com.evolveum.midpoint.model.common.mapping; +import static org.apache.commons.lang3.BooleanUtils.isNotFalse; +import static org.apache.commons.lang3.BooleanUtils.isTrue; + +import java.util.Objects; import java.util.*; -import javax.xml.datatype.DatatypeConstants; -import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; - import org.jetbrains.annotations.NotNull; -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.Mapping; import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; import com.evolveum.midpoint.prism.util.ObjectDeltaObject; import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.repo.common.expression.*; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.expression.ExpressionProfile; import com.evolveum.midpoint.schema.expression.TypedValue; -import com.evolveum.midpoint.schema.expression.VariablesMap; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.SchemaDebugUtil; import com.evolveum.midpoint.security.api.SecurityContextManager; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -46,127 +42,357 @@ import com.evolveum.prism.xml.ns._public.types_3.DeltaSetTripleType; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; -import org.jetbrains.annotations.Nullable; - /** - * Mapping is non-recyclable single-use object. Once evaluated it should not be evaluated again. It will retain its original - * inputs and outputs that can be read again and again. But these should not be changed after evaluation. + * Evaluation of a mapping. It is non-recyclable single-use object. Once evaluated it should not be evaluated again. + * It will retain its original inputs and outputs that can be read again and again. But these should not be + * changed after evaluation. + * + * TODO document evaluation of time constraints ... + * *

* Configuration properties are unmodifiable. They are to be set via Mapping.Builder. * * @author Radovan Semancik */ +@SuppressWarnings("JavadocReference") public class MappingImpl implements Mapping, DebugDumpable, PrismValueDeltaSetTripleProducer { + private static final Trace LOGGER = TraceManager.getTrace(MappingImpl.class); + private static final String OP_EVALUATE_PREPARED = MappingImpl.class.getName() + ".evaluatePrepared"; private static final String OP_EVALUATE = MappingImpl.class.getName() + ".evaluate"; + private static final String OP_EVALUATE_TIME_VALIDITY = MappingImpl.class.getName() + ".evaluateTimeValidity"; private static final String OP_PREPARE = MappingImpl.class.getName() + ".prepare"; - // configuration properties (unmodifiable) - private final MappingType mappingType; + //region Configuration properties (almost unmodifiable) + + /** + * Definition of the mapping. + */ + @NotNull final MappingType mappingBean; + + /** + * Classification of the mapping (for reporting and diagnostic purposes). + */ + @Experimental private final MappingKindType mappingKind; + + /** + * (Context) variables to be used during mapping evaluation. + */ + final ExpressionVariables variables; + + /** + * Default source object. Used for resolution of paths that have no variable specification. + * For example, when using shortened path "name" instead of e.g. "$focus/name". + */ + private final ObjectDeltaObject sourceContext; + + /** + * Typified version of {@link #sourceContext}. Lazily evaluated. + */ + private TypedValue> typedSourceContext; + + /** + * One of the sources can be denoted as default. + * See {@link ExpressionEvaluationContext#defaultSource}. + * + * Examples: attribute value for inbound mappings, "legal" information for existence mappings, etc. + * + * NOTE: Contrary to the use of defaultSource in expression evaluation context (where the default source + * is always one of the sources), here the default source is an ADDITIONAL one, related to the other sources. + * (If an explicit source of the same name is defined in the mapping, it overrides the default source.) + */ + final Source defaultSource; + + /** + * Information about the implicit source for a mapping. It is provided here for reporting and diagnostic purposes only. + * An example: attributes/ri:name for inbound mapping for that attribute. + */ + @Experimental private final ItemPath implicitSourcePath; + + /** + * Default target object. Used for resolution of paths that have no variable specification. + */ + final PrismObjectDefinition targetContext; + + /** + * Information about the implicit target for a mapping. It is provided here for reporting and diagnostic purposes only. + * An example: $shadow/activation for activation mapping. + * Useful when defaultTargetPath is not specified. + */ private final ItemPath implicitTargetPath; - private final ExpressionFactory expressionFactory; - private final ExpressionVariables variables; - private final PrismContext prismContext; - private final ObjectDeltaObject sourceContext; - private TypedValue> typedSourceContext; // cached - private final Collection> sources; - private final Source defaultSource; + /** + * Default target path if "target" or "target/path" is missing. + * Used e.g. for outbound mappings. + */ + final ItemPath defaultTargetPath; - private final PrismObjectDefinition targetContext; - private final ItemPath defaultTargetPath; - private final D defaultTargetDefinition; + /** + * Value for {@link #outputDefinition} to be used when there's no target path specified. + * (For some cases it perhaps could be derived using {@link #defaultTargetPath} but we currently + * do not use this option.) + */ + final D defaultTargetDefinition; + + /** + * Original values of the mapping target. Currently used for range checking. + */ private final Collection originalTargetValues; - private final ExpressionProfile expressionProfile; - private final ObjectResolver objectResolver; - private final SecurityContextManager securityContextManager; // in order to get c:actor variable + /** + * Expression profile to be used when evaluating various expressions (condition, + * "main" expression, value set expressions, etc). + */ + final ExpressionProfile expressionProfile; + + /** + * Information on the kind of mapping. (Partially overlaps with {@link #mappingKind}.) + * It is put into output triples as an origin metadata. Deprecated. Most probably will + * be replaced by provenance metadata. + */ private final OriginType originType; + + /** + * Information on the object where the mapping is defined (e.g. role, resource, and so on). + * Used for diagnostic and reporting purposes. + */ private final ObjectType originObject; - private final ValuePolicyResolver stringPolicyResolver; + + /** + * Provider of the value policy (used for "generate" expressions). + * See {@link ExpressionEvaluationContext#valuePolicyResolver}. + */ + final ConfigurableValuePolicyResolver valuePolicyResolver; + + /** + * Mapping pre-expression is invoked just before main mapping expression. + * Pre expression will get the same expression context as the main expression. + * This is an opportunity to manipulate the context just before evaluation. + * Or maybe evaluate additional expressions that set up environment for + * main expression. + */ + private final MappingPreExpression mappingPreExpression; + + /** + * Additional clause for condition evaluation. If set to "false" then condition for old state + * is considered to be false. Used to skip evaluation for old state if we know there's nothing + * reasonable to be evaluated: e.g. when evaluating constructions for users being added (no "old" + * state there). + */ private final boolean conditionMaskOld; + + /** + * Additional clause for condition evaluation. If set to "false" then condition for new state + * is considered to be false. Used to skip evaluation for new state if we know there's nothing + * reasonable to be evaluated: e.g. when evaluating constructions for users being deleted (no "new" + * state there). + */ private final boolean conditionMaskNew; - private final XMLGregorianCalendar defaultReferenceTime; - private final XMLGregorianCalendar now; + + /** + * "System time" to be used when evaluating this mapping. + */ + final XMLGregorianCalendar now; + + /** + * Whether to record and display evaluation times. + * Usually not used in production. + */ private final boolean profiling; + + /** + * Free-form description of the context in which the mapping is evaluated. + */ private final String contextDescription; - private final QName mappingQName; // This is sometimes used to identify the element that mapping produces - // if it is different from itemName. E.g. this happens with associations. - private final RefinedObjectClassDefinition refinedObjectClassDefinition; + /** + * Producer of extra variables. It is not directly used in mapping evaluation: + * it is propagated to {@link ExpressionEvaluationContext#variableProducer}. + */ + private final VariableProducer variableProducer; + + /** + * This is sometimes used to identify the element that mapping produces + * if it is different from itemName. E.g. this happens with associations. + * + * TODO clarify, maybe rename + */ + private final QName mappingQName; + //endregion + + //region Beans + final ExpressionFactory expressionFactory; + final PrismContext prismContext; + final ObjectResolver objectResolver; + private final SecurityContextManager securityContextManager; // in order to get c:actor variable + //endregion + + //region Working and output properties + + /** + * Parses sources and targets. Holds partial results of this process. + */ + final MappingParser parser; + + /** + * Sources for condition and expression evaluation. + * These are created during mapping parsing. + * (In rare cases, extra pre-parsed sources can be provided using builder.) + */ + final Collection> sources = new ArrayList<>(); - // working and output properties - private D outputDefinition; - private ItemPath outputPath; + /** + * State of the mapping evaluation. + */ private MappingEvaluationState state = MappingEvaluationState.UNINITIALIZED; + /** + * Evaluated mapping expression. Once evaluated it is not used any more it is remembered only for tracing purposes. + */ + private Expression expression; + + /** + * Result of the mapping evaluation: values that will be added, deleted and kept in the target item. + * (This is relative to the whole mapping and/or its assignment being added, deleted, or kept.) + */ private PrismValueDeltaSetTriple outputTriple; + + /** + * Result of the condition evaluation in old vs. new state. + */ private PrismValueDeltaSetTriple> conditionOutputTriple; - private Boolean timeConstraintValid; - private XMLGregorianCalendar nextRecomputeTime; + + /** + * Scalar result of the condition evaluation for "old" state. Non-null after evaluation. + */ + private Boolean conditionResultOld; + + /** + * Scalar result of the condition evaluation for "new" state. Non-null after evaluation. + */ + private Boolean conditionResultNew; + + /** + * Evaluation of time constraints. + */ + private TimeConstraintsEvaluation timeConstraintsEvaluation; + + + /** + * When the mapping evaluation started. Used only if profiling is turned on. + */ private Long evaluationStartTime; + + /** + * When the mapping evaluation ended. Used only if profiling is turned on. + */ private Long evaluationEndTime; + /** + * Parent context description with added information about this mapping. + * Lazily evaluated. + */ private String mappingContextDescription; - private VariableProducer variableProducer; - + /** + * Trace for mapping evaluation, attached to the operation result. + */ private MappingEvaluationTraceType trace; /** - * Mapping pre-expression is invoked just before main mapping expression. - * Pre expression will get the same expression context as the main expression. - * This is an opportunity to manipulate the context just before evaluation. - * Or maybe evaluate additional expressions that set up environment for - * main expression. + * Mapping state properties that are exposed to the expressions. They can be used by the expressions to "communicate". + * E.g. one expression setting the property and other expression checking the property. */ - private MappingPreExpression mappingPreExpression; - - // This is single-use only. Once evaluated it is not used any more - // it is remembered only for tracing purposes. - private Expression expression; - - // Mapping state properties that are exposed to the expressions. They can be used by the expressions to "communicate". - // E.g. one expression seting the property and other expression checking the property. private Map stateProperties; - private static final Trace LOGGER = TraceManager.getTrace(MappingImpl.class); - - private MappingImpl(Builder builder) { - prismContext = builder.prismContext; - expressionFactory = builder.expressionFactory; - variables = builder.variables; - mappingType = builder.mappingType; - mappingKind = builder.mappingKind; - implicitSourcePath = builder.implicitSourcePath; - implicitTargetPath = builder.implicitTargetPath; - objectResolver = builder.objectResolver; - securityContextManager = builder.securityContextManager; - defaultSource = builder.defaultSource; - defaultTargetDefinition = builder.defaultTargetDefinition; - expressionProfile = builder.expressionProfile; - defaultTargetPath = builder.defaultTargetPath; - originalTargetValues = builder.originalTargetValues; - sourceContext = builder.sourceContext; - targetContext = builder.targetContext; - sources = builder.sources; - originType = builder.originType; - originObject = builder.originObject; - stringPolicyResolver = builder.valuePolicyResolver; - variableProducer = builder.variableProducer; - mappingPreExpression = builder.mappingPreExpression; - conditionMaskOld = builder.conditionMaskOld; - conditionMaskNew = builder.conditionMaskNew; - defaultReferenceTime = builder.defaultReferenceTime; - profiling = builder.profiling; - contextDescription = builder.contextDescription; - mappingQName = builder.mappingQName; - refinedObjectClassDefinition = builder.refinedObjectClassDefinition; - now = builder.now; + /** + * Task stored during the evaluation, removed afterwards. + */ + private Task task; + //endregion + + //region Constructors and (relatively) simple getters + MappingImpl(MappingBuilder builder) { + prismContext = builder.getPrismContext(); + expressionFactory = builder.getExpressionFactory(); + variables = builder.getVariables(); + mappingBean = Objects.requireNonNull(builder.getMappingBean(), "Mapping definition cannot be null"); + mappingKind = builder.getMappingKind(); + implicitSourcePath = builder.getImplicitSourcePath(); + implicitTargetPath = builder.getImplicitTargetPath(); + objectResolver = builder.getObjectResolver(); + securityContextManager = builder.getSecurityContextManager(); + defaultSource = builder.getDefaultSource(); + defaultTargetDefinition = builder.getDefaultTargetDefinition(); + expressionProfile = builder.getExpressionProfile(); + defaultTargetPath = builder.getDefaultTargetPath(); + originalTargetValues = builder.getOriginalTargetValues(); + sourceContext = builder.getSourceContext(); + targetContext = builder.getTargetContext(); + originType = builder.getOriginType(); + originObject = builder.getOriginObject(); + valuePolicyResolver = builder.getValuePolicyResolver(); + variableProducer = builder.getVariableProducer(); + mappingPreExpression = builder.getMappingPreExpression(); + conditionMaskOld = builder.isConditionMaskOld(); + conditionMaskNew = builder.isConditionMaskNew(); + profiling = builder.isProfiling(); + contextDescription = builder.getContextDescription(); + mappingQName = builder.getMappingQName(); + now = builder.getNow(); + sources.addAll(builder.getAdditionalSources()); + parser = new MappingParser<>(this); + } + + @SuppressWarnings("CopyConstructorMissesField") // TODO what about the other fields + private MappingImpl(MappingImpl prototype) { + this.mappingBean = prototype.mappingBean; + this.mappingKind = prototype.mappingKind; + this.implicitSourcePath = prototype.implicitSourcePath; + this.implicitTargetPath = prototype.implicitTargetPath; + this.sources.addAll(prototype.sources); + this.variables = prototype.variables; + + this.expressionFactory = prototype.expressionFactory; + this.prismContext = prototype.prismContext; + this.objectResolver = prototype.objectResolver; + this.securityContextManager = prototype.securityContextManager; + + this.sourceContext = prototype.sourceContext; + // typedSourceContext as well? + this.defaultSource = prototype.defaultSource; + + this.targetContext = prototype.targetContext; + this.defaultTargetPath = prototype.defaultTargetPath; + this.defaultTargetDefinition = prototype.defaultTargetDefinition; + this.originalTargetValues = prototype.originalTargetValues; + this.expressionProfile = prototype.expressionProfile; + + this.originType = prototype.originType; + this.originObject = prototype.originObject; + this.valuePolicyResolver = prototype.valuePolicyResolver; + this.mappingPreExpression = prototype.mappingPreExpression; + this.conditionMaskOld = prototype.conditionMaskOld; + this.conditionMaskNew = prototype.conditionMaskNew; + this.now = prototype.now; + this.profiling = prototype.profiling; + this.variableProducer = prototype.variableProducer; + + this.mappingQName = prototype.mappingQName; + + this.contextDescription = prototype.contextDescription; + + if (prototype.outputTriple != null) { + this.outputTriple = prototype.outputTriple.clone(); + } + if (prototype.conditionOutputTriple != null) { + this.conditionOutputTriple = prototype.conditionOutputTriple.clone(); + } + this.parser = prototype.parser; } public ObjectResolver getObjectResolver() { @@ -174,15 +400,8 @@ public ObjectResolver getObjectResolver() { } public QName getItemName() { - if (outputDefinition != null) { - return outputDefinition.getItemName(); - } - return null; - } - - @SuppressWarnings("unused") - public OriginType getOriginType() { - return originType; + D outputDefinition = getOutputDefinition(); + return outputDefinition != null ? outputDefinition.getItemName() : null; } public ObjectType getOriginObject() { @@ -193,30 +412,15 @@ public ObjectType getOriginObject() { return defaultSource; } - @SuppressWarnings("unused") - public D getDefaultTargetDefinition() { - return defaultTargetDefinition; - } - - @SuppressWarnings("unused") - public ItemPath getDefaultTargetPath() { - return defaultTargetPath; - } - public ObjectDeltaObject getSourceContext() { return sourceContext; } - @SuppressWarnings("WeakerAccess") - public PrismObjectDefinition getTargetContext() { - return targetContext; - } - public String getContextDescription() { return contextDescription; } - private TypedValue> getTypedSourceContext() { + TypedValue> getTypedSourceContext() { if (sourceContext == null) { return null; } @@ -229,8 +433,8 @@ private TypedValue> getTypedSourceContext() { public String getMappingContextDescription() { if (mappingContextDescription == null) { StringBuilder sb = new StringBuilder("mapping "); - if (mappingType.getName() != null) { - sb.append("'").append(mappingType.getName()).append("' "); + if (mappingBean.getName() != null) { + sb.append("'").append(mappingBean.getName()).append("' "); } sb.append("in "); sb.append(contextDescription); @@ -239,17 +443,9 @@ public String getMappingContextDescription() { return mappingContextDescription; } - public MappingType getMappingType() { - return mappingType; - } - - @SuppressWarnings("unused") - public MappingPreExpression getMappingPreExpression() { - return mappingPreExpression; - } - - public void setMappingPreExpression(MappingPreExpression mappingPreExpression) { - this.mappingPreExpression = mappingPreExpression; + @NotNull + public MappingType getMappingBean() { + return mappingBean; } @Override @@ -259,70 +455,29 @@ public boolean isSourceless() { @Override public MappingStrengthType getStrength() { - return getStrength(mappingType); + return getStrength(mappingBean); } - public static MappingStrengthType getStrength(MappingType mappingType) { - if (mappingType == null) { + public static MappingStrengthType getStrength(MappingType mappingBean) { + if (mappingBean != null && mappingBean.getStrength() != null) { + return mappingBean.getStrength(); + } else { return MappingStrengthType.NORMAL; } - MappingStrengthType value = mappingType.getStrength(); - if (value == null) { - value = MappingStrengthType.NORMAL; - } - return value; } @Override public boolean isAuthoritative() { - if (mappingType == null) { - return true; - } - Boolean value = mappingType.isAuthoritative(); - if (value == null) { - value = true; - } - return value; + return isNotFalse(mappingBean.isAuthoritative()); } @Override public boolean isExclusive() { - if (mappingType == null) { - return false; - } - Boolean value = mappingType.isExclusive(); - if (value == null) { - value = false; - } - return value; + return isTrue(mappingBean.isExclusive()); } public boolean hasTargetRange() { - return mappingType.getTarget().getSet() != null; - } - - @SuppressWarnings("unused") - public boolean isConditionMaskOld() { - return conditionMaskOld; - } - - @SuppressWarnings("unused") - public boolean isConditionMaskNew() { - return conditionMaskNew; - } - - private PrismContext getPrismContext() { - return prismContext; - } - - @SuppressWarnings("unused") - public ValuePolicyResolver getStringPolicyResolver() { - return stringPolicyResolver; - } - - @SuppressWarnings("unused") - public boolean isApplicableToChannel(String channelUri) { - return isApplicableToChannel(mappingType, channelUri); + return mappingBean.getTarget().getSet() != null; } public static boolean isApplicableToChannel(MappingType mappingType, String channelUri) { @@ -338,29 +493,18 @@ public XMLGregorianCalendar getNow() { return now; } - @SuppressWarnings("unused") - public XMLGregorianCalendar getDefaultReferenceTime() { - return defaultReferenceTime; + public XMLGregorianCalendar getNextRecomputeTime() { + return timeConstraintsEvaluation.getNextRecomputeTime(); } - public XMLGregorianCalendar getNextRecomputeTime() { - return nextRecomputeTime; + public boolean isTimeConstraintValid() { + return timeConstraintsEvaluation.isTimeConstraintValid(); } public boolean isProfiling() { return profiling; } - @SuppressWarnings("unused") - public Long getEvaluationStartTime() { - return evaluationStartTime; - } - - @SuppressWarnings("unused") - public Long getEvaluationEndTime() { - return evaluationEndTime; - } - public Long getEtime() { if (evaluationStartTime == null || evaluationEndTime == null) { return null; @@ -368,26 +512,15 @@ public Long getEtime() { return evaluationEndTime - evaluationStartTime; } - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer#getMappingQName() - */ @Override public QName getMappingQName() { return mappingQName; } - @SuppressWarnings("WeakerAccess") - public RefinedObjectClassDefinition getRefinedObjectClassDefinition() { - return refinedObjectClassDefinition; - } - @Override public T getStateProperty(String propertyName) { - if (stateProperties == null) { - return null; - } //noinspection unchecked - return (T) stateProperties.get(propertyName); + return stateProperties != null ? (T) stateProperties.get(propertyName) : null; } @Override @@ -398,10 +531,16 @@ public T setStateProperty(String propertyName, T value) { //noinspection unchecked return (T) stateProperties.put(propertyName, value); } + //endregion + + //region Evaluation - // TODO: rename to evaluateAll + /** + * Evaluate the mapping. Can be called in UNINITIALIZED or PREPARED states only. + */ public void evaluate(Task task, OperationResult parentResult) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + this.task = task; OperationResult result = parentResult.subresult(OP_EVALUATE) .addArbitraryObjectAsContext("mapping", this) .addArbitraryObjectAsContext("context", getContextDescription()) @@ -409,45 +548,72 @@ public void evaluate(Task task, OperationResult parentResult) throws ExpressionE .setMinor() .build(); if (result.isTracingNormal(MappingEvaluationTraceType.class)) { - // temporary solution - to avoid checking level at too many places trace = new MappingEvaluationTraceType(prismContext) - .mapping(mappingType.clone()) + .mapping(mappingBean.clone()) .mappingKind(mappingKind) .implicitSourcePath(implicitSourcePath != null ? new ItemPathType(implicitSourcePath) : null) .implicitTargetPath(implicitTargetPath != null ? new ItemPathType(implicitTargetPath) : null) .containingObjectRef(ObjectTypeUtil.createObjectRef(originObject, prismContext)); - trace.setMapping(mappingType.clone()); result.addTrace(trace); } else { trace = null; } try { - prepare(task, result); - - // if (!isActivated()) { - // outputTriple = null; - // LOGGER.debug("Skipping evaluation of mapping {} in {} because it is not activated", - // mappingType.getName() == null?null:mappingType.getName(), contextDescription); - // return; - // } + assertUninitializedOrPrepared(); + prepare(result); + evaluatePrepared(result); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + this.task = null; + } + } - evaluateBody(task, result); + /** + * Evaluate the time validity. Can be called in UNINITIALIZED or PREPARED states only. + */ + public void evaluateTimeValidity(Task task, OperationResult parentResult) throws ExpressionEvaluationException, ObjectNotFoundException, + SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + this.task = task; + OperationResult result = parentResult.subresult(OP_EVALUATE_TIME_VALIDITY) + .addArbitraryObjectAsContext("mapping", this) + .addArbitraryObjectAsContext("context", getContextDescription()) + .addArbitraryObjectAsContext("task", task) + .setMinor() + .build(); + try { + assertUninitializedOrPrepared(); + prepare(result); + evaluateTimeConstraint(result); } catch (Throwable t) { result.recordFatalError(t); throw t; } finally { result.computeStatusIfUnknown(); + this.task = null; + } + } + + private void assertUninitializedOrPrepared() { + if (state != MappingEvaluationState.UNINITIALIZED && state != MappingEvaluationState.PREPARED) { + throw new IllegalArgumentException("Expected mapping state UNINITIALIZED or PREPARED, but was " + state); } } /** - * Prepare mapping for evaluation. Parse the values - * After this call it can be checked if a mapping is activated (i.e. if the input changes will "trigger" the mapping). + * Prepare mapping for evaluation. Parse the values. After this call it can be checked if a mapping is + * activated (i.e. if the input changes will "trigger" the mapping). */ - public void prepare(Task task, OperationResult parentResult) + public void prepare(OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, SecurityViolationException, ConfigurationException, CommunicationException { + if (state == MappingEvaluationState.PREPARED) { + return; + } + OperationResult result = parentResult.subresult(OP_PREPARE) .addArbitraryObjectAsContext("mapping", this) .addArbitraryObjectAsContext("task", task) @@ -456,40 +622,24 @@ public void prepare(Task task, OperationResult parentResult) assertState(MappingEvaluationState.UNINITIALIZED); try { - parseSources(task, result); - - parseTarget(); - if (outputPath != null && outputDefinition == null) { - throw new IllegalArgumentException("No output definition, cannot evaluate " + getMappingContextDescription()); - } + parser.parseSourcesAndTarget(result); - } catch (ExpressionEvaluationException | ObjectNotFoundException | RuntimeException | SchemaException | - CommunicationException | SecurityViolationException | ConfigurationException | Error e) { - result.recordFatalError(e); - throw e; + } catch (Throwable t) { + result.recordFatalError(t); + throw t; } transitionState(MappingEvaluationState.PREPARED); result.recordSuccess(); } - private void traceSources() throws SchemaException { - for (Source source : sources) { - MappingSourceEvaluationTraceType sourceTrace = new MappingSourceEvaluationTraceType(prismContext); - sourceTrace.setName(source.getName()); - sourceTrace.setItemDeltaItem(source.toItemDeltaItemType(prismContext)); - trace.getSource().add(sourceTrace); - } - } - public boolean isActivated() { - // TODO -// return isActivated; return sourcesChanged(); } - // TODO: rename to evaluate -- or evaluatePrepared? - private void evaluateBody(Task task, OperationResult parentResult) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + private void evaluatePrepared(OperationResult parentResult) throws ExpressionEvaluationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, + CommunicationException { assertState(MappingEvaluationState.PREPARED); @@ -499,77 +649,73 @@ private void evaluateBody(Task task, OperationResult parentResult) throws Expres .setMinor() .build(); - traceEvaluationStart(); + recordEvaluationStart(); try { - - if (trace != null) { - traceSources(); - } + traceSources(); // We may need to re-parse the sources here - evaluateTimeConstraintValid(task, result); - if (trace != null) { - trace.setNextRecomputeTime(nextRecomputeTime); - trace.setTimeConstraintValid(timeConstraintValid); - } - - if (!timeConstraintValid) { - outputTriple = null; - result.recordNotApplicableIfUnknown(); - traceDeferred(); - return; - } - - evaluateCondition(task, result); + evaluateTimeConstraint(result); - boolean conditionOutputOld = computeConditionResult(conditionOutputTriple == null ? null : conditionOutputTriple.getNonPositiveValues()); - boolean conditionResultOld = conditionOutputOld && conditionMaskOld; + // We have to evaluate condition even for mappings that are not time-valid. This is because we want + // to skip trigger creation for mappings that do not satisfy the condition (see MID-6040). + evaluateCondition(result); - boolean conditionOutputNew = computeConditionResult(conditionOutputTriple == null ? null : conditionOutputTriple.getNonNegativeValues()); - boolean conditionResultNew = conditionOutputNew && conditionMaskNew; - - if (trace != null) { - trace.setConditionResultOld(conditionResultOld); - trace.setConditionResultNew(conditionResultNew); - } + if (isTimeConstraintValid()) { + if (isConditionSatisfied()) { + evaluateExpression(result); + applyDefinitionToOutputTriple(); + recomputeValues(); + setOrigin(); + adjustForAuthoritative(); + } else { + outputTriple = null; + } + checkRange(result); // we check the range even for not-applicable mappings (MID-5953) + transitionState(MappingEvaluationState.EVALUATED); - boolean applicable = conditionResultOld || conditionResultNew; - if (applicable) { - // TODO trace source and target values ... and range processing - evaluateExpression(task, result, conditionResultOld, conditionResultNew); - fixDefinition(); - recomputeValues(); - setOrigin(); - adjustForAuthoritative(); + if (isConditionSatisfied()) { + result.recordSuccess(); + traceSuccess(); + } else { + result.recordNotApplicableIfUnknown(); + traceNotApplicable("condition is false"); + } + traceOutput(); } else { outputTriple = null; - } - checkRange(task, result); // we check the range even for not-applicable mappings (MID-5953) - transitionState(MappingEvaluationState.EVALUATED); - - if (applicable) { - result.recordSuccess(); - traceSuccess(conditionResultOld, conditionResultNew); - } else { result.recordNotApplicableIfUnknown(); - traceNotApplicable("condition is false"); - } - - if (trace != null) { - traceOutput(); + traceDeferred(); } - } catch (Throwable e) { result.recordFatalError(e); traceFailure(e); throw e; + } finally { + recordEvaluationEnd(); + } + } + + private void traceTimeConstraintValidity() { + if (trace != null) { + trace.setNextRecomputeTime(getNextRecomputeTime()); + trace.setTimeConstraintValid(isTimeConstraintValid()); + } + } + + private void traceSources() throws SchemaException { + if (trace != null) { + for (Source source : sources) { + trace.beginSource() + .name(source.getName()) + .itemDeltaItem(source.toItemDeltaItemType(prismContext)); + } } } private void traceOutput() { - if (outputTriple != null) { + if (trace != null && outputTriple != null) { trace.setOutput(DeltaSetTripleType.fromDeltaSetTriple(outputTriple, prismContext)); } } @@ -589,27 +735,27 @@ private void adjustForAuthoritative() { outputTriple.clearMinusSet(); } - private void checkRange(Task task, OperationResult result) + private void checkRange(OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - VariableBindingDefinitionType target = mappingType.getTarget(); + VariableBindingDefinitionType target = mappingBean.getTarget(); if (target != null && target.getSet() != null) { - checkRangeTarget(task, result); + checkRangeTarget(result); } } - private void checkRangeTarget(Task task, OperationResult result) + private void checkRangeTarget(OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { String name; - if (outputPath != null) { - name = outputPath.lastName().getLocalPart(); + if (getOutputPath() != null) { + name = getOutputPath().lastName().getLocalPart(); } else { - name = outputDefinition.getItemName().getLocalPart(); + name = getOutputDefinition().getItemName().getLocalPart(); } if (originalTargetValues == null) { throw new IllegalStateException("Couldn't check range for mapping in " + contextDescription + ", as original target values are not known."); } - ValueSetDefinitionType rangetSetDefType = mappingType.getTarget().getSet(); - ValueSetDefinition setDef = new ValueSetDefinition<>(rangetSetDefType, outputDefinition, expressionProfile, name, "range of " + name + " in " + getMappingContextDescription(), task, result); + ValueSetDefinitionType rangeSetDefType = mappingBean.getTarget().getSet(); + ValueSetDefinition setDef = new ValueSetDefinition<>(rangeSetDefType, getOutputDefinition(), expressionProfile, name, "range of " + name + " in " + getMappingContextDescription(), task, result); setDef.init(expressionFactory); setDef.setAdditionalVariables(variables); for (V originalValue : originalTargetValues) { @@ -627,43 +773,33 @@ private void addToMinusIfNecessary(V originalValue) { } // remove it! if (outputTriple == null) { - outputTriple = getPrismContext().deltaFactory().createPrismValueDeltaSetTriple(); + outputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple(); } LOGGER.trace("Original value is in the mapping range (while not in mapping result), adding it to minus set: {}", originalValue); outputTriple.addToMinusSet((V) originalValue.clone()); } - @SuppressWarnings("unused") // todo is this externally used? - public boolean isSatisfyCondition() { - if (conditionOutputTriple == null) { - return true; - } - boolean conditionOutputOld = computeConditionResult(conditionOutputTriple.getNonPositiveValues()); - boolean conditionResultOld = conditionOutputOld && conditionMaskOld; - - boolean conditionOutputNew = computeConditionResult(conditionOutputTriple.getNonNegativeValues()); - boolean conditionResultNew = conditionOutputNew && conditionMaskNew; - return (conditionResultOld || conditionResultNew); + public boolean isConditionSatisfied() { + return conditionResultOld || conditionResultNew; } public PrismValueDeltaSetTriple> getConditionOutputTriple() { return conditionOutputTriple; } - private void traceEvaluationStart() { + private void recordEvaluationStart() { if (profiling) { evaluationStartTime = System.currentTimeMillis(); } } - private void traceEvaluationEnd() { + private void recordEvaluationEnd() { if (profiling) { evaluationEndTime = System.currentTimeMillis(); } } - private void traceSuccess(boolean conditionResultOld, boolean conditionResultNew) { - traceEvaluationEnd(); + private void traceSuccess() { if (!isTrace()) { return; } @@ -671,9 +807,9 @@ private void traceSuccess(boolean conditionResultOld, boolean conditionResultNew sb.append("Mapping trace:\n"); appendTraceHeader(sb); sb.append("\nCondition: ").append(conditionResultOld).append(" -> ").append(conditionResultNew); - if (nextRecomputeTime != null) { + if (getNextRecomputeTime() != null) { sb.append("\nNext recompute: "); - sb.append(nextRecomputeTime); + sb.append(getNextRecomputeTime()); } sb.append("\nResult: "); if (outputTriple == null) { @@ -691,18 +827,23 @@ private void traceSuccess(boolean conditionResultOld, boolean conditionResultNew } private void traceDeferred() { - traceEvaluationEnd(); if (!isTrace()) { return; } StringBuilder sb = new StringBuilder(); sb.append("Mapping trace:\n"); appendTraceHeader(sb); - sb.append("\nEvaluation DEFERRED to: "); - if (nextRecomputeTime == null) { - sb.append("null"); + sb.append("\nCondition: ").append(conditionResultOld).append(" -> ").append(conditionResultNew); + + sb.append("\nEvaluation "); + if (!isConditionSatisfied()) { + sb.append("WOULD BE "); + } + sb.append("DEFERRED to: "); + if (getNextRecomputeTime() == null) { + sb.append("null"); } else { - sb.append(nextRecomputeTime); + sb.append(getNextRecomputeTime()); } if (profiling) { sb.append("\nEtime: "); @@ -715,7 +856,6 @@ private void traceDeferred() { @SuppressWarnings("SameParameterValue") private void traceNotApplicable(String reason) { - traceEvaluationEnd(); if (!isTrace()) { return; } @@ -734,7 +874,6 @@ private void traceNotApplicable(String reason) { private void traceFailure(Throwable e) { LOGGER.error("Error evaluating {}: {}-{}", getMappingContextDescription(), e.getMessage(), e); - traceEvaluationEnd(); if (!isTrace()) { return; } @@ -753,11 +892,11 @@ private void traceFailure(Throwable e) { @SuppressWarnings("BooleanMethodIsAlwaysInverted") private boolean isTrace() { - return trace != null || LOGGER.isTraceEnabled() || (mappingType != null && mappingType.isTrace() == Boolean.TRUE); + return trace != null || LOGGER.isTraceEnabled() || mappingBean.isTrace() == Boolean.TRUE; } private void trace(String msg) { - if (mappingType != null && mappingType.isTrace() == Boolean.TRUE) { + if (mappingBean.isTrace() == Boolean.TRUE) { LOGGER.info(msg); } else { LOGGER.trace(msg); @@ -769,8 +908,8 @@ private void trace(String msg) { private void appendTraceHeader(StringBuilder sb) { sb.append("---[ MAPPING "); - if (mappingType.getName() != null) { - sb.append("'").append(mappingType.getName()).append("' "); + if (mappingBean.getName() != null) { + sb.append("'").append(mappingBean.getName()).append("' "); } sb.append(" in "); sb.append(contextDescription); @@ -786,7 +925,7 @@ private void appendTraceHeader(StringBuilder sb) { sb.append("\n"); source.mediumDump(sb); } - sb.append("\nTarget: ").append(MiscUtil.toString(outputDefinition)); + sb.append("\nTarget: ").append(MiscUtil.toString(getOutputDefinition())); sb.append("\nExpression: "); if (expression == null) { sb.append("null"); @@ -805,268 +944,17 @@ private void appendTraceFooter(StringBuilder sb) { private boolean computeConditionResult(Collection> booleanPropertyValues) { // If condition is not present at all consider it to be true - return mappingType.getCondition() == null || ExpressionUtil.computeConditionResult(booleanPropertyValues); - } - - public boolean evaluateTimeConstraintValid(Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (timeConstraintValid == null) { - timeConstraintValid = parseTimeConstraints(task, result); - } - return timeConstraintValid; - } - - // Sets nextRecomputeTime as a side effect. - private boolean parseTimeConstraints(Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - MappingTimeDeclarationType timeFromType = mappingType.getTimeFrom(); - MappingTimeDeclarationType timeToType = mappingType.getTimeTo(); - if (timeFromType == null && timeToType == null) { - return true; - } - - XMLGregorianCalendar timeFrom = parseTime(timeFromType, task, result); - if (trace != null) { - trace.setTimeFrom(timeFrom); - } - if (timeFrom == null && timeFromType != null) { - // Time is specified but there is no value for it. - // This means that event that should start validity haven't happened yet - // therefore the mapping is not yet valid. - return false; - } - XMLGregorianCalendar timeTo = parseTime(timeToType, task, result); - if (trace != null) { - trace.setTimeTo(timeTo); - } - - if (timeFrom != null && timeFrom.compare(now) == DatatypeConstants.GREATER) { - // before timeFrom - nextRecomputeTime = timeFrom; - return false; - } - - if (timeTo == null && timeToType != null) { - // Time is specified but there is no value for it. - // This means that event that should stop validity haven't happened yet - // therefore the mapping is still valid. - return true; - } - - if (timeTo != null && timeTo.compare(now) == DatatypeConstants.GREATER) { - // between timeFrom and timeTo (also no timeFrom and before timeTo) - nextRecomputeTime = timeTo; - return true; - } - - // If timeTo is null, we are "in range" - // Otherwise it is less than now (so we are after it), i.e. we are "out of range" - // In both cases there is nothing to recompute in the future - return timeTo == null; - } - - private XMLGregorianCalendar parseTime(MappingTimeDeclarationType timeType, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (timeType == null) { - return null; - } - XMLGregorianCalendar referenceTime; - ExpressionType expressionType = timeType.getExpression(); - VariableBindingDefinitionType referenceTimeType = timeType.getReferenceTime(); - if (referenceTimeType == null) { - if (defaultReferenceTime == null) { - if (expressionType == null) { - throw new SchemaException("No reference time specified, there is also no default and no expression; in time specification in " + getMappingContextDescription()); - } else { - referenceTime = null; - } - } else { - referenceTime = defaultReferenceTime; - } - } else { - referenceTime = parseTimeSource(referenceTimeType, task, result); - } - - XMLGregorianCalendar time; - if (expressionType == null) { - if (referenceTime == null) { - return null; - } else { - time = (XMLGregorianCalendar) referenceTime.clone(); - } - } else { - MutablePrismPropertyDefinition timeDefinition = prismContext.definitionFactory().createPropertyDefinition( - ExpressionConstants.OUTPUT_ELEMENT_NAME, PrimitiveType.XSD_DATETIME); - timeDefinition.setMaxOccurs(1); - - ExpressionVariables timeVariables = new ExpressionVariables(); - timeVariables.addVariableDefinitions(variables); - timeVariables.addVariableDefinition(ExpressionConstants.VAR_REFERENCE_TIME, referenceTime, timeDefinition); - - PrismPropertyValue timePropVal = ExpressionUtil.evaluateExpression(sources, timeVariables, timeDefinition, expressionType, expressionProfile, expressionFactory, "time expression in " + contextDescription, task, result); - - if (timePropVal == null) { - return null; - } - - time = timePropVal.getValue(); - } - Duration offset = timeType.getOffset(); - if (offset != null) { - time.add(offset); - } - return time; - } - - private XMLGregorianCalendar parseTimeSource(VariableBindingDefinitionType source, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - ItemPath path = getSourcePath(source); - - Object sourceObject = ExpressionUtil.resolvePathGetValue(path, variables, false, getTypedSourceContext(), objectResolver, getPrismContext(), "reference time definition in " + getMappingContextDescription(), task, result); - if (sourceObject == null) { - return null; - } - PrismProperty timeProperty; - if (sourceObject instanceof ItemDeltaItem) { - //noinspection unchecked - timeProperty = (PrismProperty) ((ItemDeltaItem) sourceObject).getItemNew(); - } else if (sourceObject instanceof Item) { - //noinspection unchecked - timeProperty = (PrismProperty) sourceObject; - } else { - throw new IllegalStateException("Unknown resolve result " + sourceObject); - } - return timeProperty != null ? timeProperty.getRealValue() : null; - } - - private void parseSources(Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, SecurityViolationException, - ConfigurationException, CommunicationException { - List sourceDefinitions = mappingType.getSource(); - if (defaultSource != null) { - defaultSource.recompute(); - this.sources.add(defaultSource); - defaultSource.recompute(); - } - if (sourceDefinitions != null) { - for (VariableBindingDefinitionType sourceDefinition : sourceDefinitions) { - Source source = parseSource(sourceDefinition, task, result); - source.recompute(); - - // Override existing sources (e.g. default source) - this.sources.removeIf(next -> next.getName().equals(source.getName())); - this.sources.add(source); - } - } - } - - private Source parseSource( - VariableBindingDefinitionType sourceDefinition, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - ItemPath path = getSourcePath(sourceDefinition); - @NotNull QName sourceQName = sourceDefinition.getName() != null ? sourceDefinition.getName() : ItemPath.toName(path.last()); - String variableName = sourceQName.getLocalPart(); - - TypedValue typedSourceObject = ExpressionUtil.resolvePathGetTypedValue(path, variables, true, - getTypedSourceContext(), objectResolver, getPrismContext(), - "source definition in " + getMappingContextDescription(), task, result); - - Object sourceObject = typedSourceObject != null ? typedSourceObject.getValue() : null; - Item itemOld = null; - ItemDelta delta = null; - Item itemNew = null; - ItemPath resolvePath = path; - ItemPath residualPath = null; - Collection> subItemDeltas = null; - if (sourceObject != null) { - if (sourceObject instanceof ItemDeltaItem) { - //noinspection unchecked - itemOld = ((ItemDeltaItem) sourceObject).getItemOld(); - //noinspection unchecked - delta = ((ItemDeltaItem) sourceObject).getDelta(); - //noinspection unchecked - itemNew = ((ItemDeltaItem) sourceObject).getItemNew(); - //noinspection unchecked - residualPath = ((ItemDeltaItem) sourceObject).getResidualPath(); - //noinspection unchecked - resolvePath = ((ItemDeltaItem) sourceObject).getResolvePath(); - //noinspection unchecked - subItemDeltas = ((ItemDeltaItem) sourceObject).getSubItemDeltas(); - } else if (sourceObject instanceof Item) { - //noinspection unchecked - itemOld = (Item) sourceObject; - //noinspection unchecked - itemNew = (Item) sourceObject; - } else { - throw new IllegalStateException("Unknown resolve result " + sourceObject); - } - } - - ID sourceItemDefinition = typedSourceObject != null ? typedSourceObject.getDefinition() : null; - - // apply domain - ValueSetDefinitionType domainSetType = sourceDefinition.getSet(); - if (domainSetType != null) { - ValueSetDefinition setDef = new ValueSetDefinition<>( - domainSetType, sourceItemDefinition, expressionProfile, variableName, - "domain of " + variableName + " in " + getMappingContextDescription(), - task, result); - setDef.init(expressionFactory); - setDef.setAdditionalVariables(variables); - try { - - if (itemOld != null) { - //noinspection unchecked - itemOld = itemOld.clone(); - itemOld.filterValues(val -> setDef.containsTunnel(val)); - } - - if (itemNew != null) { - //noinspection unchecked - itemNew = itemNew.clone(); - itemNew.filterValues(val -> setDef.containsTunnel(val)); - } - - if (delta != null) { - delta = delta.clone(); - delta.filterValues(val -> setDef.containsTunnel(val)); - } - - } catch (TunnelException te) { - Throwable cause = te.getCause(); - if (cause instanceof SchemaException) { - throw (SchemaException) cause; - } else if (cause instanceof ExpressionEvaluationException) { - throw (ExpressionEvaluationException) cause; - } else if (cause instanceof ObjectNotFoundException) { - throw (ObjectNotFoundException) cause; - } else if (cause instanceof CommunicationException) { - throw (CommunicationException) cause; - } else if (cause instanceof ConfigurationException) { - throw (ConfigurationException) cause; - } else if (cause instanceof SecurityViolationException) { - throw (SecurityViolationException) cause; - } - } - } - - Source source = new Source<>(itemOld, delta, itemNew, sourceQName, sourceItemDefinition); - source.setResidualPath(residualPath); - source.setResolvePath(resolvePath); - source.setSubItemDeltas(subItemDeltas); - return source; + return mappingBean.getCondition() == null || ExpressionUtil.computeConditionResult(booleanPropertyValues); } - @NotNull - private ItemPath getSourcePath(VariableBindingDefinitionType sourceType) throws SchemaException { - ItemPathType itemPathType = sourceType.getPath(); - if (itemPathType == null) { - throw new SchemaException("No path in source definition in " + getMappingContextDescription()); + private void evaluateTimeConstraint(OperationResult result) throws SchemaException, ObjectNotFoundException, + CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (timeConstraintsEvaluation == null) { + timeConstraintsEvaluation = new TimeConstraintsEvaluation(this); + timeConstraintsEvaluation.evaluate(result); + traceTimeConstraintValidity(); } - ItemPath path = itemPathType.getItemPath(); - if (path.isEmpty()) { - throw new SchemaException("Empty source path in " + getMappingContextDescription()); - } - return path; + timeConstraintsEvaluation.isTimeConstraintValid(); } private boolean sourcesChanged() { @@ -1078,57 +966,23 @@ private boolean sourcesChanged() { return false; } - private void parseTarget() throws SchemaException { - VariableBindingDefinitionType targetType = mappingType.getTarget(); - if (targetType == null) { - outputDefinition = defaultTargetDefinition; - outputPath = defaultTargetPath; - } else { - ItemPathType itemPathType = targetType.getPath(); - if (itemPathType == null) { - outputDefinition = defaultTargetDefinition; - outputPath = defaultTargetPath; - } else { - ItemPath path = itemPathType.getItemPath(); - outputDefinition = ExpressionUtil.resolveDefinitionPath( - path, variables, targetContext, - "target definition in " + getMappingContextDescription()); - if (outputDefinition == null) { - throw new SchemaException("No target item that would conform to the path " - + path + " in " + getMappingContextDescription()); - } - outputPath = path.stripVariableSegment(); - } - } - if (stringPolicyResolver != null) { - stringPolicyResolver.setOutputDefinition(outputDefinition); - stringPolicyResolver.setOutputPath(outputPath); - } - } - - public D getOutputDefinition() throws SchemaException { - if (outputDefinition == null) { - parseTarget(); - } - return outputDefinition; + public D getOutputDefinition() { + return parser.getOutputDefinition(); } - public ItemPath getOutputPath() throws SchemaException { - if (outputDefinition == null) { - parseTarget(); - } - return outputPath; + public ItemPath getOutputPath() { + return parser.getOutputPath(); } /** * Applies definition to the output if needed. */ - private void fixDefinition() throws SchemaException { + private void applyDefinitionToOutputTriple() throws SchemaException { if (outputTriple == null) { return; } if (outputTriple.isRaw()) { - outputTriple.applyDefinition(outputDefinition); + outputTriple.applyDefinition(getOutputDefinition()); } } @@ -1138,7 +992,7 @@ private void recomputeValues() { } Visitor visitor = visitable -> { if (visitable instanceof PrismValue) { - ((PrismValue) visitable).recompute(getPrismContext()); + ((PrismValue) visitable).recompute(prismContext); } }; outputTriple.accept(visitor); @@ -1156,50 +1010,61 @@ private void setOrigin() { } } - private void evaluateCondition(Task task, OperationResult result) + private void evaluateCondition(OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType conditionExpressionType = mappingType.getCondition(); + + computeConditionTriple(result); + + boolean conditionOutputOld = computeConditionResult(conditionOutputTriple == null ? null : conditionOutputTriple.getNonPositiveValues()); + conditionResultOld = conditionOutputOld && conditionMaskOld; + + boolean conditionOutputNew = computeConditionResult(conditionOutputTriple == null ? null : conditionOutputTriple.getNonNegativeValues()); + conditionResultNew = conditionOutputNew && conditionMaskNew; + + if (trace != null) { + trace.setConditionResultOld(conditionResultOld); + trace.setConditionResultNew(conditionResultNew); + } + } + + private void computeConditionTriple(OperationResult result) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, + ExpressionEvaluationException, + CommunicationException, + ConfigurationException { + ExpressionType conditionExpressionType = mappingBean.getCondition(); if (conditionExpressionType == null) { // True -> True - conditionOutputTriple = getPrismContext().deltaFactory().createPrismValueDeltaSetTriple(); - conditionOutputTriple.addToZeroSet(getPrismContext().itemFactory().createPropertyValue(Boolean.TRUE)); - return; + conditionOutputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple(); + conditionOutputTriple.addToZeroSet(prismContext.itemFactory().createPropertyValue(Boolean.TRUE)); + } else { + Expression, PrismPropertyDefinition> expression = + ExpressionUtil.createCondition(conditionExpressionType, expressionProfile, expressionFactory, + "condition in " + getMappingContextDescription(), task, result); + ExpressionEvaluationContext context = new ExpressionEvaluationContext(sources, variables, + "condition in " + getMappingContextDescription(), task); + context.setValuePolicyResolver(valuePolicyResolver); + context.setExpressionFactory(expressionFactory); + context.setDefaultSource(defaultSource); + context.setMappingQName(mappingQName); + context.setVariableProducer(variableProducer); + conditionOutputTriple = expression.evaluate(context, result); } - Expression, PrismPropertyDefinition> expression = - ExpressionUtil.createCondition(conditionExpressionType, expressionProfile, expressionFactory, - "condition in " + getMappingContextDescription(), task, result); - ExpressionEvaluationContext context = new ExpressionEvaluationContext(sources, variables, - "condition in " + getMappingContextDescription(), task); - context.setValuePolicyResolver(stringPolicyResolver); - context.setExpressionFactory(expressionFactory); - context.setDefaultSource(defaultSource); - context.setDefaultTargetContext(getTargetContext()); - context.setRefinedObjectClassDefinition(getRefinedObjectClassDefinition()); - context.setMappingQName(mappingQName); - context.setVariableProducer(variableProducer); - conditionOutputTriple = expression.evaluate(context, result); } - private void evaluateExpression(Task task, OperationResult result, - boolean conditionResultOld, boolean conditionResultNew) + private void evaluateExpression(OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType expressionType = null; - if (mappingType != null) { - expressionType = mappingType.getExpression(); - } - expression = expressionFactory.makeExpression(expressionType, outputDefinition, expressionProfile, + expression = expressionFactory.makeExpression(mappingBean.getExpression(), getOutputDefinition(), expressionProfile, "expression in " + getMappingContextDescription(), task, result); ExpressionEvaluationContext context = new ExpressionEvaluationContext(sources, variables, "expression in " + getMappingContextDescription(), task); context.setDefaultSource(defaultSource); context.setSkipEvaluationMinus(!conditionResultOld); context.setSkipEvaluationPlus(!conditionResultNew); - context.setValuePolicyResolver(stringPolicyResolver); + context.setValuePolicyResolver(valuePolicyResolver); context.setExpressionFactory(expressionFactory); - context.setDefaultTargetContext(getTargetContext()); - context.setRefinedObjectClassDefinition(getRefinedObjectClassDefinition()); context.setMappingQName(mappingQName); context.setVariableProducer(variableProducer); @@ -1217,7 +1082,7 @@ private void evaluateExpression(Task task, OperationResult result, // so the mapping is applicable. // Returning null would mean that the mapping is not applicable // at all. - outputTriple = getPrismContext().deltaFactory().createPrismValueDeltaSetTriple(); + outputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple(); } } else { @@ -1238,9 +1103,6 @@ private void evaluateExpression(Task task, OperationResult result, } } - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer#getOutputTriple() - */ @Override public PrismValueDeltaSetTriple getOutputTriple() { if (outputTriple != null && InternalsConfig.consistencyChecks) { @@ -1258,16 +1120,11 @@ public Item getOutput() throws SchemaException { return null; } //noinspection unchecked - Item output = outputDefinition.instantiate(); + Item output = getOutputDefinition().instantiate(); output.addAll(PrismValueCollectionsUtil.cloneCollection(outputTriple.getNonNegativeValues())); return output; } - public ItemDelta createEmptyDelta(ItemPath path) { - //noinspection unchecked - return outputDefinition.createEmptyDelta(path); - } - private void transitionState(MappingEvaluationState newState) { state = newState; } @@ -1283,38 +1140,7 @@ private void assertState(MappingEvaluationState expectedState) { */ @SuppressWarnings("MethodDoesntCallSuperMethod") public PrismValueDeltaSetTripleProducer clone() { - MappingImpl clone = new Builder() - .mappingType(mappingType) - .mappingKind(mappingKind) - .implicitSourcePath(implicitSourcePath) - .implicitTargetPath(implicitTargetPath) - .contextDescription(contextDescription) - .expressionFactory(expressionFactory) - .securityContextManager(securityContextManager) - .variables(variables) - .conditionMaskNew(conditionMaskNew) - .conditionMaskOld(conditionMaskOld) - .defaultSource(defaultSource) - .defaultTargetDefinition(defaultTargetDefinition) - .expressionProfile(expressionProfile) - .objectResolver(objectResolver) - .originObject(originObject) - .originType(originType) - .sourceContext(sourceContext) - .sources(sources) - .targetContext(targetContext) - .build(); - - clone.outputDefinition = outputDefinition; - clone.outputPath = outputPath; - - if (this.outputTriple != null) { - clone.outputTriple = this.outputTriple.clone(); - } - if (this.conditionOutputTriple != null) { - clone.conditionOutputTriple = this.conditionOutputTriple.clone(); - } - return clone; + return new MappingImpl<>(this); } @Override @@ -1328,15 +1154,14 @@ public int hashCode() { result = prime * result + ((defaultTargetDefinition == null) ? 0 : defaultTargetDefinition.hashCode()); result = prime * result + ((expressionProfile == null) ? 0 : expressionProfile.hashCode()); result = prime * result + ((expressionFactory == null) ? 0 : expressionFactory.hashCode()); - result = prime * result + ((mappingType == null) ? 0 : mappingType.hashCode()); + result = prime * result + mappingBean.hashCode(); result = prime * result + ((objectResolver == null) ? 0 : objectResolver.hashCode()); result = prime * result + ((originObject == null) ? 0 : originObject.hashCode()); result = prime * result + ((originType == null) ? 0 : originType.hashCode()); - result = prime * result + ((outputDefinition == null) ? 0 : outputDefinition.hashCode()); result = prime * result + ((outputTriple == null) ? 0 : outputTriple.hashCode()); result = prime * result + ((contextDescription == null) ? 0 : contextDescription.hashCode()); result = prime * result + ((sourceContext == null) ? 0 : sourceContext.hashCode()); - result = prime * result + ((sources == null) ? 0 : sources.hashCode()); + result = prime * result + sources.hashCode(); result = prime * result + ((targetContext == null) ? 0 : targetContext.hashCode()); result = prime * result + ((variables == null) ? 0 : variables.hashCode()); return result; @@ -1366,9 +1191,7 @@ public boolean equals(Object obj) { if (expressionFactory == null) { if (other.expressionFactory != null) { return false; } } else if (!expressionFactory.equals(other.expressionFactory)) { return false; } - if (mappingType == null) { - if (other.mappingType != null) { return false; } - } else if (!mappingType.equals(other.mappingType)) { return false; } + if (!mappingBean.equals(other.mappingBean)) { return false; } if (objectResolver == null) { if (other.objectResolver != null) { return false; } } else if (!objectResolver.equals(other.objectResolver)) { return false; } @@ -1376,9 +1199,6 @@ public boolean equals(Object obj) { if (other.originObject != null) { return false; } } else if (!originObject.equals(other.originObject)) { return false; } if (originType != other.originType) { return false; } - if (outputDefinition == null) { - if (other.outputDefinition != null) { return false; } - } else if (!outputDefinition.equals(other.outputDefinition)) { return false; } if (outputTriple == null) { if (other.outputTriple != null) { return false; } } else if (!outputTriple.equals(other.outputTriple)) { return false; } @@ -1388,9 +1208,7 @@ public boolean equals(Object obj) { if (sourceContext == null) { if (other.sourceContext != null) { return false; } } else if (!sourceContext.equals(other.sourceContext)) { return false; } - if (sources == null) { - if (other.sources != null) { return false; } - } else if (!sources.equals(other.sources)) { return false; } + if (!sources.equals(other.sources)) { return false; } if (targetContext == null) { if (other.targetContext != null) { return false; } } else if (!targetContext.equals(other.targetContext)) { return false; } @@ -1410,8 +1228,8 @@ public String debugDump(int indent) { @Override public String toString() { - if (mappingType != null && mappingType.getName() != null) { - return "M(" + mappingType.getName() + ": " + getMappingDisplayName() + " = " + outputTriple + toStringStrength() + ")"; + if (mappingBean.getName() != null) { + return "M(" + mappingBean.getName() + ": " + getMappingDisplayName() + " = " + outputTriple + toStringStrength() + ")"; } else { return "M(" + getMappingDisplayName() + " = " + outputTriple + toStringStrength() + ")"; } @@ -1421,6 +1239,7 @@ private String getMappingDisplayName() { if (mappingQName != null) { return SchemaDebugUtil.prettyPrint(mappingQName); } + D outputDefinition = getOutputDefinition(); if (outputDefinition == null) { return null; } @@ -1441,15 +1260,15 @@ private String toStringStrength() { @Override public String getIdentifier() { - return mappingType != null ? mappingType.getName() : null; + return mappingBean.getName(); } @Override public String toHumanReadableDescription() { StringBuilder sb = new StringBuilder(); sb.append("mapping "); - if (mappingType != null && mappingType.getName() != null) { - sb.append("'").append(mappingType.getName()).append("'"); + if (mappingBean.getName() != null) { + sb.append("'").append(mappingBean.getName()).append("'"); } else { sb.append(getMappingDisplayName()); } @@ -1460,430 +1279,19 @@ public String toHumanReadableDescription() { return sb.toString(); } - /** - * Builder is used to construct a configuration of Mapping object, which - after building - becomes - * immutable. - *

- * In order to provide backward-compatibility with existing use of Mapping object, the builder has - * also traditional setter methods. Both setters and "builder-style" methods MODIFY existing Builder - * object (i.e. they do not create a new one). - *

- * TODO decide on which style of setters to keep (setters vs builder-style). - */ - @SuppressWarnings({ "unused", "BooleanMethodIsAlwaysInverted", "UnusedReturnValue" }) - public static final class Builder { - private ExpressionFactory expressionFactory; - private ExpressionVariables variables = new ExpressionVariables(); - private MappingType mappingType; - private MappingKindType mappingKind; - private ItemPath implicitSourcePath; // for tracing purposes - private ItemPath implicitTargetPath; // for tracing purposes - private ObjectResolver objectResolver; - private SecurityContextManager securityContextManager; - private Source defaultSource; - private D defaultTargetDefinition; - private ExpressionProfile expressionProfile; - private ItemPath defaultTargetPath; - private Collection originalTargetValues; - private ObjectDeltaObject sourceContext; - private PrismObjectDefinition targetContext; - private Collection> sources = new ArrayList<>(); - private OriginType originType; - private ObjectType originObject; - private ValuePolicyResolver valuePolicyResolver; - private VariableProducer variableProducer; - private MappingPreExpression mappingPreExpression; - private boolean conditionMaskOld = true; - private boolean conditionMaskNew = true; - private XMLGregorianCalendar now; - private XMLGregorianCalendar defaultReferenceTime; - private boolean profiling; - private String contextDescription; - private QName mappingQName; - private RefinedObjectClassDefinition refinedObjectClassDefinition; - private PrismContext prismContext; - - public Builder expressionFactory(ExpressionFactory val) { - expressionFactory = val; - return this; - } - - public Builder variables(ExpressionVariables val) { - variables = val; - return this; - } - - public Builder mappingType(MappingType val) { - mappingType = val; - return this; - } - - public Builder mappingKind(MappingKindType val) { - mappingKind = val; - return this; - } - - public Builder implicitSourcePath(ItemPath val) { - implicitSourcePath = val; - return this; - } - - public Builder implicitTargetPath(ItemPath val) { - implicitTargetPath = val; - return this; - } - - public Builder objectResolver(ObjectResolver val) { - objectResolver = val; - return this; - } - - public Builder securityContextManager(SecurityContextManager val) { - securityContextManager = val; - return this; - } - - public Builder defaultSource(Source val) { - defaultSource = val; - return this; - } - - public Builder defaultTargetDefinition(D val) { - defaultTargetDefinition = val; - return this; - } - - public Builder expressionProfile(ExpressionProfile val) { - expressionProfile = val; - return this; - } - - public Builder defaultTargetPath(ItemPath val) { - defaultTargetPath = val; - return this; - } - - public Builder originalTargetValues(Collection values) { - originalTargetValues = values; - return this; - } - - public Builder sourceContext(ObjectDeltaObject val) { - if (val.getDefinition() == null) { - throw new IllegalArgumentException("Attempt to set mapping source context without a definition"); - } - sourceContext = val; - return this; - } - - public Builder targetContext(PrismObjectDefinition val) { - targetContext = val; - return this; - } - - public Builder sources(Collection> val) { - sources = val; - return this; - } - - public Builder originType(OriginType val) { - originType = val; - return this; - } - - public Builder originObject(ObjectType val) { - originObject = val; - return this; - } - - public Builder valuePolicyResolver(ValuePolicyResolver val) { - valuePolicyResolver = val; - return this; - } - - public Builder variableResolver(VariableProducer variableProducer) { - this.variableProducer = variableProducer; - return this; - } - - public Builder mappingPreExpression(MappingPreExpression mappingPreExpression) { - this.mappingPreExpression = mappingPreExpression; - return this; - } - - public Builder conditionMaskOld(boolean val) { - conditionMaskOld = val; - return this; - } - - public Builder conditionMaskNew(boolean val) { - conditionMaskNew = val; - return this; - } - - public Builder now(XMLGregorianCalendar val) { - now = val; - return this; - } - - public Builder defaultReferenceTime(XMLGregorianCalendar val) { - defaultReferenceTime = val; - return this; - } - - public Builder profiling(boolean val) { - profiling = val; - return this; - } - - public Builder contextDescription(String val) { - contextDescription = val; - return this; - } - - public Builder mappingQName(QName val) { - mappingQName = val; - return this; - } - - public Builder refinedObjectClassDefinition(RefinedObjectClassDefinition val) { - refinedObjectClassDefinition = val; - return this; - } - - public Builder prismContext(PrismContext val) { - prismContext = val; - return this; - } - - public MappingImpl build() { - return new MappingImpl<>(this); - } - - public ExpressionFactory getExpressionFactory() { - return expressionFactory; - } - - public ExpressionVariables getVariables() { - return variables; - } - - public MappingType getMappingType() { - return mappingType; - } - - public ObjectResolver getObjectResolver() { - return objectResolver; - } - - public SecurityContextManager getSecurityContextManager() { - return securityContextManager; - } - - public Source getDefaultSource() { - return defaultSource; - } - - public D getDefaultTargetDefinition() { - return defaultTargetDefinition; - } - - public ItemPath getDefaultTargetPath() { - return defaultTargetPath; - } - - public Collection getOriginalTargetValues() { - return originalTargetValues; - } - - public ObjectDeltaObject getSourceContext() { - return sourceContext; - } - - public PrismObjectDefinition getTargetContext() { - return targetContext; - } - - public Collection> getSources() { - return sources; - } - - public OriginType getOriginType() { - return originType; - } - - public ObjectType getOriginObject() { - return originObject; - } - - public ValuePolicyResolver getValuePolicyResolver() { - return valuePolicyResolver; - } - - public VariableProducer getVariableProducer() { - return variableProducer; - } - - public boolean isConditionMaskOld() { - return conditionMaskOld; - } - - public boolean isConditionMaskNew() { - return conditionMaskNew; - } - - public XMLGregorianCalendar getNow() { - return now; - } - - public XMLGregorianCalendar getDefaultReferenceTime() { - return defaultReferenceTime; - } - - public boolean isProfiling() { - return profiling; - } - - public String getContextDescription() { - return contextDescription; - } - - public QName getMappingQName() { - return mappingQName; - } - - public RefinedObjectClassDefinition getRefinedObjectClassDefinition() { - return refinedObjectClassDefinition; - } - - public Builder rootNode(ObjectReferenceType objectRef) { - return addVariableDefinition(null, objectRef); - } - - public Builder rootNode(ObjectDeltaObject odo) { - return addVariableDefinition(null, odo); - } - - public Builder rootNode(O objectType, PrismObjectDefinition definition) { - variables.put(null, objectType, definition); - return this; - } - - public Builder rootNode(PrismObject mpObject, PrismObjectDefinition definition) { - variables.put(null, mpObject, definition); - return this; - } - - public PrismContext getPrismContext() { - return prismContext; - } - - public Builder addVariableDefinition(ExpressionVariableDefinitionType varDef) throws SchemaException { - if (varDef.getObjectRef() != null) { - ObjectReferenceType ref = varDef.getObjectRef(); - ref.setType(getPrismContext().getSchemaRegistry().qualifyTypeName(ref.getType())); - return addVariableDefinition(varDef.getName().getLocalPart(), ref); - } else if (varDef.getValue() != null) { - // This is raw value. We do have definition here. The best we can do is autodetect. - // Expression evaluation code will do that as a fallback behavior. - return addVariableDefinition(varDef.getName().getLocalPart(), varDef.getValue(), Object.class); - } else { - LOGGER.warn("Empty definition of variable {} in {}, ignoring it", varDef.getName(), getContextDescription()); - return this; - } - } - - public Builder addVariableDefinition(String name, ObjectReferenceType objectRef) { - return addVariableDefinition(name, objectRef, objectRef.asReferenceValue().getDefinition()); - } - - public Builder addVariableDefinition(String name, O objectType, Class expectedClass) { - // Maybe determine definition from schema registry here in case that object is null. We can do that here. - variables.putObject(name, objectType, expectedClass); - return this; - } - - public Builder addVariableDefinition(String name, PrismObject midpointObject, Class expectedClass) { - // Maybe determine definition from schema registry here in case that object is null. We can do that here. - variables.putObject(name, midpointObject, expectedClass); - return this; - } - - public Builder addVariableDefinition(String name, String value) { - MutablePrismPropertyDefinition def = prismContext.definitionFactory().createPropertyDefinition( - new QName(SchemaConstants.NS_C, name), PrimitiveType.STRING.getQname()); - return addVariableDefinition(name, value, def); - } - - public Builder addVariableDefinition(String name, boolean value) { - MutablePrismPropertyDefinition def = prismContext.definitionFactory().createPropertyDefinition( - new QName(SchemaConstants.NS_C, name), PrimitiveType.BOOLEAN.getQname()); - return addVariableDefinition(name, value, def); - } - - public Builder addVariableDefinition(String name, int value) { - MutablePrismPropertyDefinition def = prismContext.definitionFactory().createPropertyDefinition( - new QName(SchemaConstants.NS_C, name), PrimitiveType.INT.getQname()); - return addVariableDefinition(name, value, def); - } - -// public Builder addVariableDefinition(String name, Element value) { -// return addVariableDefinition(name, (Object)value); -// } - - public Builder addVariableDefinition(String name, PrismValue value) { - return addVariableDefinition(name, value, value.getParent().getDefinition()); - } - - public Builder addVariableDefinition(String name, ObjectDeltaObject value) { - PrismObjectDefinition definition = value.getDefinition(); - if (definition == null) { - throw new IllegalArgumentException("Attempt to set variable '" + name + "' as ODO without a definition: " + value); - } - return addVariableDefinition(name, value, definition); - } - - // mainVariable of "null" means the default source - public Builder addAliasRegistration(String alias, @Nullable String mainVariable) { - variables.registerAlias(alias, mainVariable); - return this; - } - - public Builder addVariableDefinitions(VariablesMap extraVariables) { - variables.putAll(extraVariables); - return this; - } - - public Builder addVariableDefinition(String name, Object value, ItemDefinition definition) { - variables.put(name, value, definition); - return this; - } - - public Builder addVariableDefinition(String name, Object value, Class typeClass) { - variables.put(name, value, typeClass); - return this; - } - - public Builder stringPolicyResolver(ValuePolicyResolver stringPolicyResolver) { - this.valuePolicyResolver = stringPolicyResolver; - return this; - } - - public boolean hasVariableDefinition(String varName) { - return variables.containsKey(varName); - } - - public boolean isApplicableToChannel(String channel) { - return MappingImpl.isApplicableToChannel(mappingType, channel); - } + public Task getTask() { + return task; + } - public Builder addSource(Source source) { - sources.add(source); - return this; + void traceTimeFrom(XMLGregorianCalendar timeFrom) { + if (trace != null) { + trace.setTimeFrom(timeFrom); } + } - public MappingStrengthType getStrength() { - return MappingImpl.getStrength(mappingType); + void traceTimeTo(XMLGregorianCalendar timeTo) { + if (trace != null) { + trace.setTimeTo(timeTo); } } } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingParser.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingParser.java new file mode 100644 index 00000000000..095ded222bb --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingParser.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2020 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.mapping; + +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.Source; +import com.evolveum.midpoint.repo.common.expression.ValueSetDefinition; +import com.evolveum.midpoint.schema.expression.TypedValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ValueSetDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType; + +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; + +import org.jetbrains.annotations.NotNull; + +import javax.xml.namespace.QName; +import java.util.Collection; + +/** + * TODO better name, clean up the code; maybe move operation result management code here + */ +class MappingParser { + + private final MappingImpl m; + + /** + * Definition of the output item (i.e. target). + */ + private D outputDefinition; + + /** + * Path of the output item (i.e. target) in the targetContext. + */ + private ItemPath outputPath; + + MappingParser(MappingImpl mapping) { + this.m = mapping; + } + + void parseSourcesAndTarget(OperationResult result) throws SchemaException, CommunicationException, ObjectNotFoundException, + ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + parseSources(result); + parseTarget(); + assertOutputDefinition(); + } + + private void assertOutputDefinition() { + if (outputPath != null && outputDefinition == null) { + throw new IllegalArgumentException("No output definition, cannot evaluate " + m.getMappingContextDescription()); + } + } + + private void parseTarget() throws SchemaException { + VariableBindingDefinitionType targetSpecification = m.mappingBean.getTarget(); + if (targetSpecification == null) { + outputDefinition = m.defaultTargetDefinition; + outputPath = m.defaultTargetPath; + } else { + ItemPathType itemPathType = targetSpecification.getPath(); + if (itemPathType == null) { + outputDefinition = m.defaultTargetDefinition; + outputPath = m.defaultTargetPath; + } else { + ItemPath path = itemPathType.getItemPath(); + outputDefinition = ExpressionUtil.resolveDefinitionPath( + path, m.variables, m.targetContext, + "target definition in " + m.getMappingContextDescription()); + if (outputDefinition == null) { + throw new SchemaException("No target item that would conform to the path " + + path + " in " + m.getMappingContextDescription()); + } + outputPath = path.stripVariableSegment(); + } + } + if (m.valuePolicyResolver != null) { + m.valuePolicyResolver.setOutputDefinition(outputDefinition); + m.valuePolicyResolver.setOutputPath(outputPath); + } + } + + private void parseSources(OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, SecurityViolationException, + ConfigurationException, CommunicationException { + if (m.defaultSource != null) { + m.sources.add(m.defaultSource); + m.defaultSource.recompute(); + } + for (VariableBindingDefinitionType sourceDefinition : m.mappingBean.getSource()) { + Source source = parseSource(sourceDefinition, result); + source.recompute(); + + // Override existing sources (e.g. default source) + m.sources.removeIf(next -> next.getName().equals(source.getName())); + m.sources.add(source); + } + } + + @NotNull ItemPath getSourcePath(VariableBindingDefinitionType sourceType) throws SchemaException { + ItemPathType itemPathType = sourceType.getPath(); + if (itemPathType == null) { + throw new SchemaException("No path in source definition in " + m.getMappingContextDescription()); + } + ItemPath path = itemPathType.getItemPath(); + if (path.isEmpty()) { + throw new SchemaException("Empty source path in " + m.getMappingContextDescription()); + } + return path; + } + + private Source parseSource( + VariableBindingDefinitionType sourceDefinition, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { + ItemPath path = getSourcePath(sourceDefinition); + @NotNull QName sourceQName = sourceDefinition.getName() != null ? sourceDefinition.getName() : ItemPath.toName(path.last()); + String variableName = sourceQName.getLocalPart(); + + TypedValue typedSourceObject = ExpressionUtil.resolvePathGetTypedValue(path, m.variables, true, + m.getTypedSourceContext(), m.objectResolver, m.prismContext, + "source definition in " + m.getMappingContextDescription(), m.getTask(), result); + + Object sourceObject = typedSourceObject != null ? typedSourceObject.getValue() : null; + Item itemOld = null; + ItemDelta delta = null; + Item itemNew = null; + ItemPath resolvePath = path; + ItemPath residualPath = null; + Collection> subItemDeltas = null; + if (sourceObject != null) { + if (sourceObject instanceof ItemDeltaItem) { + //noinspection unchecked + itemOld = ((ItemDeltaItem) sourceObject).getItemOld(); + //noinspection unchecked + delta = ((ItemDeltaItem) sourceObject).getDelta(); + //noinspection unchecked + itemNew = ((ItemDeltaItem) sourceObject).getItemNew(); + //noinspection unchecked + residualPath = ((ItemDeltaItem) sourceObject).getResidualPath(); + //noinspection unchecked + resolvePath = ((ItemDeltaItem) sourceObject).getResolvePath(); + //noinspection unchecked + subItemDeltas = ((ItemDeltaItem) sourceObject).getSubItemDeltas(); + } else if (sourceObject instanceof Item) { + //noinspection unchecked + itemOld = (Item) sourceObject; + //noinspection unchecked + itemNew = (Item) sourceObject; + } else { + throw new IllegalStateException("Unknown resolve result " + sourceObject); + } + } + + ID sourceItemDefinition = typedSourceObject != null ? typedSourceObject.getDefinition() : null; + + // apply domain + ValueSetDefinitionType domainSetType = sourceDefinition.getSet(); + if (domainSetType != null) { + ValueSetDefinition setDef = new ValueSetDefinition<>( + domainSetType, sourceItemDefinition, m.expressionProfile, variableName, + "domain of " + variableName + " in " + m.getMappingContextDescription(), + m.getTask(), result); + setDef.init(m.expressionFactory); + setDef.setAdditionalVariables(m.variables); + try { + + if (itemOld != null) { + //noinspection unchecked + itemOld = itemOld.clone(); + itemOld.filterValues(setDef::containsTunnel); + } + + if (itemNew != null) { + //noinspection unchecked + itemNew = itemNew.clone(); + itemNew.filterValues(setDef::containsTunnel); + } + + if (delta != null) { + delta = delta.clone(); + delta.filterValues(setDef::containsTunnel); + } + + } catch (TunnelException te) { + unwrapTunnelException(te); + } + } + + Source source = new Source<>(itemOld, delta, itemNew, sourceQName, sourceItemDefinition); + source.setResidualPath(residualPath); + source.setResolvePath(resolvePath); + source.setSubItemDeltas(subItemDeltas); + return source; + } + + private void unwrapTunnelException(TunnelException te) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, SecurityViolationException { + Throwable cause = te.getCause(); + if (cause instanceof SchemaException) { + throw (SchemaException) cause; + } else if (cause instanceof ExpressionEvaluationException) { + throw (ExpressionEvaluationException) cause; + } else if (cause instanceof ObjectNotFoundException) { + throw (ObjectNotFoundException) cause; + } else if (cause instanceof CommunicationException) { + throw (CommunicationException) cause; + } else if (cause instanceof ConfigurationException) { + throw (ConfigurationException) cause; + } else if (cause instanceof SecurityViolationException) { + throw (SecurityViolationException) cause; + } else { + throw te; + } + } + + D getOutputDefinition() { + if (outputDefinition == null) { + try { + parseTarget(); + } catch (SchemaException e) { + throw new SystemException(e); // we assume that targets are (usually) already parsed + } + } + return outputDefinition; + } + + ItemPath getOutputPath() { + if (outputDefinition == null) { + try { + parseTarget(); + } catch (SchemaException e) { + throw new SystemException(e); // we assume that targets are (usually) already parsed + } + } + return outputPath; + } +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingPreExpression.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingPreExpression.java index 1bc91d14ad4..94f9bd578ec 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingPreExpression.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingPreExpression.java @@ -17,11 +17,12 @@ /** * @author semancik - * */ @FunctionalInterface public interface MappingPreExpression { - void mappingPreExpression(ExpressionEvaluationContext context, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; + void mappingPreExpression(ExpressionEvaluationContext context, OperationResult result) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, + SecurityViolationException; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/TimeConstraintsEvaluation.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/TimeConstraintsEvaluation.java new file mode 100644 index 00000000000..4791830bb3b --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/TimeConstraintsEvaluation.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020 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.mapping; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingTimeDeclarationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType; + +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.Duration; +import javax.xml.datatype.XMLGregorianCalendar; +import java.util.Objects; + +/** + * Evaluates mapping time constraints. + */ +class TimeConstraintsEvaluation { + + /** + * "Parent" mapping evaluation. + */ + private final MappingImpl m; + + /** + * Is the mapping valid regarding specified time constraint (timeFrom, timeTo)? + * (If no constraint is provided, mapping is considered valid.) + */ + private Boolean timeConstraintValid; + + /** + * If the time constraints indicate that the validity of the mapping will change in the future + * (either it becomes valid or becomes invalid), this is the time of the expected change. + */ + private XMLGregorianCalendar nextRecomputeTime; + + TimeConstraintsEvaluation(MappingImpl m) { + this.m = m; + } + + void evaluate(OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, + ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + MappingTimeDeclarationType timeFromSpec = m.mappingBean.getTimeFrom(); + MappingTimeDeclarationType timeToSpec = m.mappingBean.getTimeTo(); + if (timeFromSpec == null && timeToSpec == null) { + timeConstraintValid = true; + return; + } + + XMLGregorianCalendar timeFrom = parseTime(timeFromSpec, result); + m.traceTimeFrom(timeFrom); + + if (timeFrom == null && timeFromSpec != null) { + // Time is specified but there is no value for it. + // This means that event that should start validity haven't happened yet + // therefore the mapping is not yet valid. + timeConstraintValid = false; + return; + } + XMLGregorianCalendar timeTo = parseTime(timeToSpec, result); + m.traceTimeTo(timeTo); + + if (timeFrom != null && timeFrom.compare(m.now) == DatatypeConstants.GREATER) { + // before timeFrom + nextRecomputeTime = timeFrom; + timeConstraintValid = false; + return; + } + + if (timeTo == null && timeToSpec != null) { + // Time is specified but there is no value for it. + // This means that event that should stop validity haven't happened yet + // therefore the mapping is still valid. + timeConstraintValid = true; + return; + } + + if (timeTo != null && timeTo.compare(m.now) == DatatypeConstants.GREATER) { + // between timeFrom and timeTo (also no timeFrom and before timeTo) + nextRecomputeTime = timeTo; + timeConstraintValid = true; + return; + } + + // If timeTo is null, we are "in range" + // Otherwise it is less than now (so we are after it), i.e. we are "out of range" + // In both cases there is nothing to recompute in the future + timeConstraintValid = timeTo == null; + } + + private XMLGregorianCalendar parseTime(MappingTimeDeclarationType timeType, OperationResult result) throws SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException { + if (timeType == null) { + return null; + } + XMLGregorianCalendar referenceTime; + ExpressionType expressionType = timeType.getExpression(); + VariableBindingDefinitionType referenceTimeType = timeType.getReferenceTime(); + if (referenceTimeType == null) { + if (expressionType == null) { + throw new SchemaException("No reference time specified, there is also no default and no expression; in time specification in " + m.getMappingContextDescription()); + } else { + referenceTime = null; + } + } else { + referenceTime = parseTimeSource(referenceTimeType, result); + } + + XMLGregorianCalendar time; + if (expressionType == null) { + if (referenceTime == null) { + return null; + } else { + time = (XMLGregorianCalendar) referenceTime.clone(); + } + } else { + MutablePrismPropertyDefinition timeDefinition = m.prismContext.definitionFactory().createPropertyDefinition( + ExpressionConstants.OUTPUT_ELEMENT_NAME, PrimitiveType.XSD_DATETIME); + timeDefinition.setMaxOccurs(1); + + ExpressionVariables timeVariables = new ExpressionVariables(); + timeVariables.addVariableDefinitions(m.variables); + timeVariables.addVariableDefinition(ExpressionConstants.VAR_REFERENCE_TIME, referenceTime, timeDefinition); + + PrismPropertyValue timePropVal = ExpressionUtil.evaluateExpression(m.sources, timeVariables, timeDefinition, + expressionType, m.expressionProfile, m.expressionFactory, "time expression in " + m.getMappingContextDescription(), m.getTask(), result); + + if (timePropVal == null) { + return null; + } + + time = timePropVal.getValue(); + } + Duration offset = timeType.getOffset(); + if (offset != null) { + time.add(offset); + } + return time; + } + + private XMLGregorianCalendar parseTimeSource(VariableBindingDefinitionType source, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + ItemPath path = m.parser.getSourcePath(source); + + Object sourceObject = ExpressionUtil.resolvePathGetValue(path, m.variables, false, + m.getTypedSourceContext(), m.objectResolver, m.prismContext, + "reference time definition in " + m.getMappingContextDescription(), m.getTask(), result); + if (sourceObject == null) { + return null; + } + PrismProperty timeProperty; + if (sourceObject instanceof ItemDeltaItem) { + //noinspection unchecked + timeProperty = (PrismProperty) ((ItemDeltaItem) sourceObject).getItemNew(); + } else if (sourceObject instanceof Item) { + //noinspection unchecked + timeProperty = (PrismProperty) sourceObject; + } else { + throw new IllegalStateException("Unknown resolve result " + sourceObject); + } + return timeProperty != null ? timeProperty.getRealValue() : null; + } + + boolean isTimeConstraintValid() { + return Objects.requireNonNull(timeConstraintValid, "Time validity has not been established"); + } + + XMLGregorianCalendar getNextRecomputeTime() { + return nextRecomputeTime; + } +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/util/PopulatorUtil.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/util/PopulatorUtil.java index 3fb908b58fd..8ac0f41f7d6 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/util/PopulatorUtil.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/util/PopulatorUtil.java @@ -110,7 +110,6 @@ private static MappingImpl, PrismPropertyDefinition> create return this.createMappingBuilder(filename, testName, null, toPath(defaultTargetPropertyName), userDelta).build(); } - public MappingImpl.Builder, PrismPropertyDefinition> createMappingBuilder( + public MappingBuilder, PrismPropertyDefinition> createMappingBuilder( String filename, String testName, String defaultTargetPropertyName, ObjectDelta userDelta) throws SchemaException, IOException, EncryptionException { return createMappingBuilder(filename, testName, null, toPath(defaultTargetPropertyName), userDelta); @@ -123,7 +121,7 @@ public MappingImpl, PrismPropertyDefinition> create return this.createMappingBuilder(filename, testName, null, toPath(defaultTargetPropertyName), userDelta, userOld).build(); } - public MappingImpl.Builder, PrismPropertyDefinition> createMappingBuilder(String filename, String testName, String defaultTargetPropertyName, + public MappingBuilder, PrismPropertyDefinition> createMappingBuilder(String filename, String testName, String defaultTargetPropertyName, ObjectDelta userDelta, PrismObject userOld) throws SchemaException, IOException { return this.createMappingBuilder(filename, testName, null, toPath(defaultTargetPropertyName), userDelta, userOld); } @@ -134,7 +132,7 @@ public MappingImpl, PrismPropertyDefinition> create return this.createMappingBuilder(filename, testName, null, defaultTargetPropertyName, userDelta).build(); } - public MappingImpl.Builder, PrismPropertyDefinition> createMappingBuilder( + public MappingBuilder, PrismPropertyDefinition> createMappingBuilder( String filename, String testName, final ValuePolicyType policy, ItemPath defaultTargetPropertyPath, ObjectDelta userDelta) throws SchemaException, IOException, EncryptionException { @@ -145,14 +143,14 @@ public MappingImpl.Builder, PrismPropertyDefinition return createMappingBuilder(filename, testName, policy, defaultTargetPropertyPath, userDelta, userOld); } - public MappingImpl.Builder, PrismPropertyDefinition> createMappingBuilder( + public MappingBuilder, PrismPropertyDefinition> createMappingBuilder( String filename, String testName, final ValuePolicyType policy, ItemPath defaultTargetPropertyPath, ObjectDelta userDelta, PrismObject userOld) throws SchemaException, IOException { MappingType mappingType = PrismTestUtil.parseAtomicValue( new File(TEST_DIR, filename), MappingType.COMPLEX_TYPE); - MappingImpl.Builder, PrismPropertyDefinition> mappingBuilder = + MappingBuilder, PrismPropertyDefinition> mappingBuilder = mappingFactory.createMappingBuilder(mappingType, testName); mappingBuilder.prismContext(prismContext); @@ -180,27 +178,7 @@ public MappingImpl.Builder, PrismPropertyDefinition PrismObjectDefinition userDefinition = getUserDefinition(); mappingBuilder.targetContext(userDefinition); - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { - ItemPath outputPath; - ItemDefinition outputDefinition; - - @Override - public void setOutputPath(ItemPath outputPath) { - this.outputPath = outputPath; - } - - @Override - public void setOutputDefinition(ItemDefinition outputDefinition) { - this.outputDefinition = outputDefinition; - } - - @Override - public ValuePolicyType resolve() { - return policy; - } - }; - - mappingBuilder.stringPolicyResolver(stringPolicyResolver); + mappingBuilder.stringPolicyResolver((result) -> policy); // Default target if (defaultTargetPropertyPath != null) { PrismPropertyDefinition targetDefDefinition = userDefinition.findItemDefinition(defaultTargetPropertyPath); @@ -221,7 +199,7 @@ public MappingImpl, PrismPropertyDefinition> create MappingType mappingType = PrismTestUtil.parseAtomicValue( new File(TEST_DIR, filename), MappingType.COMPLEX_TYPE); - MappingImpl.Builder, PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder(mappingType, testName); + MappingBuilder, PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder(mappingType, testName); Source, PrismPropertyDefinition> defaultSource = new Source<>(null, delta, null, ExpressionConstants.VAR_INPUT_QNAME, delta.getDefinition()); defaultSource.recompute(); @@ -233,27 +211,7 @@ public MappingImpl, PrismPropertyDefinition> create builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, account.asPrismObject(), ShadowType.class); builder.addVariableDefinition(ExpressionConstants.VAR_PROJECTION, account.asPrismObject(), ShadowType.class); - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { - ItemPath outputPath; - ItemDefinition outputDefinition; - - @Override - public void setOutputPath(ItemPath outputPath) { - this.outputPath = outputPath; - } - - @Override - public void setOutputDefinition(ItemDefinition outputDefinition) { - this.outputDefinition = outputDefinition; - } - - @Override - public ValuePolicyType resolve() { - return policy; - } - }; - - builder.stringPolicyResolver(stringPolicyResolver); + builder.stringPolicyResolver((result) -> policy); builder.originType(OriginType.INBOUND); builder.originObject(resource); diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingDynamicSimple.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingDynamicSimple.java index 2bfbb13d886..04ae96e6b69 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingDynamicSimple.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingDynamicSimple.java @@ -530,7 +530,7 @@ public void testAsIsVariablesPolyStringOrig() throws Exception { @Test public void testScriptExtraVariablesRef() throws Exception { // GIVEN - MappingImpl.Builder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder("mapping-script-extra-variables.xml", + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder("mapping-script-extra-variables.xml", "testScriptExtraVariablesRef", "employeeType", null); VariablesMap vars = new VariablesMap(); @@ -558,7 +558,7 @@ public void testScriptExtraVariablesRef() throws Exception { @Test public void testScriptExtraVariablesJaxb() throws Exception { // GIVEN - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( "mapping-script-extra-variables.xml", getTestNameShort(), "employeeType", null); diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingTime.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingTime.java index feace488979..9e7d66f4741 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingTime.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/mapping/TestMappingTime.java @@ -64,7 +64,7 @@ public void testBeforeTimeFrom() throws Exception { .createModificationReplaceProperty(UserType.class, MappingTestEvaluator.USER_OLD_OID, UserType.F_EMPLOYEE_TYPE, "CAPTAIN"); - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_FROM_TO_FILENAME, getTestNameShort(), "title", delta); builder.now(TIME_PAST); @@ -93,7 +93,7 @@ public void testBetweenTimes() throws Exception { .createModificationReplaceProperty(UserType.class, MappingTestEvaluator.USER_OLD_OID, UserType.F_EMPLOYEE_TYPE, "CAPTAIN"); - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_FROM_TO_FILENAME, getTestNameShort(), "title", delta); builder.now(TIME_BETWEEN); @@ -121,7 +121,7 @@ public void testAfterTimeTo() throws Exception { .createModificationReplaceProperty(UserType.class, MappingTestEvaluator.USER_OLD_OID, UserType.F_EMPLOYEE_TYPE, "CAPTAIN"); - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_FROM_TO_FILENAME, getTestNameShort(), "title", delta); builder.now(TIME_FUTURE); @@ -143,7 +143,7 @@ public void testAfterTimeTo() throws Exception { @Test public void testExistenceBefore() throws Exception { // GIVEN - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_ACTIVATION, getTestNameShort(), "title", null); @@ -171,7 +171,7 @@ public void testExistenceBefore() throws Exception { @Test public void testExistenceAfter() throws Exception { // GIVEN - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_ACTIVATION, getTestNameShort(), "title", null); @@ -204,7 +204,7 @@ public void testNoReferenceTime() throws Exception { PrismObject userOld = evaluator.getUserOld(); userOld.asObjectable().getActivation().setDisableTimestamp(null); - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_ACTIVATION, getTestNameShort(), "title", null, userOld); @@ -241,7 +241,7 @@ public void testSetReferenceTimeBefore() throws Exception { ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_DISABLE_TIMESTAMP), disableTimestamp); - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_ACTIVATION, getTestNameShort(), "title", delta, userOld); @@ -278,7 +278,7 @@ public void testSetReferenceTimeAfter() throws Exception { ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_DISABLE_TIMESTAMP), disableTimestamp); - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = evaluator.createMappingBuilder( MAPPING_TIME_ACTIVATION, getTestNameShort(), "title", delta, userOld); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java index 343b62e3547..73d35123164 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java @@ -9,6 +9,7 @@ import com.evolveum.midpoint.common.Clock; import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.ModelObjectResolver; @@ -61,12 +62,12 @@ public MappingEvaluationResponseType evaluateMapping(@NotNull MappingEvaluationR @NotNull OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { - MappingImpl.Builder builder = mappingFactory.createMappingBuilder(); + MappingBuilder builder = mappingFactory.createMappingBuilder(); ObjectDeltaObject sourceContext = createSourceContext(request, task, result); - builder = builder - .mappingType(request.getMapping()) + builder + .mappingBean(request.getMapping()) .mappingKind(MappingKindType.OTHER) .contextDescription("mapping diagnostic execution") .sourceContext(sourceContext) @@ -88,7 +89,7 @@ public MappingEvaluationResponseType evaluateMapping(@NotNull MappingEvaluationR dumpOutputTriple(sb, mapping.getOutputTriple()); sb.append("Condition output triple: "); dumpOutputTriple(sb, mapping.getConditionOutputTriple()); - sb.append("Time constraint valid: ").append(mapping.evaluateTimeConstraintValid(task, result)).append("\n"); + sb.append("Time constraint valid: ").append(mapping.isTimeConstraintValid()).append("\n"); sb.append("Next recompute time: ").append(mapping.getNextRecomputeTime()).append("\n"); sb.append("\n"); sb.append("Evaluation time: ").append(mapping.getEtime()).append(" ms\n"); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index e2b0e124919..6dd968d1f13 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -17,6 +17,7 @@ import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathImpl; import com.evolveum.midpoint.model.impl.lens.assignments.AssignmentPathSegmentImpl; @@ -42,7 +43,6 @@ import com.evolveum.midpoint.common.ActivationComputer; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; @@ -717,12 +717,18 @@ private static void mergeExtensionContainers(Item MappingImpl.Builder addAssignmentPathVariables(MappingImpl.Builder builder, AssignmentPathVariables assignmentPathVariables, PrismContext prismContext) { + public static MappingBuilder addAssignmentPathVariables(MappingBuilder builder, AssignmentPathVariables assignmentPathVariables, PrismContext prismContext) { ExpressionVariables expressionVariables = new ExpressionVariables(); ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, expressionVariables, prismContext); return builder.addVariableDefinitions(expressionVariables); } + public static ExpressionVariables getAssignmentPathExpressionVariables(AssignmentPathVariables assignmentPathVariables, PrismContext prismContext) { + ExpressionVariables expressionVariables = new ExpressionVariables(); + ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, expressionVariables, prismContext); + return expressionVariables; + } + public static void checkContextSanity(LensContext context, String activityDescription, OperationResult result) throws SchemaException, PolicyViolationException { LensFocusContext focusContext = context.getFocusContext(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/ConditionEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/ConditionEvaluator.java index 5bcdce404e9..aecb05c9360 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/ConditionEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/assignments/ConditionEvaluator.java @@ -9,6 +9,7 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.model.impl.lens.AssignmentPathVariables; import com.evolveum.midpoint.model.impl.lens.LensUtil; @@ -74,9 +75,9 @@ private PrismValueDeltaSetTriple> evaluateCondition( ObjectType source, AssignmentPathVariables assignmentPathVariables, String contextDescription, EvaluationContext ctx, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { - MappingImpl.Builder, PrismPropertyDefinition> builder = + MappingBuilder, PrismPropertyDefinition> builder = ctx.ae.mappingFactory.createMappingBuilder(); - builder = builder.mappingType(condition) + builder = builder.mappingBean(condition) .mappingKind(MappingKindType.ASSIGNMENT_CONDITION) .contextDescription(contextDescription) .sourceContext(ctx.ae.focusOdo) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/Construction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/Construction.java index ac1fe15edd6..762d3626e00 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/Construction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/Construction.java @@ -13,6 +13,7 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.impl.lens.AssignmentPathVariables; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; @@ -430,7 +431,7 @@ private PrismValueDeltaSetTriple> evaluateTagTripe(Ta return null; } - MappingImpl.Builder, PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder( + MappingBuilder, PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder( outboundMappingSpec, "for outbound tag mapping in " + getSource()); @@ -510,10 +511,9 @@ public PrismContainerDefinition getAssociationContainerDe } @SuppressWarnings("ConstantConditions") - public > MappingImpl.Builder initializeMappingBuilder( - MappingImpl.Builder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, - RefinedObjectClassDefinition assocTargetObjectClassDefinition, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + > MappingBuilder initializeMappingBuilder( + MappingBuilder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, + RefinedObjectClassDefinition assocTargetObjectClassDefinition, Task task, OperationResult result) { if (!builder.isApplicableToChannel(getChannel())) { return null; @@ -562,7 +562,7 @@ public > MappingImpl.Builder> MappingImpl evaluateMapping( - MappingImpl.Builder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, + MappingBuilder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, RefinedObjectClassDefinition assocTargetObjectClassDefinition, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedConstructionImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedConstructionImpl.java index 616b2dc5267..d59525959da 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedConstructionImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedConstructionImpl.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.AssignmentPath; import com.evolveum.midpoint.model.api.context.EvaluatedConstruction; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.projector.mappings.NextRecompute; @@ -200,7 +201,7 @@ private MappingImpl, ResourceAttributeDefinition> e + getIntent() + ", " + construction.getResolvedResource().resource + " as defined in " + construction.getSource(), attrName); } - MappingImpl.Builder, ResourceAttributeDefinition> builder = construction.getMappingFactory().createMappingBuilder( + MappingBuilder, ResourceAttributeDefinition> builder = construction.getMappingFactory().createMappingBuilder( outboundMappingType, "for attribute " + PrettyPrinter.prettyPrint(attrName) + " in " + construction.getSource()); @@ -262,11 +263,7 @@ private void evaluateAssociations(Task task, OperationResult result) throw new SchemaException("No outbound section in definition of association " + assocName + " in construction in " + construction.getSource()); } - MappingImpl, PrismContainerDefinition> assocMapping = - evaluateAssociation(associationDefinitionType, task, result); - if (assocMapping != null) { - associationMappings.add(assocMapping); - } + associationMappings.add(evaluateAssociation(associationDefinitionType, task, result)); } } @@ -293,9 +290,9 @@ private MappingImpl, PrismContainerDe } PrismContainerDefinition outputDefinition = construction.getAssociationContainerDefinition(); - MappingImpl.Builder, PrismContainerDefinition> mappingBuilder = + MappingBuilder, PrismContainerDefinition> mappingBuilder = construction.getMappingFactory()., PrismContainerDefinition>createMappingBuilder() - .mappingType(outboundMappingType) + .mappingBean(outboundMappingType) .contextDescription("for association " + PrettyPrinter.prettyPrint(assocName) + " in " + construction.getSource()) .originType(OriginType.ASSIGNMENTS) .originObject(construction.getSource()); @@ -309,7 +306,7 @@ private MappingImpl, PrismContainerDe } private > MappingImpl evaluateMapping( - MappingImpl.Builder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, + MappingBuilder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, RefinedObjectClassDefinition assocTargetObjectClassDefinition, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedOutboundConstructionImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedOutboundConstructionImpl.java index 100291536d3..f8fbe93e173 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedOutboundConstructionImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/construction/EvaluatedOutboundConstructionImpl.java @@ -15,13 +15,14 @@ import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.lens.projector.mappings.NextRecompute; import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; +import com.evolveum.midpoint.repo.common.expression.ConfigurableValuePolicyResolver; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import org.jetbrains.annotations.NotNull; @@ -31,7 +32,6 @@ import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.PrettyPrinter; @@ -95,7 +95,7 @@ private NextRecompute evaluateAttributes(Task task, OperationResult result) String mappingShortDesc = "outbound mapping for " + PrettyPrinter.prettyPrint(refinedAttributeDefinition.getItemName()) + " in " + getProjectionContext().getResource(); - MappingImpl.Builder, RefinedAttributeDefinition> builder = + MappingBuilder, RefinedAttributeDefinition> builder = getConstruction().getMappingFactory().createMappingBuilder(outboundMappingType, mappingShortDesc); //noinspection ConstantConditions builder = builder.originObject(getProjectionContext().getResource()) @@ -131,7 +131,7 @@ private NextRecompute evaluateAssociations(NextRecompute nextRecompute, Task tas // continue; // } - MappingImpl.Builder,PrismContainerDefinition> mappingBuilder = getConstruction().getMappingFactory().createMappingBuilder(outboundMappingType, + MappingBuilder,PrismContainerDefinition> mappingBuilder = getConstruction().getMappingFactory().createMappingBuilder(outboundMappingType, "outbound mapping for " + PrettyPrinter.prettyPrint(associationDefinition.getName()) + " in " + getProjectionContext().getResource()); @@ -150,7 +150,7 @@ assocName, outputDefinition, getConstruction().getFocusOdo(), projectionOdo, ope } // TODO: unify with MappingEvaluator.evaluateOutboundMapping(...) - private MappingImpl evaluateMapping(final MappingImpl.Builder mappingBuilder, QName attributeQName, + private MappingImpl evaluateMapping(final MappingBuilder mappingBuilder, QName attributeQName, D targetDefinition, ObjectDeltaObject focusOdo, ObjectDeltaObject projectionOdo, String operation, RefinedObjectClassDefinition rOcDef, RefinedObjectClassDefinition assocTargetObjectClassDefinition, LensContext context, LensProjectionContext projCtx, final Task task, OperationResult result) @@ -231,30 +231,26 @@ private MappingImpl evalu } } - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { + ConfigurableValuePolicyResolver valuePolicyResolver = new ConfigurableValuePolicyResolver() { private ItemDefinition outputDefinition; - @Override - public void setOutputPath(ItemPath outputPath) { - } - @Override public void setOutputDefinition(ItemDefinition outputDefinition) { this.outputDefinition = outputDefinition; } @Override - public ValuePolicyType resolve() { + public ValuePolicyType resolve(OperationResult result) { - if (mappingBuilder.getMappingType().getExpression() != null) { - List> evaluators = mappingBuilder.getMappingType().getExpression().getExpressionEvaluator(); + if (mappingBuilder.getMappingBean().getExpression() != null) { + List> evaluators = mappingBuilder.getMappingBean().getExpression().getExpressionEvaluator(); for (JAXBElement jaxbEvaluator : evaluators) { Object object = jaxbEvaluator.getValue(); if (object instanceof GenerateExpressionEvaluatorType && ((GenerateExpressionEvaluatorType) object).getValuePolicyRef() != null) { ObjectReferenceType ref = ((GenerateExpressionEvaluatorType) object).getValuePolicyRef(); try { ValuePolicyType valuePolicyType = mappingBuilder.getObjectResolver().resolve(ref, ValuePolicyType.class, - null, "resolving value policy for generate attribute "+ outputDefinition.getItemName()+"value", task, new OperationResult("Resolving value policy")); + null, "resolving value policy for generate attribute "+ outputDefinition.getItemName()+"value", task, result); if (valuePolicyType != null) { return valuePolicyType; } @@ -267,7 +263,7 @@ public ValuePolicyType resolve() { return null; } }; - mappingBuilder.valuePolicyResolver(stringPolicyResolver); + mappingBuilder.valuePolicyResolver(valuePolicyResolver); // TODO: other variables? // Set condition masks. There are used as a brakes to avoid evaluating to nonsense values in case user is not present diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java index 7afdf41d776..b0cf224be8a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java @@ -472,10 +472,8 @@ public void processActivationUserFuture(LensContext con SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); } - } - private boolean evaluateExistenceMapping(final LensContext context, final LensProjectionContext projCtx, final XMLGregorianCalendar now, final MappingTimeEval current, Task task, final OperationResult result) @@ -520,22 +518,9 @@ private boolean evaluateExistenceMapping(final LensContext .implicitSourcePath(LEGAL_PROPERTY_NAME) .implicitTargetPath(SHADOW_EXISTS_PROPERTY_NAME); - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(projCtx); - Source,PrismPropertyDefinition> legalSource - = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL_QNAME); - builder.defaultSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); - builder.addSource(focusExistsSource); + builder.defaultSource(new Source<>(getLegalIdi(projCtx), ExpressionConstants.VAR_LEGAL_QNAME)); + builder.additionalSource(new Source<>(getAssignedIdi(projCtx), ExpressionConstants.VAR_ASSIGNED_QNAME)); + builder.additionalSource(new Source<>(getFocusExistsIdi(context.getFocusContext()), ExpressionConstants.VAR_FOCUS_EXISTS_QNAME)); // Variable: focus builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); @@ -633,33 +618,17 @@ private void evaluateActivationMapping(final LensContex context.getFocusContext().getObjectDeltaObject().findIdi(sourcePath); builder.implicitSourcePath(sourcePath); - Source,PrismPropertyDefinition> computedSource = new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME); - builder.defaultSource(computedSource); - - Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME); - builder.addSource(source); + builder.defaultSource(new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME)); + builder.additionalSource(new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME)); } else { - Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME); - builder.defaultSource(source); + builder.defaultSource(new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME)); builder.implicitSourcePath(focusPropertyPath); } - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalIdi = getLegalIdi(projCtx); - Source,PrismPropertyDefinition> legalSource = new Source<>(legalIdi, ExpressionConstants.VAR_LEGAL_QNAME); - builder.addSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); - builder.addSource(focusExistsSource); + builder.additionalSource(new Source<>(getLegalIdi(projCtx), ExpressionConstants.VAR_LEGAL_QNAME)); + builder.additionalSource(new Source<>(getAssignedIdi(projCtx), ExpressionConstants.VAR_ASSIGNED_QNAME)); + builder.additionalSource(new Source<>(getFocusExistsIdi(context.getFocusContext()), ExpressionConstants.VAR_FOCUS_EXISTS_QNAME)); return builder; }; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java index 4eee63fbe52..13b728991fa 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java @@ -20,6 +20,7 @@ import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.common.expression.ConfigurableValuePolicyResolver; import com.evolveum.midpoint.schema.CapabilityUtil; import com.evolveum.midpoint.util.LocalizableMessageBuilder; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -41,7 +42,6 @@ import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.util.ItemDeltaItem; import com.evolveum.midpoint.repo.common.expression.Source; -import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.constants.SchemaConstants; @@ -163,18 +163,7 @@ private void processProjectionPasswordMapping(LensContext< ItemDeltaItem, PrismPropertyDefinition> focusPasswordIdi = focusContext .getObjectDeltaObject().findIdi(SchemaConstants.PATH_PASSWORD_VALUE); - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { - @Override - public void setOutputPath(ItemPath outputPath) { - } - @Override - public void setOutputDefinition(ItemDefinition outputDefinition) { - } - @Override - public ValuePolicyType resolve() { - return SecurityUtil.getPasswordPolicy(securityPolicy); - } - }; + ConfigurableValuePolicyResolver valuePolicyResolver = (result1) -> SecurityUtil.getPasswordPolicy(securityPolicy); MappingInitializer,PrismPropertyDefinition> initializer = (builder) -> { @@ -183,7 +172,7 @@ public ValuePolicyType resolve() { .implicitTargetPath(SchemaConstants.PATH_PASSWORD_VALUE); builder.defaultTargetDefinition(projPasswordPropertyDefinition); builder.defaultSource(new Source<>(focusPasswordIdi, ExpressionConstants.VAR_INPUT_QNAME)); - builder.valuePolicyResolver(stringPolicyResolver); + builder.valuePolicyResolver(valuePolicyResolver); return builder; }; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java index 29baeda2b01..da45814fed7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java @@ -20,6 +20,7 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; @@ -27,6 +28,7 @@ import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.common.expression.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.springframework.beans.factory.annotation.Autowired; @@ -52,11 +54,6 @@ import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.util.ItemDeltaItem; import com.evolveum.midpoint.provisioning.api.ProvisioningService; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.repo.common.expression.Source; -import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; -import com.evolveum.midpoint.repo.common.expression.VariableProducer; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.expression.TypedValue; @@ -662,7 +659,7 @@ private boolean checkWeakSkip(MappingImpl inbound, P * Note: in this case "attribute" may also be an association */ private void collectMappingsForTargets(final LensContext context, - LensProjectionContext projectionCtx, MappingType inboundMappingType, + LensProjectionContext projectionCtx, MappingType inboundMappingBean, QName accountAttributeQName, Item oldAccountProperty, ItemDelta attributeAPrioriDelta, D attributeDefinition, @@ -675,47 +672,59 @@ private vo } ResourceType resource = projectionCtx.getResource(); - MappingImpl.Builder builder = mappingFactory.createMappingBuilder(inboundMappingType, - "inbound expression for "+accountAttributeQName+" in "+resource); - if (!builder.isApplicableToChannel(context.getChannel())) { + if (!MappingImpl.isApplicableToChannel(inboundMappingBean, context.getChannel())) { return; } PrismObject shadowNew = projectionCtx.getObjectNew(); PrismObjectDefinition shadowNewDef; - if (shadowNew == null) { + if (shadowNew != null) { + shadowNewDef = shadowNew.getDefinition(); + } else { shadowNewDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class); + } + + PrismObjectDefinition focusNewDef; + if (focusNew != null) { + focusNewDef = focusNew.getDefinition(); } else { - shadowNewDef = shadowNew.getDefinition(); + focusNewDef = context.getFocusContextRequired().getObjectDefinition(); } - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_USER, focusNew, focusNew.getDefinition()); - variables.put(ExpressionConstants.VAR_FOCUS, focusNew, focusNew.getDefinition()); - variables.put(ExpressionConstants.VAR_ACCOUNT, shadowNew, shadowNewDef); - variables.put(ExpressionConstants.VAR_SHADOW, shadowNew, shadowNewDef); - variables.put(ExpressionConstants.VAR_PROJECTION, shadowNew, shadowNewDef); - variables.put(ExpressionConstants.VAR_RESOURCE, resource, resource.asPrismObject().getDefinition()); - variables.put(ExpressionConstants.VAR_CONFIGURATION, context.getSystemConfiguration(), context.getSystemConfiguration().getDefinition()); - variables.put(ExpressionConstants.VAR_OPERATION, context.getFocusContext().getOperation().getValue(), String.class); - - Source defaultSource = new Source<>(oldAccountProperty, attributeAPrioriDelta, null, ExpressionConstants.VAR_INPUT_QNAME, (D)attributeDefinition); + + Source defaultSource = new Source<>(oldAccountProperty, attributeAPrioriDelta, null, ExpressionConstants.VAR_INPUT_QNAME, attributeDefinition); defaultSource.recompute(); - builder = builder.defaultSource(defaultSource) + + MappingBuilder builder = mappingFactory.createMappingBuilder() + .mappingBean(inboundMappingBean) + .mappingKind(MappingKindType.INBOUND) + .implicitSourcePath(ShadowType.F_ATTRIBUTES.append(accountAttributeQName)) + .contextDescription("inbound expression for "+accountAttributeQName+" in "+resource) + .defaultSource(defaultSource) .targetContext(LensUtil.getFocusDefinition(context)) - .variables(variables) + .addVariableDefinition(ExpressionConstants.VAR_USER, focusNew, focusNewDef) + .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusNew, focusNewDef) + .addAliasRegistration(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS) + .addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, shadowNew, shadowNewDef) + .addVariableDefinition(ExpressionConstants.VAR_SHADOW, shadowNew, shadowNewDef) + .addVariableDefinition(ExpressionConstants.VAR_PROJECTION, shadowNew, shadowNewDef) + .addAliasRegistration(ExpressionConstants.VAR_ACCOUNT, ExpressionConstants.VAR_PROJECTION) + .addAliasRegistration(ExpressionConstants.VAR_SHADOW, ExpressionConstants.VAR_PROJECTION) + .addVariableDefinition(ExpressionConstants.VAR_RESOURCE, resource, resource.asPrismObject().getDefinition()) + .addVariableDefinition(ExpressionConstants.VAR_CONFIGURATION, context.getSystemConfiguration(), context.getSystemConfiguration().getDefinition()) + .addVariableDefinition(ExpressionConstants.VAR_OPERATION, context.getFocusContext().getOperation().getValue(), String.class) .variableResolver(variableProducer) .valuePolicyResolver(createStringPolicyResolver(context)) - .mappingKind(MappingKindType.INBOUND) - .implicitSourcePath(ShadowType.F_ATTRIBUTES.append(accountAttributeQName)) .originType(OriginType.INBOUND) .originObject(resource); - if (!context.getFocusContext().isDelete()){ - TypedValue> targetContext = new TypedValue<>(focusNew); - Collection originalValues = ExpressionUtil.computeTargetValues(inboundMappingType.getTarget(), targetContext, variables, mappingFactory.getObjectResolver() , "resolving range", - prismContext, task, result); - builder.originalTargetValues(originalValues); + if (!context.getFocusContext().isDelete()) { + assert focusNew != null; + TypedValue> targetContext = new TypedValue<>(focusNew); + ExpressionVariables variables = builder.getVariables(); + Collection originalValues = ExpressionUtil.computeTargetValues(inboundMappingBean.getTarget(), targetContext, variables, mappingFactory.getObjectResolver() , "resolving target values", + prismContext, task, result); + builder.originalTargetValues(originalValues); } MappingImpl mapping = builder.build(); @@ -729,6 +738,7 @@ private vo if (ItemPath.isEmpty(targetFocusItemPath)) { throw new ConfigurationException("Empty target path in "+mapping.getContextDescription()); } + // TODO resolve these unused variables boolean isAssignment = FocusType.F_ASSIGNMENT.equivalent(targetFocusItemPath); Item targetFocusItem = null; if (focusNew != null) { @@ -762,13 +772,13 @@ private voi focusNew = context.getFocusContext().getObjectNew(); } - Set>>> mappingsToTargeEntrySet = (Set)mappingsToTarget.entrySet(); - for (Entry>> mappingsToTargeEntry : mappingsToTargeEntrySet) { + Set>>> mappingsToTargetEntrySet = (Set)mappingsToTarget.entrySet(); + for (Entry>> mappingsToTargetEntry : mappingsToTargetEntrySet) { D outputDefinition = null; boolean rangeCompletelyDefined = true; DeltaSetTriple> allTriples = prismContext.deltaFactory().createDeltaSetTriple(); - for (InboundMappingStruct mappingsToTargetStruct : mappingsToTargeEntry.getValue()) { + for (InboundMappingStruct mappingsToTargetStruct : mappingsToTargetEntry.getValue()) { MappingImpl mapping = mappingsToTargetStruct.getMapping(); LensProjectionContext projectionCtx = mappingsToTargetStruct.getProjectionContext(); outputDefinition = mapping.getOutputDefinition(); @@ -800,9 +810,9 @@ private voi } DeltaSetTriple> consolidatedTriples = consolidateTriples(allTriples); - LOGGER.trace("Consolidated triples for mapping for item {}\n{}", mappingsToTargeEntry.getKey(), consolidatedTriples.debugDumpLazily(1)); + LOGGER.trace("Consolidated triples for mapping for item {}\n{}", mappingsToTargetEntry.getKey(), consolidatedTriples.debugDumpLazily(1)); - ItemDelta focusItemDelta = collectOutputDelta(outputDefinition, mappingsToTargeEntry.getKey(), focusNew, + ItemDelta focusItemDelta = collectOutputDelta(outputDefinition, mappingsToTargetEntry.getKey(), focusNew, consolidatedTriples, rangeCompletelyDefined); if (focusItemDelta != null && !focusItemDelta.isEmpty()) { @@ -1010,7 +1020,7 @@ private It if (targetFocusItem != null && !targetFocusItem.getDefinition().isMultiValue() && !targetFocusItem.isEmpty()) { Collection replace = new ArrayList<>(); - replace.add(LensUtil.cloneAndApplyMetadata(value, isAssignment, originMapping.getMappingType())); + replace.add(LensUtil.cloneAndApplyMetadata(value, isAssignment, originMapping.getMappingBean())); outputFocusItemDelta.setValuesToReplace(replace); @@ -1021,7 +1031,7 @@ private It alreadyReplaced = true; } } else { - outputFocusItemDelta.addValueToAdd(LensUtil.cloneAndApplyMetadata(value, isAssignment, originMapping.getMappingType())); + outputFocusItemDelta.addValueToAdd(LensUtil.cloneAndApplyMetadata(value, isAssignment, originMapping.getMappingBean())); } } } @@ -1170,14 +1180,9 @@ private void resolveEntitlementsIfNeeded(ContainerDelta a } } - private ValuePolicyResolver createStringPolicyResolver(final LensContext context) { - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { - private ItemPath outputPath; + private ConfigurableValuePolicyResolver createStringPolicyResolver(final LensContext context) { + return new ConfigurableValuePolicyResolver() { private ItemDefinition outputDefinition; - @Override - public void setOutputPath(ItemPath outputPath) { - this.outputPath = outputPath; - } @Override public void setOutputDefinition(ItemDefinition outputDefinition) { @@ -1185,23 +1190,18 @@ public void setOutputDefinition(ItemDefinition outputDefinition) { } @Override - public ValuePolicyType resolve() { - if (!outputDefinition.getItemName().equals(PasswordType.F_VALUE)) { - return null; - } - ValuePolicyType passwordPolicy = credentialsProcessor.determinePasswordPolicy(context.getFocusContext()); - if (passwordPolicy == null) { + public ValuePolicyType resolve(OperationResult result) { + if (outputDefinition.getItemName().equals(PasswordType.F_VALUE)) { + return credentialsProcessor.determinePasswordPolicy(context.getFocusContext()); + } else { return null; } - return passwordPolicy; } }; - return stringPolicyResolver; } /** * Processing for special (fixed-schema) properties such as credentials and activation. - * @throws ObjectNotFoundException */ private void processSpecialPropertyInbound(ResourceBidirectionalMappingType biMappingType, ItemPath sourceTargetPath, PrismObject newUser, LensProjectionContext projCtx, @@ -1259,7 +1259,7 @@ private void processSpecialPropertyInbound(Collection initializer = (builder) -> { if (projContext.getObjectNew() == null) { projContext.recompute(); @@ -1288,13 +1288,13 @@ private void processSpecialPropertyInbound(Collection accountNew = projContext.getObjectNew(); - builder = builder.addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, accountNew, ShadowType.class) + builder.addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, accountNew, ShadowType.class) .addVariableDefinition(ExpressionConstants.VAR_SHADOW, accountNew, ShadowType.class) .addVariableDefinition(ExpressionConstants.VAR_PROJECTION, accountNew, ShadowType.class) .addAliasRegistration(ExpressionConstants.VAR_ACCOUNT, ExpressionConstants.VAR_PROJECTION) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java index 92888872a27..0f72cc743ac 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/AutoassignRoleMappingEvaluationRequest.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.impl.lens.projector.mappings; +import com.evolveum.midpoint.model.common.mapping.MappingPreExpression; import com.evolveum.midpoint.model.common.util.PopulatorUtil; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ItemDelta; @@ -29,7 +30,7 @@ public class AutoassignRoleMappingEvaluationRequest extends FocalMappingEvaluati // Internal state private PrismContainerDefinition assignmentDef; - private AssignmentType assignmentType; + private AssignmentType assignment; public AutoassignRoleMappingEvaluationRequest(@NotNull AutoassignMappingType mapping, @NotNull AbstractRoleType role) { super(mapping, MappingKindType.AUTO_ASSIGN, role); @@ -40,38 +41,43 @@ public focusOdo) throws SchemaException { PrismObject focus = focusOdo.getAnyObject(); assignmentDef = focus.getDefinition().findContainerDefinition(FocusType.F_ASSIGNMENT); - PrismContainer assignment = assignmentDef.instantiate(); - assignmentType = assignment.createNewValue().asContainerable(); + PrismContainer assignmentContainer = assignmentDef.instantiate(); + assignment = assignmentContainer.createNewValue().asContainerable(); QName relation; AssignmentPropertiesSpecificationType assignmentProperties = mapping.getAssignmentProperties(); if (assignmentProperties != null) { relation = assignmentProperties.getRelation(); - assignmentType.getSubtype().addAll(assignmentProperties.getSubtype()); + assignment.getSubtype().addAll(assignmentProperties.getSubtype()); } else { relation = null; } - assignmentType.targetRef(originObject.getOid(), originObject.asPrismObject().getDefinition().getTypeName(), relation); + assignment.targetRef(originObject.getOid(), originObject.asPrismObject().getDefinition().getTypeName(), relation); Source, PrismContainerDefinition> source = - new Source<>(assignment, null, assignment, FocusType.F_ASSIGNMENT, assignmentDef); + new Source<>(assignmentContainer, null, assignmentContainer, FocusType.F_ASSIGNMENT, assignmentDef); //noinspection unchecked return (Source) source; } @Override - public void mappingPreExpression(ExpressionEvaluationContext context, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, - ConfigurationException, SecurityViolationException { - PopulateType populate = mapping.getPopulate(); - if (populate == null) { - return; - } - List> populateItemDeltas = PopulatorUtil - .computePopulateItemDeltas(populate, assignmentDef, context.getVariables(), context, - context.getContextDescription(), context.getTask(), result); - if (populateItemDeltas != null) { - ItemDeltaCollectionsUtil.applyTo(populateItemDeltas, assignmentType.asPrismContainerValue()); - } + public MappingPreExpression getMappingPreExpression() { + /* + * Executed before mapping expression is executed. It is used to populate the assignment. + * We need to do that just before mapping expression is executed, because we want all the sources + * and variables set the same way as mapping is set. + */ + return (ExpressionEvaluationContext context, OperationResult result) -> { + PopulateType populate = mapping.getPopulate(); + if (populate == null) { + return; + } + List> populateItemDeltas = PopulatorUtil + .computePopulateItemDeltas(populate, assignmentDef, context.getVariables(), context, + context.getContextDescription(), context.getTask(), result); + if (populateItemDeltas != null) { + ItemDeltaCollectionsUtil.applyTo(populateItemDeltas, assignment.asPrismContainerValue()); + } + }; } @Override diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java index ce8b0fd9d7a..d1af98a3cd4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/FocalMappingEvaluationRequest.java @@ -34,8 +34,7 @@ * * @author semancik */ -public abstract class FocalMappingEvaluationRequest implements ShortDumpable, - MappingPreExpression { +public abstract class FocalMappingEvaluationRequest implements ShortDumpable { @NotNull protected final MT mapping; @NotNull protected final MappingKindType mappingKind; @@ -59,14 +58,8 @@ public > outputTripleMap = new HashMap<>(); XMLGregorianCalendar nextRecomputeTime = null; String triggerOriginDescription = null; - Collection mappingTypes = params.getMappingTypes(); - Collection> mappings = new ArrayList<>(mappingTypes.size()); + Collection mappingBeans = params.getMappingTypes(); + Collection> mappings = new ArrayList<>(mappingBeans.size()); - for (MappingType mappingType : mappingTypes) { + for (MappingType mappingBean : mappingBeans) { - MappingImpl.Builder mappingBuilder = mappingFactory.createMappingBuilder(mappingType, mappingDesc); + MappingBuilder mappingBuilder = mappingFactory.createMappingBuilder(mappingBean, mappingDesc); String mappingName = null; - if (mappingType.getName() != null) { - mappingName = mappingType.getName(); + if (mappingBean.getName() != null) { + mappingName = mappingBean.getName(); } if (!mappingBuilder.isApplicableToChannel(params.getContext().getChannel())) { @@ -205,10 +204,11 @@ public initializedMappingBuilder = params.getInitializer().initialize(mappingBuilder); + MappingBuilder initializedMappingBuilder = params.getInitializer().initialize(mappingBuilder); MappingImpl mapping = initializedMappingBuilder.build(); - boolean timeConstraintValid = mapping.evaluateTimeConstraintValid(task, result); + mapping.evaluateTimeValidity(task, result); + boolean timeConstraintValid = mapping.isTimeConstraintValid(); if (params.getEvaluateCurrent() == MappingTimeEval.CURRENT && !timeConstraintValid) { LOGGER.trace("Mapping {} is non-current, but evaluating current mappings, skipping {}", mappingName, params.getContext().getChannel()); @@ -350,7 +350,6 @@ public mapping : mappings) { XMLGregorianCalendar mappingNextRecomputeTime = mapping.getNextRecomputeTime(); if (mappingNextRecomputeTime != null) { - if (mapping.isSatisfyCondition() && (nextRecomputeTime == null || nextRecomputeTime.compare(mappingNextRecomputeTime) == DatatypeConstants.GREATER)) { + if (mapping.isConditionSatisfied() && (nextRecomputeTime == null || nextRecomputeTime.compare(mappingNextRecomputeTime) == DatatypeConstants.GREATER)) { nextRecomputeTime = mappingNextRecomputeTime; // TODO: maybe better description? But consider storage requirements. We do not want to store too much. triggerOriginDescription = mapping.getIdentifier(); @@ -553,39 +552,42 @@ private boolean hasNoValue(Item aPrioriTargetItem) { || (aPrioriTargetItem.isEmpty() && !aPrioriTargetItem.isIncomplete()); } - MappingImpl createFocusMapping( - final MappingFactory mappingFactory, final LensContext context, final MappingType mappingType, MappingKindType mappingKind, ObjectType originObject, - ObjectDeltaObject focusOdo, Source defaultSource, PrismObject defaultTargetObject, AssignmentPathVariables assignmentPathVariables, - Integer iteration, String iterationToken, PrismObject configuration, - XMLGregorianCalendar now, String contextDesc, final Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (!MappingImpl.isApplicableToChannel(mappingType, context.getChannel())) { - LOGGER.trace("Mapping {} not applicable to channel {}, skipping.", mappingType, context.getChannel()); + + MappingImpl createFocusMapping( + MappingFactory mappingFactory, LensContext context, FocalMappingEvaluationRequest request, + ObjectDeltaObject focusOdo, PrismObject targetContext, Integer iteration, String iterationToken, + PrismObject configuration, XMLGregorianCalendar now, String contextDesc, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, SecurityViolationException { + + MappingType mappingBean = request.getMapping(); + MappingKindType mappingKind = request.getMappingKind(); + ObjectType originObject = request.getOriginObject(); + Source defaultSource = request.constructDefaultSource(focusOdo); + AssignmentPathVariables assignmentPathVariables = request.getAssignmentPathVariables(); + + if (!MappingImpl.isApplicableToChannel(mappingBean, context.getChannel())) { + LOGGER.trace("Mapping {} not applicable to channel {}, skipping.", mappingBean, context.getChannel()); return null; } - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { - private ItemPath outputPath; + ConfigurableValuePolicyResolver valuePolicyResolver = new ConfigurableValuePolicyResolver() { private ItemDefinition outputDefinition; - @Override - public void setOutputPath(ItemPath outputPath) { - this.outputPath = outputPath; - } - @Override public void setOutputDefinition(ItemDefinition outputDefinition) { this.outputDefinition = outputDefinition; } @Override - public ValuePolicyType resolve() { + public ValuePolicyType resolve(OperationResult result) { // TODO need to switch to ObjectValuePolicyEvaluator if (outputDefinition.getItemName().equals(PasswordType.F_VALUE)) { return credentialsProcessor.determinePasswordPolicy(context.getFocusContext()); } - if (mappingType.getExpression() != null) { - List> evaluators = mappingType.getExpression().getExpressionEvaluator(); + if (mappingBean.getExpression() != null) { + List> evaluators = mappingBean.getExpression().getExpressionEvaluator(); if (evaluators != null) { for (JAXBElement jaxbEvaluator : evaluators) { Object object = jaxbEvaluator.getValue(); @@ -593,7 +595,7 @@ public ValuePolicyType resolve() { ObjectReferenceType ref = ((GenerateExpressionEvaluatorType) object).getValuePolicyRef(); try { ValuePolicyType valuePolicyType = mappingFactory.getObjectResolver().resolve(ref, ValuePolicyType.class, - null, "resolving value policy for generate attribute " + outputDefinition.getItemName() + " value", task, new OperationResult("Resolving value policy")); + null, "resolving value policy for generate attribute " + outputDefinition.getItemName() + " value", task, result); if (valuePolicyType != null) { return valuePolicyType; } @@ -611,32 +613,34 @@ public ValuePolicyType resolve() { }; ExpressionVariables variables = new ExpressionVariables(); - FOCUS_VARIABLE_NAMES.forEach(name -> variables.addVariableDefinition(name, focusOdo, focusOdo.getDefinition())); + variables.put(ExpressionConstants.VAR_FOCUS, focusOdo, focusOdo.getDefinition()); + variables.put(ExpressionConstants.VAR_USER, focusOdo, focusOdo.getDefinition()); + variables.registerAlias(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS); variables.put(ExpressionConstants.VAR_ITERATION, iteration, Integer.class); variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken, String.class); variables.put(ExpressionConstants.VAR_CONFIGURATION, configuration, SystemConfigurationType.class); variables.put(ExpressionConstants.VAR_OPERATION, context.getFocusContext().getOperation().getValue(), String.class); variables.put(ExpressionConstants.VAR_SOURCE, originObject, ObjectType.class); - TypedValue> defaultTargetContext = new TypedValue<>(defaultTargetObject); - Collection targetValues = ExpressionUtil.computeTargetValues(mappingType.getTarget(), defaultTargetContext, variables, mappingFactory.getObjectResolver(), contextDesc, prismContext, task, result); + TypedValue> defaultTargetContext = new TypedValue<>(targetContext); + Collection targetValues = ExpressionUtil. computeTargetValues(mappingBean.getTarget(), defaultTargetContext, variables, mappingFactory.getObjectResolver(), contextDesc, prismContext, task, result); - MappingImpl.Builder mappingBuilder = mappingFactory.createMappingBuilder(mappingType, contextDesc) + MappingBuilder mappingBuilder = mappingFactory.createMappingBuilder(mappingBean, contextDesc) .sourceContext(focusOdo) .defaultSource(defaultSource) - .targetContext(defaultTargetObject.getDefinition()) - .variables(variables) + .targetContext(targetContext.getDefinition()) + .variablesFrom(variables) + .variablesFrom(LensUtil.getAssignmentPathExpressionVariables(assignmentPathVariables, prismContext)) .originalTargetValues(targetValues) .mappingKind(mappingKind) .originType(OriginType.USER_POLICY) .originObject(originObject) .objectResolver(objectResolver) - .valuePolicyResolver(stringPolicyResolver) + .valuePolicyResolver(valuePolicyResolver) .rootNode(focusOdo) + .mappingPreExpression(request.getMappingPreExpression()) // Used to populate autoassign assignments .now(now); - mappingBuilder = LensUtil.addAssignmentPathVariables(mappingBuilder, assignmentPathVariables, prismContext); - MappingImpl mapping = mappingBuilder.build(); ItemPath itemPath = mapping.getOutputPath(); @@ -645,13 +649,11 @@ public ValuePolicyType resolve() { return mapping; } - if (defaultTargetObject != null) { - Item existingTargetItem = (Item) defaultTargetObject.findItem(itemPath); - if (existingTargetItem != null && !existingTargetItem.isEmpty() - && mapping.getStrength() == MappingStrengthType.WEAK) { - LOGGER.trace("Mapping {} is weak and target already has a value {}, skipping.", mapping, existingTargetItem); - return null; - } + Item existingTargetItem = targetContext.findItem(itemPath); + if (existingTargetItem != null && !existingTargetItem.isEmpty() + && mapping.getStrength() == MappingStrengthType.WEAK) { + LOGGER.trace("Mapping {} is weak and target already has a value {}, skipping.", mapping, existingTargetItem); + return null; } return mapping; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingInitializer.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingInitializer.java index 8dd90456687..b97422c4526 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingInitializer.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingInitializer.java @@ -6,7 +6,7 @@ */ package com.evolveum.midpoint.model.impl.lens.projector.mappings; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.util.exception.SchemaException; @@ -18,6 +18,6 @@ @FunctionalInterface public interface MappingInitializer { - MappingImpl.Builder initialize(MappingImpl.Builder mapping) throws SchemaException; + MappingBuilder initialize(MappingBuilder mapping) throws SchemaException; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java index eb7d93f5bdc..67d3253a3a2 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingSetEvaluator.java @@ -90,23 +90,19 @@ NextRecompute evaluateMappingsToTriples( PrismObject targetObject = targetSpecification.getTargetObject(updatedFocusOdo); - MappingImpl mapping = mappingEvaluator.createFocusMapping(mappingFactory, context, request.getMapping(), - request.getMappingKind(), request.getOriginObject(), updatedFocusOdo, request.constructDefaultSource(updatedFocusOdo), - targetObject, request.getAssignmentPathVariables(), iteration, iterationToken, + MappingImpl mapping = mappingEvaluator.createFocusMapping(mappingFactory, context, + request, updatedFocusOdo, targetObject, iteration, iterationToken, context.getSystemConfiguration(), now, description, task, result); if (mapping == null) { continue; } - // Used to populate autoassign assignments - mapping.setMappingPreExpression(request); - mappingEvaluator.evaluateMapping(mapping, context, task, result); // We need to update nextRecompute even for mappings with "time valid" state. nextRecompute = NextRecompute.update(mapping, nextRecompute); - if (!mapping.evaluateTimeConstraintValid(task, result)) { + if (!mapping.isTimeConstraintValid()) { continue; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java index 5805afaac5e..3a6745e1b71 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy; import com.evolveum.midpoint.model.api.context.*; +import com.evolveum.midpoint.model.common.mapping.MappingBuilder; import com.evolveum.midpoint.model.common.mapping.MappingImpl; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.lens.*; @@ -639,10 +640,10 @@ private boolean isRuleConditionTrue(GlobalPoli return true; } - MappingImpl.Builder, PrismPropertyDefinition> builder = mappingFactory + MappingBuilder, PrismPropertyDefinition> builder = mappingFactory .createMappingBuilder(); ObjectDeltaObject focusOdo = new ObjectDeltaObject<>(focus, null, focus, focus.getDefinition()); - builder = builder.mappingType(condition) + builder = builder.mappingBean(condition) .mappingKind(MappingKindType.POLICY_RULE_CONDITION) .contextDescription("condition in global policy rule " + globalPolicyRule.getName()) .sourceContext(focusOdo) diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java index 6bdabcbbf6c..6fb49a7be68 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java @@ -2346,7 +2346,7 @@ private void assertFocusMappings(EvaluatedAssignmentImpl ev } private void assertFocusMappings(EvaluatedAssignmentImpl evaluatedAssignment, Collection expectedItems) { - assertUnsortedListsEquals("Wrong focus mappings", expectedItems, evaluatedAssignment.getFocusMappings(), m -> m.getMappingType().getName()); + assertUnsortedListsEquals("Wrong focus mappings", expectedItems, evaluatedAssignment.getFocusMappings(), m -> m.getMappingBean().getName()); // TODO look at the content of the mappings (e.g. zero, plus, minus sets) } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/spec/expressions/TestExpressionSpec.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/spec/expressions/TestExpressionSpec.java new file mode 100644 index 00000000000..80c2c77bdd1 --- /dev/null +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/spec/expressions/TestExpressionSpec.java @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2020 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.impl.spec.expressions; + +import static com.evolveum.midpoint.model.impl.spec.expressions.TestExpressionSpec.VariablesStyle.DELTA; +import static com.evolveum.midpoint.model.impl.spec.expressions.TestExpressionSpec.VariablesStyle.NO_DELTA; + +import static java.util.Collections.singleton; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.prism.delta.PropertyDelta; + +import com.evolveum.midpoint.prism.util.ItemDeltaItem; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import com.evolveum.midpoint.model.impl.AbstractModelImplementationIntegrationTest; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.repo.common.expression.*; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.internals.InternalCounters; +import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.PredefinedTestMethodTracing; +import com.evolveum.midpoint.test.asserter.prism.PrismValueDeltaSetTripleAsserter; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; + +/** + * + */ +@ContextConfiguration(locations = { "classpath:ctx-model-test-main.xml" }) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class TestExpressionSpec extends AbstractModelImplementationIntegrationTest { + + private static final Trace LOGGER = TraceManager.getTrace(TestExpressionSpec.class); + + private static final String TEST_DIR = "src/test/resources/spec/expressions"; + + @Autowired private ExpressionFactory expressionFactory; + @Autowired private PrismContext prismContext; + + private static final File EXPRESSION_GIVEN_NAME_TO_UPPERCASE_FILE = new File(TEST_DIR, "givenName-to-uppercase.xml"); + private static final File EXPRESSION_FULL_NAME_FROM_PARTS_FILE = new File(TEST_DIR, "fullName-from-parts.xml"); + + private long lastScriptExecutionCount; + + private static final String SRC_GIVEN_NAME = "givenName"; + private static final String SRC_FAMILY_NAME = "familyName"; + private static final String SRC_HONORIFIC_PREFIX = "honorificPrefix"; + private static final String SRC_HONORIFIC_SUFFIX = "honorificSuffix"; + private static final String VAR_FOO = "foo"; + private static final String VAR_BAR = "bar"; + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + + predefinedTestMethodTracing = PredefinedTestMethodTracing.MODEL_LOGGING; + rememberScriptExecutionCount(); + } + + @Test + public void test100GivenNameUppercaseNoDelta() throws Exception { + given(); + Collection> sources = + source(SRC_GIVEN_NAME) + .old("jack") + .buildSources(); + + when(); + PrismValueDeltaSetTriple> outputTriple = + evaluate(EXPRESSION_GIVEN_NAME_TO_UPPERCASE_FILE, sources, NO_DELTA); + + then(); + assertOutputTriple(outputTriple) + .assertEmptyMinus() + .assertEmptyPlus() + .zeroSet() + .assertSinglePropertyValue("JACK"); + + assertScriptExecutionIncrement(1); + } + + @Test + public void test110GivenNameUppercaseReplaceDelta() throws Exception { + given(); + Collection> sources = + source(SRC_GIVEN_NAME) + .old("jack") + .replace("jackie") + .buildSources(); + + when(); + PrismValueDeltaSetTriple> outputTriple = + evaluate(EXPRESSION_GIVEN_NAME_TO_UPPERCASE_FILE, sources, DELTA); + + then(); + assertOutputTriple(outputTriple) + .assertEmptyZero() + .plusSet() + .assertSinglePropertyValue("JACKIE") + .end() + .minusSet() + .assertSinglePropertyValue("JACK") + .end(); + + assertScriptExecutionIncrement(2); + } + + @Test + public void test120GivenNameUppercaseAddDeleteDelta() throws Exception { + given(); + Collection> sources = + source(SRC_GIVEN_NAME) + .old("jack") + .delete("jack") + .add("jackie") + .buildSources(); + + when(); + PrismValueDeltaSetTriple> outputTriple = + evaluate(EXPRESSION_GIVEN_NAME_TO_UPPERCASE_FILE, sources, DELTA); + + then(); + assertOutputTriple(outputTriple) + .assertEmptyZero() + .plusSet() + .assertSinglePropertyValue("JACKIE") + .end() + .minusSet() + .assertSinglePropertyValue("JACK") + .end(); + + assertScriptExecutionIncrement(2); + } + + private PrismValueDeltaSetTriple> evaluate(File expressionFile, Collection> sources, + VariablesStyle variablesStyle) throws Exception { + ExpressionType expression = parseExpression(expressionFile); + ExpressionVariables variables = prepareVariables(variablesStyle); + Task task = getTestTask(); + ExpressionEvaluationContext expressionContext = + new ExpressionEvaluationContext(sources, variables, getTestNameShort(), task); + + return evaluatePropertyExpression(expression, PrimitiveType.STRING, expressionContext, task.getResult()); + } + + private ExpressionVariables prepareVariables(VariablesStyle variablesStyle) throws SchemaException { + switch (variablesStyle) { + case NO_DELTA: + return prepareBasicVariablesNoDelta(); + case DELTA: + return prepareBasicVariablesWithDelta(); + default: + throw new AssertionError(variablesStyle); + } + } + + @Test + public void test200FullNameFromPartsNoDelta() throws Exception { + given(); + Collection> sources = Arrays.asList( + source(SRC_GIVEN_NAME) + .old("Jack") + .build(), + source(SRC_FAMILY_NAME) + .old("Sparrow") + .build(), + source(SRC_HONORIFIC_PREFIX) + .old("Cpt.") + .build(), + source(SRC_HONORIFIC_SUFFIX) + .build() + ); + + when(); + PrismValueDeltaSetTriple> outputTriple = + evaluate(EXPRESSION_FULL_NAME_FROM_PARTS_FILE, sources, NO_DELTA); + + then(); + assertOutputTriple(outputTriple) + .assertEmptyMinus() + .assertEmptyPlus() + .zeroSet() + .assertSinglePropertyValue("Cpt. Jack Sparrow"); + + assertScriptExecutionIncrement(1); + } + + private ExpressionType parseExpression(File file) throws SchemaException, IOException { + return PrismTestUtil.parseAtomicValue(file, ExpressionType.COMPLEX_TYPE); + } + + private ExpressionVariables prepareBasicVariablesNoDelta() { + ExpressionVariables variables = new ExpressionVariables(); + + variables.put(VAR_FOO, "fooValue", String.class); + variables.put(VAR_BAR, "barValue", String.class); + + return variables; + } + + private ExpressionVariables prepareBasicVariablesWithDelta() throws SchemaException { + ExpressionVariables variables = new ExpressionVariables(); + + variables.put(VAR_FOO, "fooValue", String.class); + + PrismProperty barProperty = createProperty(VAR_BAR, singleton("barValueOld")); + PropertyDelta barDelta = barProperty.createDelta(); + barDelta.setRealValuesToReplace("barValueNew"); + ItemDeltaItem, PrismPropertyDefinition> barIdi = new ItemDeltaItem<>(barProperty, barDelta, null, barProperty.getDefinition()); + barIdi.recompute(); + variables.put(VAR_BAR, barIdi, String.class); + return variables; + } + + private PrismValueDeltaSetTriple evaluateExpression( + ExpressionType expressionType, D outputDefinition, ExpressionEvaluationContext expressionContext, + OperationResult result) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, ExpressionEvaluationException, CommunicationException, ConfigurationException { + Expression expression = expressionFactory.makeExpression(expressionType, outputDefinition, null, + expressionContext.getContextDescription(), expressionContext.getTask(), result); + LOGGER.debug("Starting evaluation of expression: {}", expression); + return expression.evaluate(expressionContext, result); + } + + private PrismValueDeltaSetTriple> evaluatePropertyExpression( + ExpressionType expressionType, QName outputType, ExpressionEvaluationContext expressionContext, OperationResult result) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, ExpressionEvaluationException, CommunicationException, ConfigurationException { + PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition( + ExpressionConstants.OUTPUT_ELEMENT_NAME, outputType); + return evaluateExpression(expressionType, outputDefinition, expressionContext, result); + } + + private PrismValueDeltaSetTriple> evaluatePropertyExpression( + ExpressionType expressionType, PrimitiveType outputType, ExpressionEvaluationContext expressionContext, + OperationResult result) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, ExpressionEvaluationException, CommunicationException, ConfigurationException { + return evaluatePropertyExpression(expressionType, outputType.getQname(), expressionContext, result); + } + + private void rememberScriptExecutionCount() { + lastScriptExecutionCount = InternalMonitor.getCount(InternalCounters.SCRIPT_EXECUTION_COUNT); + } + + private void assertScriptExecutionIncrement(int expectedIncrement) { + long currentScriptExecutionCount = InternalMonitor.getCount(InternalCounters.SCRIPT_EXECUTION_COUNT); + long actualIncrement = currentScriptExecutionCount - lastScriptExecutionCount; + assertEquals("Unexpected increment in script execution count", + expectedIncrement, actualIncrement); + lastScriptExecutionCount = currentScriptExecutionCount; + } + + private PrismValueDeltaSetTripleAsserter assertOutputTriple(PrismValueDeltaSetTriple triple) { + assertNotNull(triple); + triple.checkConsistence(); + PrismValueDeltaSetTripleAsserter asserter = new PrismValueDeltaSetTripleAsserter<>(triple, "expression output triple"); + asserter.setPrismContext(prismContext); + asserter.display(); + return asserter; + } + + private SourceBuilder source(String name) { + return new SourceBuilder(name); + } + + private class SourceBuilder { + + private final String name; + private final Collection oldValues = new HashSet<>(); + private Collection addValues; + private Collection deleteValues; + private Collection replaceValues; + + private SourceBuilder(String name) { + this.name = name; + } + + private SourceBuilder old(String... values) { + oldValues.addAll(Arrays.asList(values)); + return this; + } + + private SourceBuilder add(String... values) { + addValues = Arrays.asList(values); + return this; + } + + private SourceBuilder delete(String... values) { + deleteValues = Arrays.asList(values); + return this; + } + + private SourceBuilder replace(String... values) { + replaceValues = Arrays.asList(values); + return this; + } + + private Collection> buildSources() throws SchemaException { + return singleton(build()); + } + + private Source build() throws SchemaException { + PrismProperty property = createProperty(name, oldValues); + PropertyDelta delta; + if (addValues != null || deleteValues != null || replaceValues != null) { + delta = property.createDelta(); + if (addValues != null) { + delta.addRealValuesToAdd(addValues); + } + if (deleteValues != null) { + delta.addRealValuesToDelete(deleteValues); + } + if (replaceValues != null) { + delta.setRealValuesToReplace(replaceValues.toArray(new String[0])); + } + } else { + delta = null; + } + Source, PrismPropertyDefinition> source = + new Source<>(property, delta, null, property.getElementName(), property.getDefinition()); + source.recompute(); + return source; + } + } + + private PrismProperty createProperty(String name, Collection values) + throws SchemaException { + PrismPropertyDefinition propDef = prismContext.definitionFactory() + .createPropertyDefinition(new QName(SchemaConstants.NS_C, name), PrimitiveType.STRING.getQname()); + PrismProperty property = prismContext.itemFactory().createProperty(propDef.getItemName(), propDef); + for (String value : values) { + property.add(prismContext.itemFactory().createPropertyValue(value)); + } + return property; + } + + enum VariablesStyle { + NO_DELTA, DELTA + } +} diff --git a/model/model-impl/src/test/resources/spec/expressions/fullName-from-parts.xml b/model/model-impl/src/test/resources/spec/expressions/fullName-from-parts.xml new file mode 100644 index 00000000000..32561b4bdc5 --- /dev/null +++ b/model/model-impl/src/test/resources/spec/expressions/fullName-from-parts.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/model/model-impl/src/test/resources/spec/expressions/givenName-to-uppercase.xml b/model/model-impl/src/test/resources/spec/expressions/givenName-to-uppercase.xml new file mode 100644 index 00000000000..e24a932ae7c --- /dev/null +++ b/model/model-impl/src/test/resources/spec/expressions/givenName-to-uppercase.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ConfigurableValuePolicyResolver.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ConfigurableValuePolicyResolver.java new file mode 100644 index 00000000000..1a6f2abce98 --- /dev/null +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ConfigurableValuePolicyResolver.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2010-2017 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.repo.common.expression; + +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.path.ItemPath; + +/** + * Provides value policy when needed (e.g. in generate expression evaluator). + * Accepts setting of path and definition for the item for which the value policy will be obtained. + */ +public interface ConfigurableValuePolicyResolver extends ValuePolicyResolver { + + /** + * Sets the definition of the item for which value policy will be provided. + */ + default void setOutputDefinition(ItemDefinition outputDefinition) { } + + /** + * Sets the path of the item for which value policy will be provided. + * (Actually this seems to be quite unused.) + */ + @SuppressWarnings("unused") + default void setOutputPath(ItemPath outputPath) { } +} diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionEvaluationContext.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionEvaluationContext.java index 8a6522cc702..41962ff4981 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionEvaluationContext.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionEvaluationContext.java @@ -36,7 +36,7 @@ public class ExpressionEvaluationContext { /** * One of the sources can be denoted as "default". - * Interpretation of this information is evaluator-specific. + * Interpretation of this information is evaluator-specific. (Currently used by AsIs evaluator.) */ private Source defaultSource; @@ -66,16 +66,6 @@ public class ExpressionEvaluationContext { */ private ExpressionFactory expressionFactory; - /** - * Purpose of this field is unknown. TODO Consider removal. - */ - private PrismObjectDefinition defaultTargetContext; - - /** - * Purpose of this field is unknown. TODO Consider removal. - */ - private RefinedObjectClassDefinition refinedObjectClassDefinition; - /** * Yet another field with unclear meaning. Seems to be used as an association name. TODO Clarify. */ @@ -199,18 +189,6 @@ public void setExpressionFactory(ExpressionFactory expressionFactory) { this.expressionFactory = expressionFactory; } - public PrismObjectDefinition getDefaultTargetContext() { - return defaultTargetContext; - } - - public void setDefaultTargetContext(PrismObjectDefinition defaultTargetContext) { - this.defaultTargetContext = defaultTargetContext; - } - - public void setRefinedObjectClassDefinition(RefinedObjectClassDefinition refinedObjectClassDefinition) { - this.refinedObjectClassDefinition = refinedObjectClassDefinition; - } - public QName getMappingQName() { return mappingQName; } @@ -264,7 +242,6 @@ public ExpressionEvaluationContext shallowClone() { clone.valuePolicyResolver = this.valuePolicyResolver; clone.expressionFactory = this.expressionFactory; clone.defaultSource = this.defaultSource; - clone.refinedObjectClassDefinition = this.refinedObjectClassDefinition; clone.mappingQName = this.mappingQName; clone.additionalConvertor = this.additionalConvertor; clone.variableProducer = this.variableProducer; diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ValuePolicyResolver.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ValuePolicyResolver.java index 8497caf4820..880d3e9efb8 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ValuePolicyResolver.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ValuePolicyResolver.java @@ -6,23 +6,18 @@ */ package com.evolveum.midpoint.repo.common.expression; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; /** - * - * Built for lazy resolving. + * Provides value policy when needed (e.g. in generate expression evaluator). * * @author semancik - * */ public interface ValuePolicyResolver { - void setOutputDefinition(ItemDefinition outputDefinition); - - void setOutputPath(ItemPath outputPath); - - ValuePolicyType resolve(); - + /** + * Returns appropriate value policy. + */ + ValuePolicyType resolve(OperationResult result); }