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 extends ObjectType> 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 extends ItemDelta, ?>> 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 extends ObjectType> 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, D> 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, D> 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 extends ItemDelta, ?>> 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