diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/DefaultPageParametersEncoder.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/DefaultPageParametersEncoder.java deleted file mode 100644 index fb6b9afe2cd..00000000000 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/DefaultPageParametersEncoder.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.evolveum.midpoint.web.util; - -import org.apache.wicket.request.Url; -import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder; -import org.apache.wicket.request.mapper.parameter.PageParameters; - -import java.util.Iterator; - -/** - * @author lazyman - */ -public class DefaultPageParametersEncoder implements IPageParametersEncoder { - - /** - * Encodes URL like this: /mountpoint/paramName1/paramValue1/paramName2/paramValue2 - */ - @Override - public Url encodePageParameters(PageParameters pageParameters) { - Url url = new Url(); - - for (PageParameters.NamedPair pair : pageParameters.getAllNamed()) { - url.getSegments().add(pair.getKey()); - url.getSegments().add(pair.getValue()); - } - - return url; - } - - /** - * Decodes URL like this: /mountpoint/paramName1/paramValue1/paramName2/paramValue2 - */ - @Override - public PageParameters decodePageParameters(Url url) { - PageParameters parameters = new PageParameters(); - - for (Iterator segment = url.getSegments().iterator(); segment.hasNext(); ) { - String key = segment.next(); - String value = segment.next(); - - parameters.add(key, value); - } - - return parameters.isEmpty() ? null : parameters; - } -} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java index 54cc56997c5..a1e5ef756b5 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/ExactMatchMountedMapper.java @@ -16,17 +16,22 @@ package com.evolveum.midpoint.web.util; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import org.apache.commons.lang3.StringUtils; import org.apache.wicket.core.request.mapper.MountedMapper; import org.apache.wicket.request.Url; import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder; +import org.apache.wicket.request.mapper.parameter.PageParametersEncoder; /** * Created by lazyman on 09/03/2017. */ public class ExactMatchMountedMapper extends MountedMapper { + private static final Trace LOG = TraceManager.getTrace(ExactMatchMountedMapper.class); + public ExactMatchMountedMapper(String mountPath, Class pageClass, IPageParametersEncoder pageParametersEncoder) { @@ -46,7 +51,15 @@ protected boolean urlStartsWithMountedSegments(Url url) { return false; } + if (!(pageParametersEncoder instanceof PageParametersEncoder)) { + LOG.trace("Matching using standard mounted mapper for '{}'", url); + return super.urlStartsWithMountedSegments(url); + } + String mountUrl = StringUtils.join(mountSegments, "/"); - return url.getPath().equals(mountUrl); + boolean matched = url.getPath().equals(mountUrl); + + LOG.trace("Matched: {} for '{}' with mount url '{}'", matched, url, mountUrl); + return matched; } } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index c52dc6cef70..b873ae20cdf 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -8509,7 +8509,7 @@ Selects some assignments from all the assignments in the object. - E.g. may be used to select only some assignments/inducments for a role. + E.g. may be used to select only some assignments/inducements for a role. @@ -11050,10 +11050,71 @@ - + + + + Default multiplicity or role assignment. This value defines whether the same + abstract role may be assigned only once or multiple times to the same focus. + This is the system-wide default value. + It will be possible to override this value for each individual role using the + policy rules in the future midPoint versions. + + + 3.6 + + + + + + + Multiplicity or role assignment. This value defines whether the same + abstract role may be assigned only once or multiple times to the same focus. + + + + 3.6 + + + + + + + The role may be assigned to each focus only once. + + + + + + + + + + The role may be assigned to each focus multiple times, + but easch assignment must have different assignment + parameters. + + + + + + + + + + The role may be assigned to each focus multiple times. + There is no limitation as to the number of assignments. + + + + + + + + + diff --git a/infra/schema/src/main/resources/xml/ns/public/model/scripting/scripting-3.xsd b/infra/schema/src/main/resources/xml/ns/public/model/scripting/scripting-3.xsd index b05545effe9..f88ea4e699c 100644 --- a/infra/schema/src/main/resources/xml/ns/public/model/scripting/scripting-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/model/scripting/scripting-3.xsd @@ -85,6 +85,29 @@ + + + + Options related to evaluation of scripting expression. + EXPERIMENTAL + In the future, these may become part of any scripting expression, allowing parts of a complex expression + to be evaluated differently from its other parts. + + + + + + + + + Causes evaluation to continue even in the presence of any errors. + TODO make this more elaborate w.r.t. kind of error(s) encountered. + + + + + + @@ -388,6 +411,7 @@ + diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ScriptingService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ScriptingService.java index b4af75b8e4a..d58a8fda3ac 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ScriptingService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ScriptingService.java @@ -21,6 +21,7 @@ import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; import javax.xml.namespace.QName; @@ -46,7 +47,9 @@ public interface ScriptingService { * * TODO consider removing this method (it was meant as a simplified version of the method below) */ - public void evaluateExpressionInBackground(QName objectType, ObjectFilter filter, String actionName, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException; + @Deprecated + void evaluateExpressionInBackground(QName objectType, ObjectFilter filter, String actionName, Task task, + OperationResult parentResult) throws SchemaException, SecurityViolationException; /** * Asynchronously executes any scripting expression. @@ -59,7 +62,7 @@ public interface ScriptingService { * @param parentResult * @throws SchemaException */ - public void evaluateExpressionInBackground(ScriptingExpressionType expression, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException; + void evaluateExpressionInBackground(ScriptingExpressionType expression, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException; /** * Synchronously executes any scripting expression (with no input data). @@ -72,5 +75,10 @@ public interface ScriptingService { * TODO return ExecutionContext (requires moving the context to model api) */ - public ScriptExecutionResult evaluateExpression(ScriptingExpressionType expression, Task task, OperationResult result) throws ScriptExecutionException, SchemaException, SecurityViolationException; + ScriptExecutionResult evaluateExpression(ScriptingExpressionType expression, Task task, OperationResult result) + throws ScriptExecutionException, SchemaException, SecurityViolationException; + + ScriptExecutionResult evaluateExpression(ExecuteScriptType executeScriptCommand, Task task, OperationResult result) + throws ScriptExecutionException, SchemaException, SecurityViolationException; + } \ No newline at end of file diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPath.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPath.java index cef295aeb55..9ba8f770d3c 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPath.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPath.java @@ -24,6 +24,11 @@ import java.util.List; /** + * Path from the source object (focus) to the ultimate assignment that is being processed or referenced. + * The path consists of a chain (list) of segments. Each segment corresponds to a single assignment or inducement. + * The source of the first segment is the focus. Source of each following segment (i.e. assignment) is the target + * of previous segment (i.e. assignment). + * * @author semancik * @author mederly */ @@ -31,13 +36,13 @@ public interface AssignmentPath extends DebugDumpable { List getSegments(); - AssignmentPathSegment getFirstAssignmentSegment(); + AssignmentPathSegment first(); boolean isEmpty(); int size(); - EvaluationOrder getEvaluationOrder(); +// EvaluationOrder getEvaluationOrder(); AssignmentPathSegment last(); diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPathSegment.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPathSegment.java index 0755b5e4675..47703f5d152 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPathSegment.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/AssignmentPathSegment.java @@ -22,27 +22,36 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentPathSegmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import org.jetbrains.annotations.NotNull; /** + * Single assignment in an assignment path. In addition to the AssignmentType, it contains resolved target: + * full object, resolved from targetRef. If targetRef resolves to multiple objects, in the path segment + * one of them is stored: the one that participates in the particular assignment path. + * * @author semancik * @author mederly */ public interface AssignmentPathSegment extends DebugDumpable { - boolean isAssignment(); AssignmentType getAssignment(); - QName getRelation(); - - ObjectType getTarget(); + /** + * True if the segment corresponds to assignment. False if it's an inducement. + */ + boolean isAssignment(); ObjectType getSource(); - EvaluationOrder getEvaluationOrder(); + ObjectType getTarget(); - ObjectType getOrderOneObject(); + QName getRelation(); + /** + * True if the relation is a delegation one. + */ boolean isDelegation(); + @NotNull AssignmentPathSegmentType toAssignmentPathSegmentType(); } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java index d275b587dbf..6eedb5a3b9f 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluationOrder.java @@ -32,6 +32,10 @@ public interface EvaluationOrder extends DebugDumpable { EvaluationOrder advance(QName relation); + EvaluationOrder decrease(int amount); + + EvaluationOrder decrease(int amount, QName relation); + int getMatchingRelationOrder(QName relation); String shortDump(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java index 0ea8bfab4a3..423e245554a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java @@ -71,6 +71,7 @@ import com.evolveum.midpoint.xml.ns._public.common.api_types_3.CompareResultType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ImportOptionsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; @@ -2042,6 +2043,7 @@ public void delegateWorkItem(String workItemId, List delega //endregion //region Scripting (bulk actions) + @Deprecated @Override public void evaluateExpressionInBackground(QName objectType, ObjectFilter filter, String actionName, Task task, OperationResult parentResult) throws SchemaException, SecurityViolationException { checkScriptingAuthorization(parentResult); @@ -2061,6 +2063,14 @@ public ScriptExecutionResult evaluateExpression(ScriptingExpressionType expressi return executionContext.toExecutionResult(); } + @Override + public ScriptExecutionResult evaluateExpression(ExecuteScriptType scriptExecutionCommand, Task task, OperationResult result) + throws ScriptExecutionException, SchemaException, SecurityViolationException { + checkScriptingAuthorization(result); + ExecutionContext executionContext = scriptingExpressionEvaluator.evaluateExpression(scriptExecutionCommand, task, result); + return executionContext.toExecutionResult(); + } + private void checkScriptingAuthorization(OperationResult parentResult) throws SchemaException, SecurityViolationException { securityEnforcer.authorize(ModelAuthorizationAction.EXECUTE_SCRIPT.getUrl(), null, null, null, null, null, parentResult); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java index 2f9375e8c58..5c74eb79a75 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java @@ -257,12 +257,19 @@ private EvaluationOrder getInitialEvaluationOrder( */ private void evaluateFromSegment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, EvaluationContext ctx) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException { - LOGGER.trace("Evaluate assignment {} (matching order: {}, mode: {})", ctx.assignmentPath, segment.isMatchingOrder(), mode); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("*** Evaluate from segment: {}", segment); + LOGGER.trace("*** Evaluation order: {}, matching: {}, mode: {}, process membership: {}", + segment.getEvaluationOrder(), segment.isMatchingOrder(), mode, segment.isProcessMembership()); + } assertSourceNotNull(segment.source, ctx.evalAssignment); checkSchema(segment, ctx); ctx.assignmentPath.add(segment); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("*** Path (with current segment already added):\n{}", ctx.assignmentPath.debugDump()); + } boolean evaluateContent = true; AssignmentType assignmentType = getAssignmentType(segment, ctx); @@ -338,7 +345,7 @@ private boolean evaluateSegmentContent(AssignmentPathSegm } if (assignmentType.getTarget() != null || assignmentType.getTargetRef() != null) { List> targets = getTargets(segment, ctx); - LOGGER.trace("Targets in {}: {}", segment.source, targets); + LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, assignmentType.getId(), targets); if (isDirectAssignment) { setEvaluatedAssignmentTarget(segment, targets, ctx); @@ -381,8 +388,10 @@ private void checkCycle(AssignmentPathSegmentImpl segment if (target.getOid().equals(segment.source.getOid())) { throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); } - LOGGER.trace("Checking for role cycle, comparing segment order {} with path order {}", segment.getEvaluationOrder(), ctx.assignmentPath.getEvaluationOrder()); - if (ctx.assignmentPath.containsTarget(target.asObjectable()) && segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())) { + // removed condition "&& segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())" + // as currently it is always true + // TODO reconsider this + if (ctx.assignmentPath.containsTarget(target.asObjectable())) { LOGGER.debug("Role cycle detected for target {} in {}", ObjectTypeUtil.toShortString(target), ctx.assignmentPath); throw new PolicyViolationException("Attempt to assign "+target+" creates a role cycle"); } @@ -585,7 +594,7 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, segment.setTarget(targetType); segment.setRelation(relation); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Evaluating TARGET:\n{}", segment.debugDump(1)); + LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDump(1)); } checkRelationWithTarget(segment, targetType, relation); @@ -654,7 +663,7 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, return; } - EvaluationOrder evaluationOrder = ctx.assignmentPath.getEvaluationOrder(); + EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); ObjectType orderOneObject; if (evaluationOrder.getSummaryOrder() == 1) { @@ -689,10 +698,24 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, roleInducementIdi.recompute(); String subSourceDescription = targetType+" in "+segment.sourceDescription; AssignmentPathSegmentImpl subAssignmentPathSegment = new AssignmentPathSegmentImpl(targetType, subSourceDescription, roleInducementIdi, false); - subAssignmentPathSegment.setEvaluationOrder(evaluationOrder); + + boolean newIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( + subAssignmentPathSegment.getAssignment(), evaluationOrder, 0); + boolean newIsMatchingOrderPlusOne = AssignmentPathSegmentImpl.computeMatchingOrder( + subAssignmentPathSegment.getAssignment(), evaluationOrder, 1); + + EvaluationOrder newEvaluationOrder; + if (roleInducement.getOrder() != null && roleInducement.getOrder() > 1) { + newEvaluationOrder = evaluationOrder.decrease(roleInducement.getOrder()-1); // TODO what about relations? + } else { + newEvaluationOrder = evaluationOrder; + } + // TODO undefined if intervals + subAssignmentPathSegment.setEvaluationOrder(newEvaluationOrder, newIsMatchingOrder, newIsMatchingOrderPlusOne); + subAssignmentPathSegment.setOrderOneObject(orderOneObject); subAssignmentPathSegment.setPathToSourceValid(isValid); - subAssignmentPathSegment.setProcessMembership(subAssignmentPathSegment.isMatchingOrder()); + subAssignmentPathSegment.setProcessMembership(newIsMatchingOrder); // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into // inducements with non-matching order: for example because we need to extract target-related policy rules @@ -701,9 +724,9 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. // processMembership attribute to false for these inducements. if (LOGGER.isTraceEnabled()) { - LOGGER.trace("E({}): evaluate inducement({}) {} in {}", - evaluationOrder.shortDump(), FocusTypeUtil.dumpInducementConstraints(roleInducement), - FocusTypeUtil.dumpAssignment(roleInducement), targetType); + LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", + evaluationOrder.shortDump(), targetType, FocusTypeUtil.dumpInducementConstraints(roleInducement), + FocusTypeUtil.dumpAssignment(roleInducement), newEvaluationOrder.shortDump()); } assert !ctx.assignmentPath.isEmpty(); evaluateFromSegment(subAssignmentPathSegment, mode, ctx); @@ -721,19 +744,20 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, continue; } } + QName subrelation = getRelation(roleAssignment); + EvaluationOrder newEvaluationOrder = evaluationOrder.advance(subrelation); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("E({}): follow assignment {} in {}", - evaluationOrder.shortDump(), FocusTypeUtil.dumpAssignment(roleAssignment), targetType); + LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", + evaluationOrder.shortDump(), targetType, FocusTypeUtil.dumpAssignment(roleAssignment), newEvaluationOrder); } ItemDeltaItem,PrismContainerDefinition> roleAssignmentIdi = new ItemDeltaItem<>(); roleAssignmentIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(roleAssignment)); roleAssignmentIdi.recompute(); String subSourceDescription = targetType+" in "+segment.sourceDescription; AssignmentPathSegmentImpl subAssignmentPathSegment = new AssignmentPathSegmentImpl(targetType, subSourceDescription, roleAssignmentIdi, true); - QName subrelation = getRelation(roleAssignment); - - subAssignmentPathSegment.setEvaluationOrder(evaluationOrder.advance(subrelation)); + subAssignmentPathSegment.setEvaluationOrder(newEvaluationOrder); subAssignmentPathSegment.setOrderOneObject(orderOneObject); + // TODO why??? this should depend on evaluation order if (targetType instanceof AbstractRoleType) { subAssignmentPathSegment.setProcessMembership(false); } else { @@ -760,6 +784,8 @@ private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, ctx.evalAssignment.addLegacyPolicyConstraints(policyConstraints, ctx.assignmentPath.clone(), targetType); } } + + LOGGER.trace("Evaluating segment target DONE for {}", segment); } private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, FocusType targetType, QName relation) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathImpl.java index f2793ee6725..2016cc60623 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathImpl.java @@ -59,7 +59,7 @@ public void removeLast(AssignmentPathSegmentImpl segment) { } @Override - public AssignmentPathSegmentImpl getFirstAssignmentSegment() { + public AssignmentPathSegmentImpl first() { return segments.get(0); } @@ -71,14 +71,14 @@ public boolean isEmpty() { @Override public int size() { return segments.size(); } - @Override - public EvaluationOrder getEvaluationOrder() { - if (isEmpty()) { - return EvaluationOrderImpl.ZERO; - } else { - return last().getEvaluationOrder(); - } - } +// @Override +// public EvaluationOrder getEvaluationOrder() { +// if (isEmpty()) { +// return EvaluationOrderImpl.ZERO; +// } else { +// return last().getEvaluationOrder(); +// } +// } @Override public AssignmentPathSegmentImpl last() { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java index 8606906ab1c..7e8684022a7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentPathSegmentImpl.java @@ -27,31 +27,173 @@ import com.evolveum.midpoint.prism.xml.XsdTypeMapper; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentPathSegmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; +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.NotNull; /** + * Primary duty of this class is to be a part of assignment path. (This is what is visible through its interface, + * AssignmentPathSegment.) However, it also serves as a place where auxiliary information about assignment evaluation + * is stored. + * * @author semancik * */ public class AssignmentPathSegmentImpl implements AssignmentPathSegment { - final ObjectType source; - final String sourceDescription; + private static final Trace LOGGER = TraceManager.getTrace(AssignmentPathSegmentImpl.class); + + // "assignment path segment" information + + final ObjectType source; // we avoid "getter" notation for some final fields to simplify client code private final ItemDeltaItem,PrismContainerDefinition> assignmentIdi; private final boolean isAssignment; // false means inducement private QName relation; private ObjectType target; - private boolean pathToSourceValid; // is the whole path to source valid? - private boolean validityOverride = false; - private EvaluationOrder evaluationOrder; - private ObjectType varThisObject; + + // assignment evaluation information + + final String sourceDescription; // Human readable text describing the source (for error messages) + private boolean pathToSourceValid; // Is the whole path to *source* valid, i.e. enabled (meaning activation.effectiveStatus)? + private boolean validityOverride = false; // Should we evaluate content of the assignment even if it's not valid i.e. enabled? + // This is set to true on the first assignment in the chain. + + /** + * Assignments and inducements can carry constructions, focus mappings, and focus policy rules. + * We can call these "assignment/inducement payload", or "payload" for short. + * + * When looking at assignments/inducements in assignment path, payload of some assignments/inducements will be collected + * to focus, while payload from others will be not. How we know what to collect? + * + * For assignments/inducements belonging directly to the focus, we take payload from all the assignments. Not from inducements. + * For assignments/inducements belonging to roles (assigned to focus), we take payload from all the inducements of order 1. + * For assignments/inducements belonging to meta-roles (assigned to roles), we take payload from all the inducements of order 2. + * And so on. (It is a bit more complicated, as described below when discussing relations. But OK for the moment.) + * + * To know whether to collect payload from assignment/inducement, i.e. from assignment path segment, we + * define "isMatchingOrder" attribute - and collect only if value of this attribute is true. + * + * How we compute this attribute? + * + * Each assignment path segment has an evaluation order. First assignment has an evaluation order of 1, second + * assignment has an order of 2, etc. Order of a segment can be seen as the number of assignments segments in the path + * (including itself). And, for "real" assignments, we collect content from assignment segments of order 1. + * + * But what about inducements? There are two - somewhat related - questions: + * + * 1. How we compute isMatchingOrder for inducement segments? + * 2. How we compute evaluation order for inducement segments? + * + * As for #1: To compute isMatchingOrder, we must take evaluation order of the _previous_ segment, and compare + * it with the order (or, more generally, order constraints) of the inducement. If they match, we say that inducement + * has matching order. + * + * As for #2: It is not usual that inducements have targets with another assignments, i.e. that evaluation continues + * after inducement segments. But it definitely could happen. We can look at it this way: inducement is something like + * a "shortcut" that creates an assignment where no assignment was before. E.g. if we have R1 -A-> MR1 -I-> MR2, + * the "newly created" assignment is R1 -A-> MR2. I.e. as if the " -A-> MR1 -I-> " part was just replaced by " -A-> ". + * If the inducement is of higher order, even more assignments are "cut out". From R1 -A-> MR1 -A-> MMR1 -(I2)-> MR2 + * we have R1 -A-> MR2, i.e. we cut out " -A-> MR1 -A-> MMR1 -(I2)-> " and replaced it by " -A-> ". + * So it looks like that when computing new evaluation order of an inducement, we have to "go back" few steps + * through the assignment path. + * TODO think this through, perhaps based on concrete examples + * It is almost certain that for some inducements we would not be able to determine the resulting order. + * Such problematic inducements are those that do not have strict order, but an interval of orders instead. + * + * Until no better algorithm is devised, we will do an approximation: when "traditional" inducement order is given, + * the we will compute the resulting order as "previous - (N-1)", where N is the order of the inducement + * (unspecified means 1). But beware, we will not manipulate evaluation order parts that are specific to relations. + * So, it is not safe to combine "higher-order inducements with targets" with non-scalar order constraints. + * + * Evaluating relations + * ==================== + * + * With the arrival of various kinds of relations (deputy, manager, approver, owner) things got a bit complicated. + * For instance, the deputy relation cannot be used to determine evaluation order in a usual way, because if + * we have this situation: + * + * Pirate -----I-----> Sailor + * A + * | (member) + * | + * jack + * A + * | (deputy) + * | + * barbossa + * + * we obviously want to have barbossa to obtain all payload from roles Pirate and Sailor: exactly as jack does. + * So, the evaluation order of " barbossa -A-> jack -A-> Pirate " should be 1, not two. So deputy is a very special + * kind of relation, that does _not_ increase the traditional evaluation order. But we really want to record + * the fact that the deputy is on the assignment path; therefore, besides traditional "scalar" evaluation order + * (called "summaryOrder") we maintain evaluation orders for each relation separately. In the above example, + * the evaluation orders would be: + * barbossa--->jack summary: 0, deputy: 1, member: 0 + * jack--->Pirate summary: 1, deputy: 1, member: 1 + * Pirate-I->Sailor summary: 1, deputy: 1, member: 1 (because the inducement has a default order of 1) + * + * When we determine matchingOrder for an inducement (question #1 above), we can ask about summary order, + * as well as about individual components (deputy and member, in this case). + * + * Actually, we have three categories of relations (see MID-3581): + * + * - membership relations: apply membership references, payload, authorizations, gui config + * - delegation relations: similar to membership, bud different handling of order + * - other relations: apply membership references but in limited way; payload, authorizations and gui config + * are - by default - not applied. + * + * Currently, membership relations are: null (i.e. member), manager, and meta. + * Delegation: deputy. + * Other: approver, owner, and custom ones. + * + * As for the "other" relations: they bring membership information, but limited to the target that is directly + * assigned. So if jack is an approver for role Landluber, his roleMembershipRef will contain ref to Landluber + * but not e.g. to role Earthworm that is induced by Landluber. In a similar way, payload from targets assigned + * by "other" relations is not collected. + * + * Both of this can be overridden by using specific orderConstraints on particular inducement. + * (TODO this is just an idea, not implemented yet) + * Set of order constraint is considered to match evaluation order with "other" relations, if for each such "other" + * relation it contains related constraint. So, if one explicitly wants an inducement to be applied when + * "approver" relation is encountered, he may do so. + * + * Note: authorizations and gui config information are not considered to be a payload, because they are not + * part of an assignment/inducement - they are part of a role. In the future we might move them into + * assignments/inducements. + * + * Collecting target policy rules + * ============================== + * + * Special consideration must be given when collecting target policy rules, i.e. rules that are attached to + * assignment targets. Such rules are typically attached to roles that are being assigned. So let's consider this: + * + * rule1 (e.g. assignment approval policy rule) + * A + * | + * | + * Pirate + * A + * | + * | + * jack + * + * When evaluating jack->Pirate assignment, rule1 would not be normally taken into account, because its assignment + * (Pirate->rule1) has an order of 2. However, we want to collect it - but not as an item related to focus, but + * as an item related to evaluated assignment's target. Therefore besides isMatchingOrder we maintain isMatchingOrderPlusOne + * that marks all segments (assignments/inducements) that contain policy rules relevant to the evaluated assignment's target. + * + * TODO how exactly do we compute it + */ private Boolean isMatchingOrder = null; + private EvaluationOrder evaluationOrder; + private Boolean isMatchingOrderPlusOne = null; + private boolean processMembership = false; + private ObjectType varThisObject; + AssignmentPathSegmentImpl(ObjectType source, String sourceDescription, ItemDeltaItem, PrismContainerDefinition> assignmentIdi, boolean isAssignment) { @@ -121,16 +263,20 @@ public void setValidityOverride(boolean validityOverride) { this.validityOverride = validityOverride; } - @Override public EvaluationOrder getEvaluationOrder() { return evaluationOrder; } public void setEvaluationOrder(EvaluationOrder evaluationOrder) { + setEvaluationOrder(evaluationOrder, null, null); + } + + public void setEvaluationOrder(EvaluationOrder evaluationOrder, Boolean matchingOrder, Boolean matchingOrderPlusOne) { this.evaluationOrder = evaluationOrder; + this.isMatchingOrder = matchingOrder; + this.isMatchingOrderPlusOne = matchingOrderPlusOne; } - @Override public ObjectType getOrderOneObject() { return varThisObject; } @@ -147,40 +293,49 @@ public void setProcessMembership(boolean processMembership) { this.processMembership = processMembership; } + /** + * Whether this assignment/inducement matches the focus level, i.e. if we should collect constructions, + * focus mappings, and focus policy rules from it. + */ public boolean isMatchingOrder() { if (isMatchingOrder == null) { - isMatchingOrder = computeMatchingOrder(0); + isMatchingOrder = computeMatchingOrder(getAssignment(), evaluationOrder, 0); } return isMatchingOrder; } public boolean isMatchingOrderPlusOne() { if (isMatchingOrderPlusOne == null) { - isMatchingOrderPlusOne = computeMatchingOrder(1); + isMatchingOrderPlusOne = computeMatchingOrder(getAssignment(), evaluationOrder, 1); } return isMatchingOrderPlusOne; } - private boolean computeMatchingOrder(int offset) { - AssignmentType assignmentType = getAssignment(); + static boolean computeMatchingOrder(AssignmentType assignmentType, EvaluationOrder evaluationOrder, int offset) { + boolean rv; if (assignmentType.getOrder() == null && assignmentType.getOrderConstraint().isEmpty()) { // compatibility - return evaluationOrder.getSummaryOrder() - offset == 1; - } - if (assignmentType.getOrder() != null) { - if (evaluationOrder.getSummaryOrder() - offset != assignmentType.getOrder()) { - return false; + rv = evaluationOrder.getSummaryOrder() - offset == 1; + } else { + rv = true; + if (assignmentType.getOrder() != null) { + if (evaluationOrder.getSummaryOrder() - offset != assignmentType.getOrder()) { + rv = false; + } } - } - for (OrderConstraintsType orderConstraint: assignmentType.getOrderConstraint()) { - if (!isMatchingConstraint(orderConstraint, offset)) { - return false; + for (OrderConstraintsType orderConstraint : assignmentType.getOrderConstraint()) { + if (!isMatchingConstraint(orderConstraint, evaluationOrder, offset)) { + rv = false; + break; + } } } - return true; + LOGGER.trace("computeMatchingOrder => {}, for offset={}; assignment.order={}, assignment.orderConstraint={}, evaluationOrder={} ... assignment = {}", + rv, offset, assignmentType.getOrder(), assignmentType.getOrderConstraint(), evaluationOrder); + return rv; } - private boolean isMatchingConstraint(OrderConstraintsType orderConstraint, int offset) { + private static boolean isMatchingConstraint(OrderConstraintsType orderConstraint, EvaluationOrder evaluationOrder, int offset) { int evaluationOrderInt = evaluationOrder.getMatchingRelationOrder(orderConstraint.getRelation()) - offset; if (orderConstraint.getOrder() != null) { return orderConstraint.getOrder() == evaluationOrderInt; @@ -246,24 +401,31 @@ public String toString() { if (isMatchingOrder()) { // here is a side effect but most probably it's harmless sb.append("(match)"); } - if (isMatchingOrderPlusOne()) { + if (isMatchingOrderPlusOne()) { // the same here sb.append("(match+1)"); } sb.append(": "); sb.append(source).append(" "); PrismContainer assignment = (PrismContainer) assignmentIdi.getAnyItem(); - if (assignment != null) { - AssignmentType assignmentType = assignment.getValue().asContainerable(); + AssignmentType assignmentType = assignment != null ? assignment.getValue().asContainerable() : null; + if (assignmentType != null) { + sb.append("id:").append(assignmentType.getId()).append(" "); if (assignmentType.getConstruction() != null) { sb.append("Constr '").append(assignmentType.getConstruction().getDescription()).append("' "); } } - if (target != null) { + ObjectReferenceType targetRef = assignmentType != null ? assignmentType.getTargetRef() : null; + if (target != null || targetRef != null) { sb.append("-["); if (relation != null) { sb.append(relation.getLocalPart()); } - sb.append("]-> ").append(target); + sb.append("]-> "); + if (target != null) { + sb.append(target); + } else { + sb.append(ObjectTypeUtil.toShortString(targetRef, true)); + } } sb.append(")"); return sb.toString(); @@ -279,18 +441,28 @@ public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.debugDumpLabel(sb, "AssignmentPathSegment", indent); sb.append("\n"); - DebugUtil.debugDumpWithLabelLn(sb, "isMatchingOrder", isMatchingOrder, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "processMembership", processMembership, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "validityOverride", validityOverride, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "source", source==null?"null":source.toString(), indent + 1); + String assignmentOrInducement = isAssignment ? "assignment" : "inducement"; + if (assignmentIdi != null) { + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " old", String.valueOf(assignmentIdi.getItemOld()), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " delta", String.valueOf(assignmentIdi.getDelta()), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement + " new", String.valueOf(assignmentIdi.getItemNew()), indent + 1); + } else { + DebugUtil.debugDumpWithLabelLn(sb, assignmentOrInducement, "null", indent + 1); + } + DebugUtil.debugDumpWithLabelLn(sb, "target", target==null?"null":target.toString(), indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "evaluationOrder", evaluationOrder, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "assignment", assignmentIdi.toString(), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "isMatchingOrder", isMatchingOrder, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "isMatchingOrderPlusOne", isMatchingOrderPlusOne, indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "relation", relation, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "target", target==null?"null":target.toString(), indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "source", source==null?"null":source.toString(), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "pathToSourceValid", pathToSourceValid, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "validityOverride", validityOverride, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "processMembership", processMembership, indent + 1); DebugUtil.debugDumpWithLabel(sb, "varThisObject", varThisObject==null?"null":varThisObject.toString(), indent + 1); return sb.toString(); } + @NotNull @Override public AssignmentPathSegmentType toAssignmentPathSegmentType() { AssignmentPathSegmentType rv = new AssignmentPathSegmentType(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java index ed2e5061bc1..2f42f63f9ae 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluationOrderImpl.java @@ -58,27 +58,41 @@ public EvaluationOrder advance() { @Override public EvaluationOrder advance(QName relation) { + return advance(1, relation); + } + + private EvaluationOrder advance(int amount, QName relation) { EvaluationOrderImpl advanced = new EvaluationOrderImpl(); boolean found = false; for (Entry entry: orderMap.entrySet()) { if (QNameUtil.match(entry.getKey(), relation)) { - advanced.orderMap.put(entry.getKey(), entry.getValue() + 1); + advanced.orderMap.put(entry.getKey(), entry.getValue() + amount); found = true; } else { advanced.orderMap.put(entry.getKey(), entry.getValue()); } } if (!found) { - advanced.orderMap.put(relation, 1); + advanced.orderMap.put(relation, amount); } if (DeputyUtils.isDelegationRelation(relation)) { advanced.summaryOrder = this.summaryOrder; } else { - advanced.summaryOrder = this.summaryOrder + 1; + advanced.summaryOrder = this.summaryOrder + amount; } return advanced; } - + + @Override + public EvaluationOrder decrease(int amount) { + return decrease(amount, null); + } + + @Override + public EvaluationOrder decrease(int amount, QName relation) { + return advance(-amount, relation); + } + @Override public int getMatchingRelationOrder(QName relation) { for (Entry entry: orderMap.entrySet()) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 16ba45f8d2a..5ba6ba9d348 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -1062,7 +1062,7 @@ public static AssignmentPathVariables computeAssignmentPathVariables(AssignmentP } } - AssignmentPathSegmentImpl focusAssignmentSegment = assignmentPath.getFirstAssignmentSegment(); + AssignmentPathSegmentImpl focusAssignmentSegment = assignmentPath.first(); ItemDeltaItem,PrismContainerDefinition> focusAssignment = focusAssignmentSegment.getAssignmentIdi().clone(); vars.setFocusAssignment(focusAssignment); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ExecutionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ExecutionContext.java index 1a8af228cc2..906d6affc88 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ExecutionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ExecutionContext.java @@ -23,6 +23,7 @@ import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionEvaluationOptionsType; import java.util.HashMap; import java.util.List; @@ -37,19 +38,29 @@ public class ExecutionContext { private static final Trace LOGGER = TraceManager.getTrace(ExecutionContext.class); private final Task task; - private StringBuilder consoleOutput = new StringBuilder(); - private Map variables = new HashMap<>(); + private final ScriptingExpressionEvaluationOptionsType options; + private final StringBuilder consoleOutput = new StringBuilder(); + private final Map variables = new HashMap<>(); private Data finalOutput; // used only when passing result to external clients (TODO do this more cleanly) - public ExecutionContext(Task task) { + public ExecutionContext(ScriptingExpressionEvaluationOptionsType options, Task task) { this.task = task; + this.options = options; } public Task getTask() { return task; } - public Data getVariable(String variableName) { + public ScriptingExpressionEvaluationOptionsType getOptions() { + return options; + } + + public boolean isContinueOnAnyError() { + return options != null && Boolean.TRUE.equals(options.isContinueOnAnyError()); + } + + public Data getVariable(String variableName) { return variables.get(variableName); } @@ -68,7 +79,7 @@ public String getConsoleOutput() { public void println(Object o) { consoleOutput.append(o).append("\n"); if (o != null) { - LOGGER.info(o.toString()); // temporary, until some better way of logging bulk action executions is found + LOGGER.info("Script console message: {}", o); // temporary, until some better way of logging bulk action executions is found } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptExecutionTaskHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptExecutionTaskHandler.java index e53e8d91387..9f76c91999f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptExecutionTaskHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptExecutionTaskHandler.java @@ -73,7 +73,7 @@ public TaskRunResult run(Task task) { try { task.startCollectingOperationStatsFromZero(true, false, true); task.setProgress(0); - ScriptExecutionResult executionResult = scriptingService.evaluateExpression(executeScriptProperty.getValue().getValue().getScriptingExpression().getValue(), task, result); + ScriptExecutionResult executionResult = scriptingService.evaluateExpression(executeScriptProperty.getRealValue(), task, result); LOGGER.debug("Execution output: {} item(s)", executionResult.getDataOutput().size()); LOGGER.debug("Execution result:\n", executionResult.getConsoleOutput()); result.computeStatus(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java index 28d04d854e3..5c7c0e214a8 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/ScriptingExpressionEvaluator.java @@ -30,16 +30,7 @@ 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.model.scripting_3.ActionExpressionType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExpressionPipelineType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExpressionSequenceType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.FilterExpressionType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ForeachExpressionType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ObjectFactory; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.SearchExpressionType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.SelectExpressionType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.*; import com.evolveum.prism.xml.ns._public.types_3.RawType; import org.apache.commons.lang.NotImplementedException; @@ -97,6 +88,7 @@ public class ScriptingExpressionEvaluator { * @param parentResult * @throws SchemaException */ + @Deprecated public void evaluateExpressionInBackground(QName objectType, ObjectFilter filter, String actionName, Task task, OperationResult parentResult) throws SchemaException { Validate.notNull(objectType); Validate.notNull(actionName); @@ -151,38 +143,40 @@ public void evaluateExpressionInBackground(ScriptingExpressionType expression, T */ public ExecutionContext evaluateExpression(ScriptingExpressionType expression, Task task, OperationResult result) throws ScriptExecutionException { - ExecutionContext context = new ExecutionContext(task); - Data output; - try { - output = evaluateExpression(expression, Data.createEmpty(), context, result); - } catch (RuntimeException e) { - result.recordFatalError("Couldn't execute script", e); - throw new ScriptExecutionException("Couldn't execute script: " + e.getMessage(), e); - } - result.computeStatusIfUnknown(); - context.setFinalOutput(output); - return context; + return evaluateExpression(expression, Data.createEmpty(), null, task, result); } -// public ExecutionContext evaluateExpression(JAXBElement expression, Task task, OperationResult result) throws ScriptExecutionException { -// ExecutionContext context = new ExecutionContext(task); -// Data output = evaluateExpression(expression.getValue(), Data.createEmpty(), context, result); -// result.computeStatusIfUnknown(); -// context.setFinalOutput(output); -// return context; -// } - public ExecutionContext evaluateExpression(ExecuteScriptType executeScript, Task task, OperationResult result) throws ScriptExecutionException { - return evaluateExpression(executeScript.getScriptingExpression().getValue(), task, result); + public ExecutionContext evaluateExpression(@NotNull ExecuteScriptType executeScript, Task task, OperationResult result) throws ScriptExecutionException { + // TODO parse input data + Validate.notNull(executeScript.getScriptingExpression(), "Scripting expression must be present"); + return evaluateExpression(executeScript.getScriptingExpression().getValue(), Data.createEmpty(), executeScript.getOptions(), task, result); } // use in tests only (we need the task at least to report progress/statistics) + @Deprecated public ExecutionContext evaluateExpression(ScriptingExpressionType expression, OperationResult result) throws ScriptExecutionException { Task task = taskManager.createTaskInstance(); return evaluateExpression(expression, task, result); } - public Data evaluateExpression(JAXBElement expression, Data input, ExecutionContext context, OperationResult parentResult) throws ScriptExecutionException { + // main entry point from the outside + private ExecutionContext evaluateExpression(ScriptingExpressionType expression, Data data, + ScriptingExpressionEvaluationOptionsType options, Task task, OperationResult result) throws ScriptExecutionException { + ExecutionContext context = new ExecutionContext(options, task); + Data output; + try { + output = evaluateExpression(expression, data, context, result); + } catch (RuntimeException e) { + result.recordFatalError("Couldn't execute script", e); + throw new ScriptExecutionException("Couldn't execute script: " + e.getMessage(), e); + } + result.computeStatusIfUnknown(); + context.setFinalOutput(output); + return context; + } + + public Data evaluateExpression(JAXBElement expression, Data input, ExecutionContext context, OperationResult parentResult) throws ScriptExecutionException { return evaluateExpression(expression.getValue(), input, context, parentResult); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AddExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AddExecutor.java index c0cda49265b..9eea25609ff 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AddExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AddExecutor.java @@ -25,8 +25,6 @@ import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ActionExpressionType; import org.springframework.beans.factory.annotation.Autowired; @@ -40,8 +38,6 @@ @Component public class AddExecutor extends BaseActionExecutor { - private static final Trace LOGGER = TraceManager.getTrace(AddExecutor.class); - private static final String NAME = "add"; @Autowired @@ -64,22 +60,24 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject prismObject = ((PrismObjectValue) value).asPrismObject(); ObjectType objectType = prismObject.asObjectable(); long started = operationsHelper.recordStart(context, objectType); + Throwable exception = null; try { operationsHelper.applyDelta(createAddDelta(objectType), operationsHelper.createExecutionOptions(raw), dryRun, context, result); operationsHelper.recordEnd(context, objectType, started, null); } catch (Throwable ex) { operationsHelper.recordEnd(context, objectType, started, ex); - throw ex; // TODO think about this + exception = processActionException(ex, NAME, value, context); } - context.println("Added " + prismObject.toString() + rawDrySuffix(raw, dryRun)); + context.println((exception != null ? "Attempted to add " : "Added ") + prismObject.toString() + rawDrySuffix(raw, dryRun) + exceptionSuffix(exception)); } else { - throw new ScriptExecutionException("Item couldn't be added, because it is not a PrismObject: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), NAME, value, context); } } return Data.createEmpty(); // todo return oid(s) in the future } - private ObjectDelta createAddDelta(ObjectType objectType) { + private ObjectDelta createAddDelta(ObjectType objectType) { return ObjectDelta.createAddDelta(objectType.asPrismObject()); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AssignExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AssignExecutor.java index c6a68a83c99..b13a754b9f4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AssignExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/AssignExecutor.java @@ -95,16 +95,18 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject prismObject = ((PrismObjectValue) value).asPrismObject(); ObjectType objectType = prismObject.asObjectable(); long started = operationsHelper.recordStart(context, objectType); + Throwable exception = null; try { operationsHelper.applyDelta(createDelta(objectType, resources, roles), operationsHelper.createExecutionOptions(raw), dryRun, context, result); operationsHelper.recordEnd(context, objectType, started, null); } catch (Throwable ex) { operationsHelper.recordEnd(context, objectType, started, ex); - throw ex; // TODO reconsider this + exception = processActionException(ex, NAME, value, context); } - context.println("Modified " + prismObject.toString() + rawDrySuffix(raw, dryRun)); + context.println((exception != null ? "Attempted to modify " : "Modified ") + prismObject.toString() + rawDrySuffix(raw, dryRun) + exceptionSuffix(exception)); } else { - throw new ScriptExecutionException("Item could not be modified, because it is not a PrismObject of FocusType: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject of FocusType"), NAME, value, context); } } return Data.createEmpty(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/BaseActionExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/BaseActionExecutor.java index 56f9c36e0ac..7b953ade97c 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/BaseActionExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/BaseActionExecutor.java @@ -25,10 +25,14 @@ import com.evolveum.midpoint.model.impl.scripting.helpers.ExpressionHelper; import com.evolveum.midpoint.model.impl.scripting.helpers.OperationsHelper; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.security.api.SecurityEnforcer; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ActionExpressionType; import org.springframework.beans.factory.annotation.Autowired; @@ -37,7 +41,9 @@ */ public abstract class BaseActionExecutor implements ActionExecutor { - private static final String PARAM_RAW = "raw"; + private static final Trace LOGGER = TraceManager.getTrace(AddExecutor.class); + + private static final String PARAM_RAW = "raw"; private static final String PARAM_DRY_RUN = "dryRun"; @Autowired @@ -82,4 +88,17 @@ protected String rawDrySuffix(boolean raw, boolean dry) { return rawSuffix(raw) + drySuffix(dry); } + protected String exceptionSuffix(Throwable t) { + return t != null ? " (error: " + t.getClass().getSimpleName() + ": " + t.getMessage() + ")" : ""; + } + + protected Throwable processActionException(Throwable e, String actionName, PrismValue value, ExecutionContext context) throws ScriptExecutionException { + if (context.isContinueOnAnyError()) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't execute action '{}' on {}: {}", e, + actionName, value, e.getMessage()); + return e; + } else { + throw new ScriptExecutionException("Couldn't execute action '" + actionName + "' on " + value + ": " + e.getMessage(), e); + } + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DeleteExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DeleteExecutor.java index 31f98270600..1797ff2c43c 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DeleteExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DeleteExecutor.java @@ -60,22 +60,24 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex boolean raw = getParamRaw(expression, input, context, result); boolean dryRun = getParamDryRun(expression, input, context, result); - for (PrismValue item : input.getData()) { + for (PrismValue value : input.getData()) { context.checkTaskStop(); - if (item instanceof PrismObjectValue) { - PrismObject prismObject = ((PrismObjectValue) item).asPrismObject(); + if (value instanceof PrismObjectValue) { + PrismObject prismObject = ((PrismObjectValue) value).asPrismObject(); ObjectType objectType = prismObject.asObjectable(); long started = operationsHelper.recordStart(context, objectType); + Throwable exception = null; try { operationsHelper.applyDelta(createDeleteDelta(objectType), operationsHelper.createExecutionOptions(raw), dryRun, context, result); operationsHelper.recordEnd(context, objectType, started, null); } catch (Throwable ex) { operationsHelper.recordEnd(context, objectType, started, ex); - throw ex; // TODO think about this + exception = processActionException(ex, NAME, value, context); } - context.println("Deleted " + prismObject.toString() + rawDrySuffix(raw, dryRun)); + context.println((exception != null ? "Attempted to delete " : "Deleted ") + prismObject.toString() + rawDrySuffix(raw, dryRun) + exceptionSuffix(exception)); } else { - throw new ScriptExecutionException("Item couldn't be deleted, because it is not a PrismObject: " + item.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), NAME, value, context); } } return Data.createEmpty(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DiscoverConnectorsExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DiscoverConnectorsExecutor.java index 0b5642507f0..c2729c4e69a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DiscoverConnectorsExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/DiscoverConnectorsExecutor.java @@ -46,10 +46,7 @@ import javax.annotation.PostConstruct; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @author mederly @@ -80,22 +77,31 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject connectorHostTypePrismObject = ((PrismObjectValue) value).asPrismObject(); Set newConnectors; long started = operationsHelper.recordStart(context, connectorHostTypePrismObject.asObjectable()); + Throwable exception = null; try { newConnectors = modelService.discoverConnectors(connectorHostTypePrismObject.asObjectable(), context.getTask(), result); operationsHelper.recordEnd(context, connectorHostTypePrismObject.asObjectable(), started, null); } catch (CommunicationException | SecurityViolationException | SchemaException | ConfigurationException | ObjectNotFoundException | RuntimeException e) { operationsHelper.recordEnd(context, connectorHostTypePrismObject.asObjectable(), started, e); - throw new ScriptExecutionException("Couldn't discover connectors from " + connectorHostTypePrismObject, e); - } - context.println("Discovered " + newConnectors.size() + " new connector(s) from " + connectorHostTypePrismObject); + exception = processActionException(e, NAME, value, context); + newConnectors = Collections.emptySet(); + } + context.println((exception != null ? "Attempted to discover " : "Discovered " + newConnectors.size()) + + " new connector(s) from " + connectorHostTypePrismObject + exceptionSuffix(exception)); for (ConnectorType connectorType : newConnectors) { output.addItem(connectorType.asPrismObject()); } - if (rebind) { - rebindConnectors(newConnectors, context, result); - } + try { + if (rebind) { + rebindConnectors(newConnectors, context, result); + } + } catch (ScriptExecutionException e) { + //noinspection ThrowableNotThrown + processActionException(e, NAME, value, context); // TODO better message + } } else { - throw new ScriptExecutionException("Input is not a PrismObject: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Input item is not a PrismObject"), NAME, value, context); } } return output; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/EnableDisableExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/EnableDisableExecutor.java index 67adcb13578..6f7636e3fbf 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/EnableDisableExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/EnableDisableExecutor.java @@ -74,23 +74,25 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject prismObject = ((PrismObjectValue) value).asPrismObject(); ObjectType objectType = prismObject.asObjectable(); long started = operationsHelper.recordStart(context, objectType); + Throwable exception = null; try { if (objectType instanceof FocusType) { operationsHelper.applyDelta(createEnableDisableDelta((FocusType) objectType, isEnable), operationsHelper.createExecutionOptions(raw), dryRun, context, result); - context.println((isEnable ? "Enabled " : "Disabled ") + prismObject.toString() + rawDrySuffix(raw, dryRun)); } else if (objectType instanceof ShadowType) { operationsHelper.applyDelta(createEnableDisableDelta((ShadowType) objectType, isEnable), context, result); - context.println((isEnable ? "Enabled " : "Disabled ") + prismObject.toString() + rawDrySuffix(raw, dryRun)); } else { - throw new ScriptExecutionException("Item could not be enabled/disabled, because it is not a FocusType nor ShadowType: " + value.toString()); + throw new ScriptExecutionException("Item is not a FocusType nor ShadowType: " + value.toString()); } operationsHelper.recordEnd(context, objectType, started, null); } catch (Throwable ex) { operationsHelper.recordEnd(context, objectType, started, ex); - throw ex; + exception = processActionException(ex, expression.getType(), value, context); } - } else { - throw new ScriptExecutionException("Item could not be enabled/disabled, because it is not a PrismObject: " + value.toString()); + context.println((exception != null ? "Attempted to " + expression.getType() : (isEnable ? "Enabled " : "Disabled ")) + + prismObject.toString() + rawDrySuffix(raw, dryRun) + exceptionSuffix(exception)); + } else { + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), expression.getType(), value, context); } } return Data.createEmpty(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ModifyExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ModifyExecutor.java index 10f4783ee91..4dde61e6009 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ModifyExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ModifyExecutor.java @@ -67,16 +67,18 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject prismObject = ((PrismObjectValue) value).asPrismObject(); ObjectType objectType = prismObject.asObjectable(); long started = operationsHelper.recordStart(context, objectType); + Throwable exception = null; try { operationsHelper.applyDelta(createDelta(objectType, deltaData), operationsHelper.createExecutionOptions(raw), dryRun, context, result); operationsHelper.recordEnd(context, objectType, started, null); } catch (Throwable ex) { operationsHelper.recordEnd(context, objectType, started, ex); - throw ex; + exception = processActionException(ex, NAME, value, context); } - context.println("Modified " + prismObject.toString() + rawDrySuffix(raw, dryRun)); + context.println((exception != null ? "Attempted to modify " : "Modified ") + prismObject.toString() + rawDrySuffix(raw, dryRun) + exceptionSuffix(exception)); } else { - throw new ScriptExecutionException("Item could not be modified, because it is not a PrismObject: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), NAME, value, context); } } return Data.createEmpty(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/PurgeSchemaExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/PurgeSchemaExecutor.java index 55b5a296321..92cca2d6dae 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/PurgeSchemaExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/PurgeSchemaExecutor.java @@ -72,10 +72,12 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex operationsHelper.recordEnd(context, resourceType, started, null); } catch (Throwable ex) { operationsHelper.recordEnd(context, resourceType, started, ex); - throw ex; + Throwable exception = processActionException(ex, NAME, value, context); + context.println("Couldn't purge schema information from " + resourceTypePrismObject + exceptionSuffix(exception)); } } else { - throw new ScriptExecutionException("Couldn't purge resource schema, because input is not a PrismObject: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), NAME, value, context); } } return output; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/RecomputeExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/RecomputeExecutor.java index c55322a4317..ef60b0ae480 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/RecomputeExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/RecomputeExecutor.java @@ -71,6 +71,7 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject focalPrismObject = ((PrismObjectValue) value).asPrismObject(); FocusType focusType = focalPrismObject.asObjectable(); long started = operationsHelper.recordStart(context, focusType); + Throwable exception = null; try { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Recomputing object {} with dryRun={}: context:\n{}", focalPrismObject, dryRun); @@ -81,11 +82,12 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex operationsHelper.recordEnd(context, focusType, started, null); } catch (Throwable e) { operationsHelper.recordEnd(context, focusType, started, e); - throw e; + exception = processActionException(e, NAME, value, context); } - context.println("Recomputed " + focalPrismObject.toString() + drySuffix(dryRun)); + context.println((exception != null ? "Attempted to recompute " : "Recomputed ") + focalPrismObject.toString() + drySuffix(dryRun) + exceptionSuffix(exception)); } else { - throw new ScriptExecutionException("Item could not be recomputed, because it is not a focal PrismObject: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), NAME, value, context); } } return Data.createEmpty(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ResolveExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ResolveExecutor.java index d8836d28ee9..7fb053220f2 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ResolveExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ResolveExecutor.java @@ -69,10 +69,15 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex if (typeClass == null) { throw new ScriptExecutionException("Couldn't resolve reference, because target type class is unknown for target type " + targetTypeQName); } - PrismObject prismObject = operationsHelper.getObject(typeClass, oid, noFetch, context, result); - output.addValue(prismObject.getValue()); + try { + output.addValue(operationsHelper.getObject(typeClass, oid, noFetch, context, result).getValue()); + } catch (Throwable e) { + //noinspection ThrowableNotThrown + processActionException(e, NAME, value, context); + } } else { - throw new ScriptExecutionException("Item could not be resolved, because it is not a PrismReference: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismReference"), NAME, value, context); } } return output; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java index 0c44619c171..b874d8485bc 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ScriptExecutor.java @@ -93,6 +93,7 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex started = 0; valueDescription = value.toHumanReadableString(); } + Throwable exception = null; try { Object outObject = executeScript(scriptExpression, value, context, result); if (outObject != null) { @@ -105,16 +106,17 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex if (value instanceof PrismObjectValue) { operationsHelper.recordEnd(context, asObjectType(value), started, ex); } - throw new ScriptExecutionException("Couldn't execute script action: " + ex.getMessage(), ex); + exception = processActionException(ex, NAME, value, context); } - context.println("Executed script on " + valueDescription); + context.println((exception != null ? "Attempted to execute " : "Executed ") + + "script on " + valueDescription + exceptionSuffix(exception)); } return output; } private void addToData(Object outObject, Data output) throws SchemaException { if (outObject == null) { - return; + // nothing to do } else if (outObject instanceof Collection) { for (Object o : (Collection) outObject) { addToData(o, output); @@ -158,7 +160,7 @@ private Object executeScript(ScriptExpression scriptExpression, PrismValue prism variables.addVariableDefinition(ExpressionConstants.VAR_PRISM_CONTEXT, prismContext); ExpressionUtil.addActorVariable(variables, securityEnforcer); - List rv = Utils.evaluateScript(scriptExpression, null, variables, true, "script action", context.getTask(), result); + List rv = Utils.evaluateScript(scriptExpression, null, variables, true, "in '"+NAME+"' action", context.getTask(), result); if (rv == null || rv.size() == 0) { return null; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/TestResourceExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/TestResourceExecutor.java index 82fed31ce7b..154073bfac6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/TestResourceExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/TestResourceExecutor.java @@ -72,82 +72,25 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject resourceTypePrismObject = ((PrismObjectValue) value).asPrismObject(); ResourceType resourceType = resourceTypePrismObject.asObjectable(); long started = operationsHelper.recordStart(context, resourceType); + Throwable exception = null; OperationResult testResult; try { testResult = modelService.testResource(resourceTypePrismObject.getOid(), context.getTask()); operationsHelper.recordEnd(context, resourceType, started, null); } catch (ObjectNotFoundException|RuntimeException e) { operationsHelper.recordEnd(context, resourceType, started, e); - throw new ScriptExecutionException("Couldn't test resource " + resourceTypePrismObject, e); + exception = processActionException(e, NAME, value, context); + testResult = new OperationResult(TestResourceExecutor.class.getName() + ".testResource"); + testResult.recordFatalError(e); } result.addSubresult(testResult); - context.println("Tested " + resourceTypePrismObject + ": " + testResult.getStatus()); + context.println("Tested " + resourceTypePrismObject + ": " + testResult.getStatus() + exceptionSuffix(exception)); output.addItem(operationsHelper.getObject(ResourceType.class, resourceTypePrismObject.getOid(), false, context, result)); } else { - throw new ScriptExecutionException("Couldn't test a resource, because input is not a PrismObject: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), NAME, value, context); } } return output; } - - private void rebindConnectors(Set newConnectors, ExecutionContext context, OperationResult result) throws ScriptExecutionException { - Map rebindMap = new HashMap<>(); - for (ConnectorType connectorType : newConnectors) { - determineConnectorMappings(rebindMap, connectorType, context, result); - } - LOGGER.trace("Connector rebind map: {}", rebindMap); - rebindResources(rebindMap, context, result); - } - - private void rebindResources(Map rebindMap, ExecutionContext context, OperationResult result) throws ScriptExecutionException { - List> resources; - try { - resources = modelService.searchObjects(ResourceType.class, null, null, null, result); - } catch (SchemaException|ConfigurationException|ObjectNotFoundException|CommunicationException|SecurityViolationException e) { - throw new ScriptExecutionException("Couldn't list resources: " + e.getMessage(), e); - } - for (PrismObject resource : resources) { - if (resource.asObjectable().getConnectorRef() != null) { - String connectorOid = resource.asObjectable().getConnectorRef().getOid(); - String newOid = rebindMap.get(connectorOid); - if (newOid != null) { - String msg = "resource " + resource + " from connector " + connectorOid + " to new one: " + newOid; - LOGGER.info("Rebinding " + msg); - ReferenceDelta refDelta = ReferenceDelta.createModificationReplace(ResourceType.F_CONNECTOR_REF, resource.getDefinition(), newOid); - ObjectDelta objDelta = ObjectDelta.createModifyDelta(resource.getOid(), refDelta, ResourceType.class, prismContext); - operationsHelper.applyDelta(objDelta, context, result); - context.println("Rebound " + msg); - } - } - } - } - - private void determineConnectorMappings(Map rebindMap, ConnectorType connectorType, ExecutionContext context, OperationResult result) throws ScriptExecutionException { - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Finding obsolete versions for connector: {}", connectorType.asPrismObject().debugDump()); - } - ObjectQuery query = QueryBuilder.queryFor(ConnectorType.class, prismContext) - .item(SchemaConstants.C_CONNECTOR_FRAMEWORK).eq(connectorType.getFramework()) - .and().item(SchemaConstants.C_CONNECTOR_CONNECTOR_TYPE).eq(connectorType.getConnectorType()) - .build(); - List> foundConnectors; - try { - foundConnectors = modelService.searchObjects(ConnectorType.class, query, null, null, result); - } catch (SchemaException|ConfigurationException|ObjectNotFoundException|CommunicationException|SecurityViolationException e) { - throw new ScriptExecutionException("Couldn't get connectors of type: " + connectorType.getConnectorType() + ": " + e.getMessage(), e); - } - - for (PrismObject foundConnector : foundConnectors) { - ConnectorType foundConnectorType = foundConnector.asObjectable(); - if (connectorType.getConnectorHostRef().equals(foundConnectorType.getConnectorHostRef()) && - foundConnectorType.getConnectorVersion() != null && - !foundConnectorType.getConnectorVersion().equals(connectorType.getConnectorVersion())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Found obsolete connector: {}", foundConnectorType.asPrismObject().debugDump()); - } - rebindMap.put(foundConnectorType.getOid(), connectorType.getOid()); - } - } - } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ValidateExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ValidateExecutor.java index 0111fdab54a..6ffdd4212e3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ValidateExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/actions/ValidateExecutor.java @@ -64,8 +64,6 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex PrismObject resourceTypePrismObject = ((PrismObjectValue) value).asPrismObject(); ResourceType resourceType = resourceTypePrismObject.asObjectable(); long started = operationsHelper.recordStart(context, resourceType); - - //new PrismContainer(, prismContext); try { ValidationResult validationResult = resourceValidator.validate(resourceTypePrismObject, Scope.THOROUGH, null, context.getTask(), result); @@ -78,11 +76,12 @@ public Data execute(ActionExpressionType expression, Data input, ExecutionContex } catch (SchemaException|RuntimeException e) { operationsHelper.recordEnd(context, resourceType, started, e); context.println("Error validation " + resourceTypePrismObject + ": " + e.getMessage()); - throw new ScriptExecutionException("Couldn't validate resource " + resourceTypePrismObject, e); + //noinspection ThrowableNotThrown + processActionException(e, NAME, value, context); } - } else { - throw new ScriptExecutionException("Couldn't test a resource, because input is not a PrismObject: " + value.toString()); + //noinspection ThrowableNotThrown + processActionException(new ScriptExecutionException("Item is not a PrismObject"), NAME, value, context); } } return output; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/expressions/SearchEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/expressions/SearchEvaluator.java index 68763d9c782..ae359f0aca0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/expressions/SearchEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/expressions/SearchEvaluator.java @@ -30,6 +30,9 @@ import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.SearchExpressionType; @@ -46,7 +49,9 @@ @Component public class SearchEvaluator extends BaseExpressionEvaluator { - @Autowired + private static final Trace LOGGER = TraceManager.getTrace(SearchEvaluator.class); + + @Autowired private ExpressionHelper expressionHelper; @Autowired @@ -105,7 +110,12 @@ public boolean handle(PrismObject object, OperationResult parentResult) { result.setSummarizeSuccesses(true); result.summarize(); } catch (ScriptExecutionException e) { - throw new SystemException(e); // todo think about this + // todo think about this + if (context.isContinueOnAnyError()) { + LoggingUtils.logUnexpectedException(LOGGER, "Exception when evaluating item from search result list.", e); + } else { + throw new SystemException(e); + } } } else { outputData.addItem(object); @@ -117,6 +127,7 @@ public boolean handle(PrismObject object, OperationResult parentResult) { try { modelService.searchObjectsIterative(objectClass, objectQuery, handler, operationsHelper.createGetOptions(noFetch), context.getTask(), result); } catch (SchemaException | ObjectNotFoundException | SecurityViolationException | CommunicationException | ConfigurationException e) { + // TODO continue on any error? throw new ScriptExecutionException("Couldn't execute searchObjects operation: " + e.getMessage(), e); } diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java new file mode 100644 index 00000000000..467a807377f --- /dev/null +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2010-2017 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.model.impl.lens; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.model.impl.lens.projector.AssignmentProcessor; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ActivationUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.testng.annotations.Test; + +import javax.xml.bind.JAXBException; +import javax.xml.namespace.QName; +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import static com.evolveum.midpoint.test.IntegrationTestTools.display; +import static com.evolveum.midpoint.test.IntegrationTestTools.displayObjectTypeCollection; +import static com.evolveum.midpoint.test.util.TestUtil.assertSuccess; +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.fail; + +/** + * Comprehensive test of assignment evaluator and processor. + * + * MMR1 -----------I------------------------------* + * ^ | + * | I + * | V + * MR1 -----------I-------------*-----> MR3 MR4 + * ^ MR2 --I---* | | | + * | ^ I I I I + * | | V V V V + * R1 --I--> R2 O3 R4 R5 R6 + * ^ + * | + * | + * jack + * + * Straight line means assignment. + * Line marked with "I" means inducement. + * + * Orders of these inducements are given by the levels of participants, so that each induced role belongs to jack, and each + * induced metarole belongs to some role. So, + * - inducement Rx->Ry is of order 1 + * - inducement MRx->MRy is of order 1 + * - inducement MRx->Ry is of order 2 + * - inducement MMRx->MRy is of order 1 + * + * Each role has authorization, construction, focus mapping, focus policy rule and target policy rule. + * + * Each assignment and each role can be selectively enabled/disabled (via activation) and has its condition matched (none/old/new/old+new). + * + * @author mederly + */ +public class TestAssignmentProcessor2 extends AbstractLensTest { + + private static final int CONSTRUCTION_LEVELS = 5; + private static final int FOCUS_MAPPING_LEVELS = 5; + private static final int POLICY_RULES_LEVELS = 5; + + @Autowired + private AssignmentProcessor assignmentProcessor; + + @Autowired + private Clock clock; + + private RoleType role1, role2, role4, role5, role6; + private OrgType org3; + private RoleType metarole1, metarole2, metarole3, metarole4; + private RoleType metametarole1; + private static final String R1_OID = getRoleOid("R1"); + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + role1 = createRole(1, 1); + role2 = createRole(1, 2); + org3 = createOrg(3); + role4 = createRole(1, 4); + role5 = createRole(1, 5); + role6 = createRole(1, 6); + metarole1 = createRole(2, 1); + metarole2 = createRole(2, 2); + metarole3 = createRole(2, 3); + metarole4 = createRole(2, 4); + metametarole1 = createRole(3, 1); + assign(role1, metarole1); + assign(role2, metarole2); + assign(metarole1, metametarole1); + induce(role1, role2, 1); + induce(metarole1, metarole3, 1); + induce(metarole1, role4, 2); + induce(metarole2, org3, 2); + induce(metarole3, role5, 2); + induce(metarole4, role6, 2); + induce(metametarole1, metarole4, 2); + + List roles = Arrays + .asList(role1, role2, org3, role4, role5, role6, metarole1, metarole2, metarole3, metarole4, metametarole1); + +// for (ObjectType role : roles) { +// System.out.println(prismContext.xmlSerializer().serialize(role.asPrismObject())); +// } + repoAddObjects(roles, initResult); + recomputeAndRefreshObjects(roles, initTask, initResult); + displayObjectTypeCollection("objects", roles); + + } + + private void induce(AbstractRoleType source, AbstractRoleType target, int inducementOrder) { + AssignmentType inducement = ObjectTypeUtil.createAssignmentTo(target.asPrismObject()); + if (inducementOrder > 1) { + inducement.setOrder(inducementOrder); + } + source.getInducement().add(inducement); + } + + private void assign(RoleType source, RoleType target) { + AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(target.asPrismObject()); + source.getAssignment().add(assignment); + } + + private RoleType createRole(int level, int number) { + return prepareAbstractRole(new RoleType(prismContext), level, number, "R"); + } + + private OrgType createOrg(int number) { + return prepareAbstractRole(new OrgType(prismContext), 1, number, "O"); + } + + private R prepareAbstractRole(R abstractRole, int level, int number, String nameSymbol) { + String name = StringUtils.repeat('M', level-1) + nameSymbol + number; + String oid = getRoleOid(name); + + abstractRole.setName(PolyStringType.fromOrig(name)); + abstractRole.setOid(oid); + + // constructions + for (int i = 0; i <= CONSTRUCTION_LEVELS; i++) { + ConstructionType c = new ConstructionType(prismContext); + c.setDescription(name + "-" + i); + c.setResourceRef(ObjectTypeUtil.createObjectRef(RESOURCE_DUMMY_OID, ObjectTypes.RESOURCE)); + AssignmentType a = new AssignmentType(prismContext); + a.setDescription("Assignment for " + c.getDescription()); + a.setConstruction(c); + addAssignmentOrInducement(abstractRole, i, a); + } + + // focus mappings + for (int i = 0; i <= FOCUS_MAPPING_LEVELS; i++) { + MappingType mapping = new MappingType(); + mapping.setName(name + "-" + i); + VariableBindingDefinitionType source = new VariableBindingDefinitionType(); + source.setPath(new ItemPath(UserType.F_NAME).asItemPathType()); + mapping.getSource().add(source); + VariableBindingDefinitionType target = new VariableBindingDefinitionType(); + target.setPath(new ItemPath(UserType.F_DESCRIPTION).asItemPathType()); + mapping.setTarget(target); + MappingsType mappings = new MappingsType(prismContext); + mappings.getMapping().add(mapping); + AssignmentType a = new AssignmentType(prismContext); + a.setFocusMappings(mappings); + addAssignmentOrInducement(abstractRole, i, a); + } + + // policy rules + for (int i = 0; i <= POLICY_RULES_LEVELS; i++) { + PolicyRuleType rule = new PolicyRuleType(prismContext); + rule.setName(name + "-" + i); + AssignmentType a = new AssignmentType(prismContext); + a.setPolicyRule(rule); + addAssignmentOrInducement(abstractRole, i, a); + } + + return abstractRole; + } + + private void addAssignmentOrInducement(R abstractRole, int order, AssignmentType assignment) { + if (order == 0) { + abstractRole.getAssignment().add(assignment); + } else { + assignment.setOrder(order); + abstractRole.getInducement().add(assignment); + } + } + + private static String getRoleOid(String name) { + return "99999999-0000-0000-0000-" + StringUtils.repeat('0', 12-name.length()) + name; + } + + @Test + public void test010AssignR1ToJack() throws Exception { + final String TEST_NAME = "test010AssignR1ToJack"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestAssignmentProcessor.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + LensContext context = createContextForRoleAssignment(USER_JACK_OID, R1_OID, null, null, result); + + // WHEN + assignmentProcessor.processAssignmentsProjections(context, clock.currentTimeXMLGregorianCalendar(), task, result); + + // THEN + display("Output context", context); + display("Evaluated assignment triple", context.getEvaluatedAssignmentTriple()); + + result.computeStatus(); + assertSuccess("Assignment processor failed (result)", result); + + Collection evaluatedAssignments = assertAssignmentTripleSetSize(context, 0, 1, 0); + EvaluatedAssignmentImpl evaluatedAssignment = evaluatedAssignments.iterator().next(); + assertEquals("Wrong evaluatedAssignment.isValid", true, evaluatedAssignment.isValid()); + + assertPrismRefValues("membershipRef", evaluatedAssignment.getMembershipRefVals(), role1, role2, org3, role4, role5, role6); + assertPrismRefValues("orgRef", evaluatedAssignment.getOrgRefVals(), org3); + assertPrismRefValues("delegationRef", evaluatedAssignment.getDelegationRefVals(), new String[0]); + + // Constructions are named "role-level". We expect e.g. that from R1 we get a construction induced with order=1 (R1-1). + List expectedItems = Arrays.asList("R1-1", "R2-1", "O3-1", "R4-1", "R5-1", "R6-1", "MR1-2", "MR2-2", "MR3-2", "MR4-2", "MMR1-3"); + assertConstructions(evaluatedAssignment, expectedItems,null, null, null, null, null); + assertFocusMappings(evaluatedAssignment, expectedItems); + assertFocusPolicyRules(evaluatedAssignment, expectedItems); + // TODO why R4-0 R5-0 R6-0 ? Sounds not good: when we are adding R1 assignment, we are not interested + // in approval rules residing in induced roles, even if they are induced through a higher levels + // (in the same way as we are not interested in R2-0 and R3-0) + // MR3-1 MR4-1 seems to be OK; these are induced in a quite intuitive way (via MR1) + String expectedThisTargetRules = "R1-0 R4-0 R5-0 R6-0 MR1-1 MR3-1 MR4-1 MMR1-2"; + String expectedTargetRules = expectedThisTargetRules + " R2-0 O3-0 MR2-1"; + assertTargetPolicyRules(evaluatedAssignment, getList(expectedTargetRules), getList(expectedThisTargetRules)); + + // assertTrue(context.getFocusContext().getPrimaryDelta().getChangeType() == ChangeType.MODIFY); +// assertNull("Unexpected user changes", context.getFocusContext().getSecondaryDelta()); +// assertFalse("No account changes", context.getProjectionContexts().isEmpty()); +// +// Collection accountContexts = context.getProjectionContexts(); +// assertEquals(1, accountContexts.size()); +// LensProjectionContext accContext = accountContexts.iterator().next(); +// assertNull(accContext.getPrimaryDelta()); +// +// ObjectDelta accountSecondaryDelta = accContext.getSecondaryDelta(); +// assertNull("Account secondary delta sneaked in", accountSecondaryDelta); +// +// assertNoDecision(accContext); +// assertLegal(accContext); + +// assignmentProcessor.processAssignmentsAccountValues(accContext, result); +// +// PrismValueDeltaSetTriple> accountConstructionDeltaSetTriple = +// accContext.getConstructionDeltaSetTriple(); +// display("accountConstructionDeltaSetTriple", accountConstructionDeltaSetTriple); +// +// PrismAsserts.assertTripleNoMinus(accountConstructionDeltaSetTriple); +// PrismAsserts.assertTripleNoPlus(accountConstructionDeltaSetTriple); +// assertSetSize("zero", accountConstructionDeltaSetTriple.getZeroSet(), 2); +// +// Construction zeroAccountConstruction = getZeroAccountConstruction(accountConstructionDeltaSetTriple, "Brethren account construction"); +// +// assertNoZeroAttributeValues(zeroAccountConstruction, +// getDummyResourceController().getAttributeQName(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME)); +// assertPlusAttributeValues(zeroAccountConstruction, +// getDummyResourceController().getAttributeQName(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME), "Tortuga"); +// assertMinusAttributeValues(zeroAccountConstruction, +// getDummyResourceController().getAttributeQName(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOCATION_NAME), "Caribbean"); + + } + + private List getList(String text) { + return Arrays.asList(StringUtils.split(text)); + } + + private void assertFocusMappings(EvaluatedAssignmentImpl evaluatedAssignment, Collection expectedItems) { + expectedItems = CollectionUtils.emptyIfNull(expectedItems); + assertEquals("Wrong # of focus mappings", expectedItems.size(), evaluatedAssignment.getFocusMappings().size()); + assertEquals("Wrong focus mappings", new HashSet<>(expectedItems), + evaluatedAssignment.getFocusMappings().stream().map(m -> m.getMappingType().getName()).collect(Collectors.toSet())); + // TODO look at the content of the mappings (e.g. zero, plus, minus sets) + } + + private void assertFocusPolicyRules(EvaluatedAssignmentImpl evaluatedAssignment, Collection expectedItems) { + expectedItems = CollectionUtils.emptyIfNull(expectedItems); + assertEquals("Wrong # of focus policy rules", expectedItems.size(), evaluatedAssignment.getFocusPolicyRules().size()); + assertEquals("Wrong focus policy rules", new HashSet<>(expectedItems), + evaluatedAssignment.getFocusPolicyRules().stream().map(r -> r.getName()).collect(Collectors.toSet())); + } + + private void assertTargetPolicyRules(EvaluatedAssignmentImpl evaluatedAssignment, Collection expectedTargetItems, Collection expectedThisTargetItems) { + expectedTargetItems = CollectionUtils.emptyIfNull(expectedTargetItems); + expectedThisTargetItems = CollectionUtils.emptyIfNull(expectedThisTargetItems); + assertEquals("Wrong # of target policy rules", expectedTargetItems.size(), evaluatedAssignment.getTargetPolicyRules().size()); + assertEquals("Wrong # of this target policy rules", expectedThisTargetItems.size(), evaluatedAssignment.getThisTargetPolicyRules().size()); + assertEquals("Wrong target policy rules", new HashSet<>(expectedTargetItems), + evaluatedAssignment.getTargetPolicyRules().stream().map(r -> r.getName()).collect(Collectors.toSet())); + assertEquals("Wrong this target policy rules", new HashSet<>(expectedThisTargetItems), + evaluatedAssignment.getThisTargetPolicyRules().stream().map(r -> r.getName()).collect(Collectors.toSet())); + + // testing (strange) condition on thisTarget vs target policy rules + outer: for (EvaluatedPolicyRule localRule : evaluatedAssignment.getThisTargetPolicyRules()) { + for (EvaluatedPolicyRule rule : evaluatedAssignment.getTargetPolicyRules()) { + if (rule == localRule) { + continue outer; + } + } + fail("This target rule " + localRule + " is not among target rules: " + evaluatedAssignment.getTargetPolicyRules()); + } + } + + private void assertConstructions(EvaluatedAssignmentImpl evaluatedAssignment, + List zeroValid, List zeroInvalid, + List plusValid, List plusInvalid, + List minusValid, List minusInvalid) { + assertConstructions("zero", evaluatedAssignment.getConstructionSet(PlusMinusZero.ZERO), zeroValid, zeroInvalid); + assertConstructions("plus", evaluatedAssignment.getConstructionSet(PlusMinusZero.PLUS), plusValid, plusInvalid); + assertConstructions("minus", evaluatedAssignment.getConstructionSet(PlusMinusZero.MINUS), minusValid, minusInvalid); + } + + private void assertConstructions(String type, Collection> constructions, List valid0, + List invalid0) { + constructions = CollectionUtils.emptyIfNull(constructions); + Collection expectedValid = CollectionUtils.emptyIfNull(valid0); + Collection expectedInvalid = CollectionUtils.emptyIfNull(invalid0); + Collection> realValid = constructions.stream().filter(c -> c.isValid()).collect(Collectors.toList()); + Collection> realInvalid = constructions.stream().filter(c -> !c.isValid()).collect(Collectors.toList()); + assertEquals("Wrong # of valid constructions in " + type + " set", expectedValid.size(), realValid.size()); + assertEquals("Wrong # of invalid constructions in " + type + " set", expectedInvalid.size(), realInvalid.size()); + assertEquals("Wrong valid constructions in " + type + " set", new HashSet<>(expectedValid), + realValid.stream().map(c -> c.getDescription()).collect(Collectors.toSet())); + assertEquals("Wrong invalid constructions in " + type + " set", new HashSet<>(expectedInvalid), + realInvalid.stream().map(c -> c.getDescription()).collect(Collectors.toSet())); + } + + private Collection assertAssignmentTripleSetSize(LensContext context, int zero, int plus, int minus) { + assertEquals("Wrong size of assignment triple zero set", zero, CollectionUtils.size(context.getEvaluatedAssignmentTriple().getZeroSet())); + assertEquals("Wrong size of assignment triple plus set", plus, CollectionUtils.size(context.getEvaluatedAssignmentTriple().getPlusSet())); + assertEquals("Wrong size of assignment triple minus set", minus, CollectionUtils.size(context.getEvaluatedAssignmentTriple().getMinusSet())); + return context.getEvaluatedAssignmentTriple().getAllValues(); + } + + @Test(enabled = false) + public void test020AssignR1ToJackProjectorDisabled() throws Exception { + final String TEST_NAME = "test020AssignR1ToJackProjectorDisabled"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(TestAssignmentProcessor.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + LensContext context = createContextForRoleAssignment(USER_JACK_OID, R1_OID, null, + a -> a.setActivation(ActivationUtil.createDisabled()), result); + + // WHEN + projector.project(context, "", task, result); + + // THEN + display("Output context", context); + + result.computeStatus(); + assertSuccess("Projector failed (result)", result); + + // MID-3679 + assertEquals("Wrong # of parentOrgRef entries", 0, + context.getFocusContext().getObjectNew().asObjectable().getParentOrgRef().size()); + assertEquals("Wrong # of roleMembershipRef entries", 0, + context.getFocusContext().getObjectNew().asObjectable().getRoleMembershipRef().size()); + } + + @NotNull + private LensContext createContextForRoleAssignment(String userOid, String roleOid, QName relation, + Consumer modificationBlock, OperationResult result) + throws SchemaException, ObjectNotFoundException, JAXBException { + LensContext context = createUserAccountContext(); + fillContextWithUser(context, userOid, result); + addFocusDeltaToContext(context, createAssignmentUserDelta(USER_JACK_OID, roleOid, RoleType.COMPLEX_TYPE, relation, + modificationBlock, true)); + context.recompute(); + display("Input context", context); + assertFocusModificationSanity(context); + return context; + } + +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/scripting/TestScriptingBasic.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/scripting/TestScriptingBasic.java index 2bd8aa1218a..308f0e961ce 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/scripting/TestScriptingBasic.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/scripting/TestScriptingBasic.java @@ -476,6 +476,7 @@ public void test370AssignToJackInBackground() throws Exception { assertAssignedRole(jack, "12345678-d34d-b33f-f00d-555555556677"); } + @Deprecated @Test public void test380DisableJackInBackgroundSimple() throws Exception { final String TEST_NAME = "test380DisableJackInBackgroundSimple"; diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index 4c28774c501..7a06bf0a73e 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -1579,6 +1579,40 @@ protected static void assertAssignedOrgs(PrismObject us MidPointAsserts.assertAssignedOrgs(user, orgOids); } + protected void assertObjectRefs(String contextDesc, Collection real, ObjectType... expected) { + assertObjectRefs(contextDesc, real, objectsToOids(expected)); + } + + protected void assertPrismRefValues(String contextDesc, Collection real, ObjectType... expected) { + assertPrismRefValues(contextDesc, real, objectsToOids(expected)); + } + + protected void assertObjectRefs(String contextDesc, Collection real, String... expected) { + List refOids = new ArrayList<>(); + for (ObjectReferenceType ref: real) { + refOids.add(ref.getOid()); + assertNotNull("Missing type in "+ref.getOid()+" in "+contextDesc, ref.getType()); + assertNotNull("Missing name in "+ref.getOid()+" in "+contextDesc, ref.getTargetName()); + } + PrismAsserts.assertSets("Wrong values in "+contextDesc, refOids, expected); + } + + protected void assertPrismRefValues(String contextDesc, Collection real, String... expected) { + List refOids = new ArrayList<>(); + for (PrismReferenceValue ref: real) { + refOids.add(ref.getOid()); + assertNotNull("Missing type in "+ref.getOid()+" in "+contextDesc, ref.getTargetType()); + assertNotNull("Missing name in "+ref.getOid()+" in "+contextDesc, ref.getTargetName()); + } + PrismAsserts.assertSets("Wrong values in "+contextDesc, refOids, expected); + } + + private String[] objectsToOids(ObjectType[] objects) { + return Arrays.stream(objects) + .map(o -> o.getOid()) + .toArray(String[]::new); + } + protected void assertRoleMembershipRef(PrismObject focus, String... roleOids) { List refOids = new ArrayList(); for (ObjectReferenceType ref: focus.asObjectable().getRoleMembershipRef()) { @@ -3952,4 +3986,20 @@ protected void resetTriggerTask(String taskOid, File taskFile, OperationResult r taskManager.resumeTasks(Collections.singleton(taskOid), result); } + protected void repoAddObjects(List objects, OperationResult result) + throws EncryptionException, ObjectAlreadyExistsException, SchemaException { + for (ObjectType object : objects) { + repoAddObject(object.asPrismObject(), result); + } + } + + protected void recomputeAndRefreshObjects(List objects, Task task, OperationResult result) + throws CommunicationException, ObjectNotFoundException, ObjectAlreadyExistsException, ConfigurationException, + SchemaException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + for (int i = 0; i < objects.size(); i++) { + ObjectType object = objects.get(i); + modelService.recompute(object.getClass(), object.getOid(), null, task, result); + objects.set(i, repositoryService.getObject(object.getClass(), object.getOid(), null, result).asObjectable()); + } + } } diff --git a/samples/tasks/bulk-actions/continue-on-any-error.xml b/samples/tasks/bulk-actions/continue-on-any-error.xml new file mode 100644 index 00000000000..f7bf97b27e0 --- /dev/null +++ b/samples/tasks/bulk-actions/continue-on-any-error.xml @@ -0,0 +1,49 @@ + + + + Execute execute script on objects 1 to 0 + + + + + RoleType + + + execute-script + + script + + + log.info('{}', input.name) + throw new IllegalStateException("custom exception for " + input.name) + + + + + + + true + + + + 1489138077377:388680045 + + runnable + BulkActions + http://midpoint.evolveum.com/xml/ns/public/model/scripting/handler-3 + single +