diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/QueryFactory.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/QueryFactory.java index 401acdd7a49..9feb687e52a 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/QueryFactory.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/QueryFactory.java @@ -115,12 +115,34 @@ LessFilter createLess(@NotNull ItemPath path, PrismPropertyDefinition @NotNull AndFilter createAnd(List conditions); + @NotNull + default ObjectFilter createAndOptimized(List conditions) { + if (conditions.isEmpty()) { + return createAll(); + } else if (conditions.size() == 1) { + return conditions.get(0); + } else { + return createAnd(conditions); + } + } + @NotNull OrFilter createOr(ObjectFilter... conditions); @NotNull OrFilter createOr(List conditions); + @NotNull + default ObjectFilter createOrOptimized(List conditions) { + if (conditions.isEmpty()) { + return createNone(); + } else if (conditions.size() == 1) { + return conditions.get(0); + } else { + return createOr(conditions); + } + } + @NotNull NotFilter createNot(ObjectFilter inner); @@ -156,7 +178,6 @@ LessFilter createLess(@NotNull ItemPath path, PrismPropertyDefinition OrgFilter createRootOrg(); @NotNull - @Deprecated // please use QueryBuilder instead TypeFilter createType(QName type, ObjectFilter filter); @NotNull diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/builder/S_AtomicFilterEntry.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/builder/S_AtomicFilterEntry.java index c9698a83bf2..ce7570b8fd2 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/builder/S_AtomicFilterEntry.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/query/builder/S_AtomicFilterEntry.java @@ -9,6 +9,7 @@ import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.OrgFilter; import javax.xml.namespace.QName; @@ -20,6 +21,7 @@ public interface S_AtomicFilterEntry { S_AtomicFilterExit all(); S_AtomicFilterExit none(); S_AtomicFilterExit undefined(); + S_AtomicFilterExit filter(ObjectFilter filter); // not much tested, use with care S_ConditionEntry item(QName... names); S_ConditionEntry item(String... names); S_ConditionEntry item(ItemPath path); diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistry.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistry.java index 7e998ca0e74..a6240d38261 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistry.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/schema/SchemaRegistry.java @@ -83,6 +83,16 @@ ItemDefinition locateItemDefinition(@NotNull QName itemName, */ QName determineTypeForClass(Class clazz); + @NotNull + default QName determineTypeForClassRequired(Class clazz) { + QName typeName = determineTypeForClass(clazz); + if (typeName != null) { + return typeName; + } else { + throw new IllegalStateException("No type for " + clazz); + } + } + /** * This method will try to locate the appropriate object definition and apply it. * @param container @@ -124,6 +134,15 @@ T findItemDefinitionByFullPath(Class Class determineClassForType(QName type); + default Class determineClassForTypeRequired(QName type) { + Class clazz = determineClassForType(type); + if (clazz != null) { + return clazz; + } else { + throw new IllegalStateException("No class for " + type); + } + } + // Takes XSD types into account as well Class determineClassForItemDefinition(ItemDefinition itemDefinition); diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/builder/R_Filter.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/builder/R_Filter.java index bea4f6520fb..079b1fb8d33 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/builder/R_Filter.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/query/builder/R_Filter.java @@ -186,6 +186,12 @@ public S_AtomicFilterExit none() { public S_AtomicFilterExit undefined() { return addSubfilter(UndefinedFilterImpl.createUndefined()); } + + @Override + public S_AtomicFilterExit filter(ObjectFilter filter) { + return addSubfilter(filter); + } + // TODO ............................................. @Override diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ExecuteScriptUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ExecuteScriptUtil.java new file mode 100644 index 00000000000..f3d23786c20 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ExecuteScriptUtil.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.schema.util; + +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ValueListType; + +import java.util.Collection; + +public class ExecuteScriptUtil { + + /** + * "Implants" specified input into ExecuteScriptType, not changing the original. + */ + public static ExecuteScriptType implantInput(ExecuteScriptType originalBean, ValueListType input) { + return originalBean.clone().input(input); + } + + public static ValueListType createInput(Collection values) { + ValueListType input = new ValueListType(); + values.forEach(o -> input.getValue().add(o)); + return input; + } + + public static ValueListType createInputCloned(Collection values) { + ValueListType input = new ValueListType(); + values.forEach(o -> input.getValue().add(o.clone())); + return input; + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java index 52b5e4deaf7..a62e492cf84 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java @@ -289,20 +289,20 @@ public static ObjectReferenceType createObjectRef(ObjectType objectType, QName r return createObjectRef(objectType.asPrismObject(), relation); } - public static ObjectReferenceType createObjectRef(PrismObject object, PrismContext prismContext) { + public static ObjectReferenceType createObjectRef(PrismObject object, PrismContext prismContext) { if (object == null) { return null; } return createObjectRef(object, prismContext.getDefaultRelation()); } - public static ObjectReferenceType createObjectRef(PrismObject object, QName relation) { + public static ObjectReferenceType createObjectRef(PrismObject object, QName relation) { if (object == null) { return null; } ObjectReferenceType ref = new ObjectReferenceType(); ref.setOid(object.getOid()); - PrismObjectDefinition definition = object.getDefinition(); + PrismObjectDefinition definition = object.getDefinition(); if (definition != null) { ref.setType(definition.getTypeName()); } 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 2c86d5aba1c..7daa2e65c0a 100755 --- 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 @@ -17151,6 +17151,7 @@ This collection is merge with filter. + true 4.2 CollectionSpecificationType.collectionRef diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd index c847c6080c5..a8252c390a4 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd @@ -2067,7 +2067,7 @@ - + Options for asynchronous script execution. 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 7240ed0f14f..f6df097f055 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 @@ -940,7 +940,7 @@ - + Does not invoke recompute immediately but uses a trigger to do that. diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java index 3b65a168d35..05340c33bba 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/MiscUtil.java @@ -41,6 +41,8 @@ import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.exception.TunnelException; +import org.springframework.util.ClassUtils; + import static java.util.Collections.*; /** @@ -798,4 +800,17 @@ public static T cast(Object value, Class expectedClass) throws SchemaExce return (T) value; } } + + public static Class determineCommonAncestor(Collection> classes) { + if (!classes.isEmpty()) { + Iterator> iterator = classes.iterator(); + Class currentCommonAncestor = iterator.next(); + while (iterator.hasNext()) { + currentCommonAncestor = ClassUtils.determineCommonAncestor(currentCommonAncestor, iterator.next()); + } + return currentCommonAncestor; + } else { + return null; + } + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java index 53cd00bb98a..7c428f817de 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/CollectionProcessor.java @@ -217,12 +217,7 @@ private Integer countObjects(Class targetTypeClass, Ob if (filter == null) { return null; } - Collection> options = null; - if (ShadowType.class.isAssignableFrom(targetTypeClass)){ - options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); - } - - return modelService.countObjects(targetTypeClass, prismContext.queryFactory().createQuery(filter), options, task, result); + return modelService.countObjects(targetTypeClass, prismContext.queryFactory().createQuery(filter), null, task, result); } public CompiledObjectCollectionView compileObjectCollectionView(PrismObject collection, diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java index 8749aad5a33..f602b5debf3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java @@ -276,6 +276,10 @@ public LensFocusContext getFocusContext() { return focusContext; } + public boolean hasFocusContext() { + return focusContext != null; + } + @Override @NotNull public LensFocusContext getFocusContextRequired() { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AbstractSingleRunTaskCreator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AbstractSingleRunTaskCreator.java new file mode 100644 index 00000000000..de0239f9751 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AbstractSingleRunTaskCreator.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.model.api.ModelPublicConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; + +/** + * Creates "single run" scripting task. + */ +abstract class AbstractSingleRunTaskCreator extends ScriptingTaskCreator { + + AbstractSingleRunTaskCreator(@NotNull ActionContext actx) { + super(actx); + } + + @NotNull + TaskType createTaskForSingleRunScript(ExecuteScriptType executeScript, OperationResult result) + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + TaskType newTask = createArchetypedTask(result); + setScriptInTask(newTask, executeScript); + return newTask; + } + + @NotNull + private TaskType createArchetypedTask(OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, + ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + + TaskType newTask = createEmptyTask(result); + newTask.setHandlerUri(ModelPublicConstants.SCRIPT_EXECUTION_TASK_HANDLER_URI); + + // TODO task customizer + // TODO task archetype + + return newTask; + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ActionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ActionContext.java new file mode 100644 index 00000000000..f4b231dd304 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ActionContext.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import com.evolveum.midpoint.model.impl.lens.EvaluatedPolicyRuleImpl; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExecutionPolicyActionType; + +import org.jetbrains.annotations.NotNull; + +/** + * Context of execution of specific "script execution" policy action. + */ +class ActionContext { + + @NotNull final ScriptExecutionPolicyActionType action; + @NotNull final EvaluatedPolicyRuleImpl rule; + @NotNull final LensContext context; + @NotNull final LensFocusContext focusContext; + @NotNull final Task task; + @NotNull final PolicyRuleScriptExecutor beans; + + ActionContext(@NotNull ScriptExecutionPolicyActionType action, @NotNull EvaluatedPolicyRuleImpl rule, + @NotNull LensContext context, @NotNull Task task, @NotNull PolicyRuleScriptExecutor beans) { + this.action = action; + this.rule = rule; + this.context = context; + this.focusContext = context.getFocusContextRequired(); + this.task = task; + this.beans = beans; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AsynchronousScriptExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AsynchronousScriptExecutor.java index 324cdc3d606..a592a6a6a50 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AsynchronousScriptExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AsynchronousScriptExecutor.java @@ -8,16 +8,11 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.impl.lens.EvaluatedPolicyRuleImpl; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.delta.DeltaFactory; import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.ObjectDeltaCollectionsUtil; import com.evolveum.midpoint.schema.ObjectDeltaOperation; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; +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; @@ -25,8 +20,7 @@ import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; +import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Set; @@ -37,58 +31,64 @@ /** * Executes scripts asynchronously. */ -@Component -public class AsynchronousScriptExecutor { +class AsynchronousScriptExecutor { private static final Trace LOGGER = TraceManager.getTrace(AsynchronousScriptExecutor.class); private static final String OP_SUBMIT_SCRIPT = AsynchronousScriptExecutor.class.getName() + ".submitScript"; - @Autowired private PolicyRuleScriptExecutor policyRuleScriptExecutor; - @Autowired private PrismContext prismContext; - @Autowired private ModelService modelService; + @NotNull private final ActionContext actx; + @NotNull private final ScriptingTaskCreator taskCreator; - public void execute(ScriptExecutionPolicyActionType action, EvaluatedPolicyRuleImpl rule, LensContext context, - Task task, OperationResult parentResult) { - AsynchronousScriptExecutionType asynchronousExecution = action.getAsynchronous(); - assert asynchronousExecution != null; + AsynchronousScriptExecutor(@NotNull ActionContext actx) { + this.actx = actx; + AsynchronousScriptExecutionType asynchronousExecution = actx.action.getAsynchronousExecution(); + AsynchronousScriptExecutionModeType mode = defaultIfNull(asynchronousExecution.getExecutionMode(), AsynchronousScriptExecutionModeType.ITERATIVE); + this.taskCreator = createTaskCreator(actx, mode); + } - if (action.getExecuteScript().size() != 1) { - throw new IllegalArgumentException("Expected exactly one 'executeScript' element in policy action, got " - + action.getExecuteScript().size() + " in " + action); + private ScriptingTaskCreator createTaskCreator(ActionContext actx, AsynchronousScriptExecutionModeType mode) { + switch(mode) { + case ITERATIVE: + return new IterativeScriptingTaskCreator(actx); + case SINGLE_RUN: + return new SingleRunTaskCreator(actx); + case SINGLE_RUN_NO_INPUT: + return new SingleRunNoInputTaskCreator(actx); + default: + throw new AssertionError(mode); } + } - OperationResult result = parentResult.createSubresult(OP_SUBMIT_SCRIPT); // cannot be minor because of background task OID - try { - ExecuteScriptType executeScript = action.getExecuteScript().get(0); - TaskType newTask; - switch (defaultIfNull(asynchronousExecution.getExecutionMode(), AsynchronousScriptExecutionModeType.ITERATIVE)) { - case ITERATIVE: - newTask = new IterativeScriptingTaskCreator(policyRuleScriptExecutor, context) - .create(executeScript, action, task, result); - break; -// case SINGLE_RUN: -// newTask = new SingleRunScriptingTaskCreator().create(); -// break; -// case SINGLE_RUN_NO_INPUT: -// newTask = new SingleRunScriptingTaskCreator().create(); -// break; - default: - throw new AssertionError(asynchronousExecution.getExecutionMode()); - } - - Set> deltas = singleton(DeltaFactory.Object.createAddDelta(newTask.asPrismObject())); - ModelExecuteOptions options = new ModelExecuteOptions(prismContext).preAuthorized(); - Collection> operations = modelService.executeChanges(deltas, options, task, result); - String oid = ObjectDeltaOperation.findAddDeltaOid(operations, newTask.asPrismObject()); - System.out.println("New task OID = " + oid); - result.setAsynchronousOperationReference(oid); + void submitScripts(OperationResult parentResult) { + for (ExecuteScriptType executeScript : actx.action.getExecuteScript()) { + submitScript(executeScript, parentResult); + } + } + private void submitScript(ExecuteScriptType executeScript, OperationResult parentResult) { + // The subresult cannot be minor because we need to preserve background task OID + OperationResult result = parentResult.createSubresult(OP_SUBMIT_SCRIPT); + try { + TaskType newTask = taskCreator.createTask(executeScript, result); + submitTask(result, newTask); } catch (Throwable t) { result.recordFatalError(t); - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't submit script for asynchronous execution: {}", t, action); + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't submit script for asynchronous execution: {}", t, actx.action); + // Should not re-throw the exception in order to submit other scripts } finally { result.computeStatusIfUnknown(); } } + + private void submitTask(OperationResult result, TaskType newTask) + throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, PolicyViolationException, SecurityViolationException { + Set> deltas = singleton(DeltaFactory.Object.createAddDelta(newTask.asPrismObject())); + ModelExecuteOptions options = new ModelExecuteOptions(actx.beans.prismContext).preAuthorized(); + Collection> operations = actx.beans.modelService.executeChanges(deltas, options, actx.task, result); + String oid = ObjectDeltaOperation.findAddDeltaOid(operations, newTask.asPrismObject()); + System.out.println("New task OID = " + oid); + result.setAsynchronousOperationReference(oid); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/FullDataBasedObjectSet.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/FullDataBasedObjectSet.java new file mode 100644 index 00000000000..e2514d3b020 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/FullDataBasedObjectSet.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import java.util.Collection; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; + +/** + * Object set represented as full prism objects. + */ +class FullDataBasedObjectSet extends ObjectSet> { + + FullDataBasedObjectSet(ActionContext actx, OperationResult result) { + super(actx, result); + } + + @Override + void collectLinkSources() throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, + SecurityViolationException, ExpressionEvaluationException { + assert objectSpec != null; + try (LinkSourceFinder sourceFinder = new LinkSourceFinder(actx, result)) { + addObjects(sourceFinder.getSourcesAsObjects(objectSpec.getLinkSource())); + } + } + + @Override + PrismObjectValue toIndividualObject(PrismObject object) { + return object.getValue(); + } + + Collection> asObjectValues() { + return individualObjects.values(); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/IterativeScriptingTaskCreator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/IterativeScriptingTaskCreator.java index 9b9389994ef..b3e798d640e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/IterativeScriptingTaskCreator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/IterativeScriptingTaskCreator.java @@ -7,142 +7,98 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; -import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType.RUNNABLE; - -import java.util.ArrayList; -import java.util.List; import javax.xml.namespace.QName; import org.jetbrains.annotations.NotNull; import com.evolveum.midpoint.model.api.ModelPublicConstants; -import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.schema.SchemaRegistry; +import com.evolveum.midpoint.repo.api.query.CompleteQuery; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.security.api.MidPointPrincipal; -import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.prism.xml.ns._public.query_3.QueryType; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** * Creates an iterative scripting task. */ class IterativeScriptingTaskCreator extends ScriptingTaskCreator { - @NotNull private final PolicyRuleScriptExecutor beans; - @NotNull private final LensContext context; + @NotNull private final SchemaRegistry schemaRegistry; - IterativeScriptingTaskCreator(@NotNull PolicyRuleScriptExecutor policyRuleScriptExecutor, @NotNull LensContext context) { - beans = policyRuleScriptExecutor; - this.context = context; + IterativeScriptingTaskCreator(@NotNull ActionContext actx) { + super(actx); + this.schemaRegistry = beans.prismContext.getSchemaRegistry(); } - public TaskType create(ExecuteScriptType executeScript, ScriptExecutionPolicyActionType action, Task task, - OperationResult result) throws SecurityViolationException, ObjectNotFoundException, SchemaException, - CommunicationException, ConfigurationException, ExpressionEvaluationException { + @Override + public TaskType createTask(ExecuteScriptType executeScript, OperationResult result) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { if (executeScript.getInput() != null) { throw new UnsupportedOperationException("Explicit input with iterative task execution is not supported yet."); } - MidPointPrincipal principal = beans.securityContextManager.getPrincipal(); - if (principal == null) { - throw new SecurityViolationException("No current user"); - } - AsynchronousScriptExecutionType asynchronousExecution = action.getAsynchronous(); - TaskType newTask; - if (asynchronousExecution.getTaskTemplateRef() != null) { - newTask = beans.modelObjectResolver.resolve(asynchronousExecution.getTaskTemplateRef(), TaskType.class, - null, "task template", task, result); - } else { - newTask = new TaskType(beans.prismContext); - newTask.setName(PolyStringType.fromOrig("Execute script")); - newTask.setRecurrence(TaskRecurrenceType.SINGLE); - } - newTask.setName(PolyStringType.fromOrig(newTask.getName().getOrig() + " " + (int) (Math.random() * 10000))); - newTask.setOid(null); - newTask.setTaskIdentifier(null); - newTask.setOwnerRef(createObjectRef(principal.getFocus(), beans.prismContext)); - newTask.setExecutionStatus(RUNNABLE); - newTask.setHandlerUri(ModelPublicConstants.ITERATIVE_SCRIPT_EXECUTION_TASK_HANDLER_URI); + TaskType newTask = createArchetypedTask(result); + CompleteQuery completeQuery = createCompleteQuery(result); - //noinspection unchecked - PrismPropertyDefinition objectTypeDef = beans.prismContext.getSchemaRegistry() - .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_OBJECT_TYPE); - PrismProperty objectTypeProp = objectTypeDef.instantiate(); - objectTypeProp.setRealValue(AssignmentHolderType.COMPLEX_TYPE); - newTask.asPrismObject().addExtensionItem(objectTypeProp); + setObjectTypeInTask(newTask, completeQuery); + setQueryInTask(newTask, completeQuery); + setScriptInTask(newTask, executeScript); - //noinspection unchecked - PrismPropertyDefinition queryDef = beans.prismContext.getSchemaRegistry() - .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_OBJECT_QUERY); - PrismProperty queryProp = queryDef.instantiate(); - queryProp.setRealValue(createQuery(action)); - newTask.asPrismObject().addExtensionItem(queryProp); + return newTask; + } - //noinspection unchecked - PrismPropertyDefinition executeScriptDef = beans.prismContext.getSchemaRegistry() - .findPropertyDefinitionByElementName(SchemaConstants.SE_EXECUTE_SCRIPT); - PrismProperty executeScriptProp = executeScriptDef.instantiate(); - executeScriptProp.setRealValue(executeScript.clone()); - newTask.asPrismObject().addExtensionItem(executeScriptProp); + @NotNull + private TaskType createArchetypedTask(OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, + ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + + TaskType newTask = super.createEmptyTask(result); + newTask.setHandlerUri(ModelPublicConstants.ITERATIVE_SCRIPT_EXECUTION_TASK_HANDLER_URI); + + // TODO task customizer + // TODO task archetype return newTask; } - private QueryType createQuery(ScriptExecutionPolicyActionType action) throws SchemaException { - ObjectFilter filter = createFilter(action); - SearchFilterType filterBean = beans.prismContext.getQueryConverter().createSearchFilterType(filter); - return new QueryType() - .filter(filterBean); - } + @NotNull + private CompleteQuery createCompleteQuery(OperationResult result) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { - private ObjectFilter createFilter(ScriptExecutionPolicyActionType action) { - ScriptExecutionObjectType objectSpec = action.getObject(); - if (objectSpec == null) { - return createCurrentFilter(); - } else { - List filters = new ArrayList<>(); - if (objectSpec.getCurrentObject() != null) { - filters.add(createCurrentFilter()); // TODO selector - } - for (LinkSourceObjectSelectorType linkSource : objectSpec.getLinkSource()) { - filters.add(createLinkSourceFilter(linkSource)); - } - if (!objectSpec.getLinkTarget().isEmpty()) { - throw new UnsupportedOperationException("Link targets are not supported yet"); - } - if (filters.isEmpty()) { - return createCurrentFilter(); - } else if (filters.size() == 1) { - return filters.get(0); - } else { - return beans.prismContext.queryFactory().createOr(filters); - } - } + QueryBasedObjectSet objectSet = new QueryBasedObjectSet(actx, result); + objectSet.collect(); + return objectSet.asQuery(); } - // TODO selector - private ObjectFilter createLinkSourceFilter(LinkSourceObjectSelectorType linkSource) { - return beans.prismContext.queryFor(AssignmentHolderType.class) - .item(AssignmentHolderType.F_ROLE_MEMBERSHIP_REF).ref(getFocusOid()) - .buildFilter(); + private void setQueryInTask(TaskType taskBean, CompleteQuery union) throws SchemaException { + QueryType queryBean = beans.prismContext.getQueryConverter().createQueryType(union.getQuery()); + //noinspection unchecked + PrismPropertyDefinition queryDef = beans.prismContext.getSchemaRegistry() + .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_OBJECT_QUERY); + PrismProperty queryProp = queryDef.instantiate(); + queryProp.setRealValue(queryBean); + taskBean.asPrismObject().addExtensionItem(queryProp); } - private ObjectFilter createCurrentFilter() { - return beans.prismContext.queryFor(AssignmentHolderType.class) - .id(getFocusOid()) - .buildFilter(); - } + private void setObjectTypeInTask(TaskType taskBean, CompleteQuery union) + throws SchemaException { + QName typeName = schemaRegistry.determineTypeForClass(union.getType()); + if (typeName == null || !schemaRegistry.isAssignableFrom(ObjectType.class, typeName)) { + throw new IllegalStateException("Type for class " + union.getType() + " is unknown or not an ObjectType: " + typeName); + } - private String getFocusOid() { - return context.getFocusContextRequired().getOid(); + //noinspection unchecked + PrismPropertyDefinition objectTypeDef = beans.prismContext.getSchemaRegistry() + .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_OBJECT_TYPE); + PrismProperty objectTypeProp = objectTypeDef.instantiate(); + objectTypeProp.setRealValue(typeName); + taskBean.asPrismObject().addExtensionItem(objectTypeProp); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkSourceFinder.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkSourceFinder.java index ad515c7cd35..528061b17a4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkSourceFinder.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkSourceFinder.java @@ -8,34 +8,36 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashSet; import java.util.List; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.LinkSourceObjectSelectorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import java.util.Set; +import javax.xml.namespace.QName; import org.jetbrains.annotations.NotNull; -import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.QueryFactory; +import com.evolveum.midpoint.repo.api.query.CompleteQuery; +import com.evolveum.midpoint.repo.common.query.SelectorToFilterTranslator; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectQueryUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; - -import javax.xml.namespace.QName; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LinkSourceObjectSelectorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; /** * Finds link sources based on a collection of selectors. * - * Preliminary implementation. It is not optimizing link source search. - * Instead of creating a sophisticated query filter based on object constraints it simply - * finds all the sources and applies the constraints afterwards. - * So: TODO optimize this class - * - * TODO think if we should use model or repository when looking for objects + * TODO Think if we should use model or repository when looking for objects. Currently we use the repo. */ class LinkSourceFinder implements AutoCloseable { @@ -43,71 +45,119 @@ class LinkSourceFinder implements AutoCloseable { private static final String OP_GET_SOURCES = LinkSourceFinder.class.getName() + ".getSources"; - private final PolicyRuleScriptExecutor beans; - private final LensContext context; + @NotNull private final ActionContext actx; + @NotNull private final PolicyRuleScriptExecutor beans; + private final QueryFactory queryFactory; + private final String focusOid; private final OperationResult result; + private final Set> narrowedSourceTypes = new HashSet<>(); - LinkSourceFinder(PolicyRuleScriptExecutor policyRuleScriptExecutor, LensContext context, - OperationResult parentResult) { - this.beans = policyRuleScriptExecutor; - this.context = context; + LinkSourceFinder(ActionContext actx, OperationResult parentResult) { + this.actx = actx; + this.beans = actx.beans; + this.queryFactory = beans.prismContext.queryFactory(); + this.focusOid = actx.focusContext.getOid(); this.result = parentResult.createMinorSubresult(OP_GET_SOURCES); } - List> getSources(List sourceSelectors) throws SchemaException, + List> getSourcesAsObjects(List sourceSelectors) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException { try { - List> allSources = getAllSources(context.getFocusContextRequired().getOid()); - // noinspection unchecked - return (List) filterObjects(allSources, sourceSelectors); + return searchForSources(getSourcesAsQuery(sourceSelectors)); } catch (Throwable t) { result.recordFatalError(t); throw t; } } - @NotNull - private List> getAllSources(String focusOid) throws SchemaException { - if (focusOid == null) { - LOGGER.warn("No focus object OID, no assignees can be found"); - return Collections.emptyList(); - } else { - ObjectQuery query = beans.prismContext.queryFor(AssignmentHolderType.class) - .item(AssignmentHolderType.F_ROLE_MEMBERSHIP_REF).ref(focusOid) - .build(); - //noinspection unchecked - return (List) beans.repositoryService.searchObjects(AssignmentHolderType.class, query, null, result); + List getSourcesAsReferences(List sourceSelectors) throws SchemaException, + ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, + ExpressionEvaluationException { + try { + return searchForSourceReferences(getSourcesAsQuery(sourceSelectors)); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; } } - private List> filterObjects(List> objects, List selectors) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException { - List> all = new ArrayList<>(); - for (LinkSourceObjectSelectorType selector : selectors) { - all.addAll(filterObjects(objects, selector)); + @NotNull + CompleteQuery getSourcesAsQuery(List sourceSelectors) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, + ExpressionEvaluationException { + ObjectQuery query = createQuery(sourceSelectors); + Class objectType = getSourceType(); + return new CompleteQuery<>(objectType, query, null); + } + + private ObjectQuery createQuery(List sourceSelectors) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, + ExpressionEvaluationException { + List convertedSelectors = new ArrayList<>(sourceSelectors.size()); + for (LinkSourceObjectSelectorType sourceSelector : sourceSelectors) { + convertedSelectors.add(createFilter(sourceSelector)); } - return all; + return queryFactory.createQuery( + queryFactory.createOrOptimized(convertedSelectors)); } - private List> filterObjects(List> objects, LinkSourceObjectSelectorType selector) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException { - List> matching = new ArrayList<>(); - for (PrismObject object : objects) { - if (beans.repositoryService.selectorMatches(selector, object, - null, LOGGER, "script object evaluation") && - relationMatches(object, selector.getRelation())) { - matching.add(object); + private ObjectFilter createFilter(LinkSourceObjectSelectorType sourceSelector) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, + ExpressionEvaluationException { + SelectorToFilterTranslator translator = new SelectorToFilterTranslator(sourceSelector, AssignmentHolderType.class, + "link source selector in rule script executor", beans.prismContext, beans.expressionFactory, + actx.task, result); + ObjectFilter selectorFilter = translator.createFilter(); + Class narrowedSourceType = translator.getNarrowedTargetType(); + ObjectFilter allSourcesFilter = beans.prismContext.queryFor(narrowedSourceType) + .item(AssignmentHolderType.F_ROLE_MEMBERSHIP_REF).ref(createExpectedReferenceValues(sourceSelector)) + .buildFilter(); + + narrowedSourceTypes.add(narrowedSourceType); + return ObjectQueryUtil.simplify(queryFactory.createAnd(allSourcesFilter, selectorFilter), beans.prismContext); + } + + @NotNull + private List createExpectedReferenceValues(LinkSourceObjectSelectorType sourceSelector) { + List values = new ArrayList<>(sourceSelector.getRelation().size()); + if (sourceSelector.getRelation().isEmpty()) { + values.add(new ObjectReferenceType().oid(focusOid).asReferenceValue()); + } else { + for (QName relation : sourceSelector.getRelation()) { + values.add(new ObjectReferenceType().oid(focusOid).relation(relation).asReferenceValue()); } } - return matching; + return values; } - private boolean relationMatches(PrismObject linkSource, List relations) { - return relations.isEmpty() || linkSource.asObjectable().getRoleInfluenceRef().stream() - .anyMatch(ref -> beans.prismContext.relationMatches(relations, ref.getRelation())); + @NotNull + private List> searchForSources(CompleteQuery completeQuery) throws SchemaException { + LOGGER.trace("Looking for link sources using computed query:\n{}", completeQuery.debugDumpLazily()); + //noinspection unchecked + return (List) beans.repositoryService.searchObjects(completeQuery.getType(), + completeQuery.getQuery(), completeQuery.getOptions(), result); + } + + @NotNull + private List searchForSourceReferences(CompleteQuery completeQuery) throws SchemaException { + LOGGER.trace("Looking for link sources (as references) using computed query:\n{}", completeQuery.debugDumpLazily()); + List references = new ArrayList<>(); + beans.repositoryService.searchObjectsIterative(completeQuery.getType(), completeQuery.getQuery(), + (object, parentResult) -> + references.add(ObjectTypeUtil.createObjectRef(object, beans.prismContext).asReferenceValue()), + completeQuery.getOptions(), false, result); + return references; + } + + private Class getSourceType() { + Class ancestor = MiscUtil.determineCommonAncestor(narrowedSourceTypes); + if (ancestor == null || !AssignmentHolderType.class.isAssignableFrom(ancestor)) { + return AssignmentHolderType.class; + } else { + //noinspection unchecked + return (Class) ancestor; + } } @Override diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkTargetFinder.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkTargetFinder.java index f6e87728e4e..526204c2a8f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkTargetFinder.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/LinkTargetFinder.java @@ -18,20 +18,18 @@ import java.util.stream.Collectors; import javax.xml.namespace.QName; -import com.evolveum.midpoint.util.exception.*; - -import com.evolveum.midpoint.util.logging.LoggingUtils; - import org.apache.commons.collections4.SetUtils; import org.jetbrains.annotations.NotNull; -import com.evolveum.midpoint.model.impl.lens.EvaluatedPolicyRuleImpl; -import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentTargetImpl; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.CommonException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +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.*; @@ -45,17 +43,14 @@ class LinkTargetFinder implements AutoCloseable { private static final String OP_GET_TARGETS = LinkTargetFinder.class.getName() + ".getTargets"; + @NotNull private final ActionContext actx; @NotNull private final PolicyRuleScriptExecutor beans; - @NotNull private final LensContext context; - @NotNull private final EvaluatedPolicyRuleImpl rule; @NotNull private final OperationResult result; - LinkTargetFinder(@NotNull PolicyRuleScriptExecutor policyRuleScriptExecutor, - @NotNull LensContext context, @NotNull EvaluatedPolicyRuleImpl rule, + LinkTargetFinder(@NotNull ActionContext actx, @NotNull OperationResult parentResult) { - this.beans = policyRuleScriptExecutor; - this.context = context; - this.rule = rule; + this.actx = actx; + this.beans = actx.beans; this.result = parentResult.createMinorSubresult(OP_GET_TARGETS); } @@ -69,7 +64,7 @@ List> getTargets(LinkTargetObjectSelectorType } private List> getTargetsInternal(LinkTargetObjectSelectorType selector) { - LOGGER.trace("Selecting matching link targets for {} with rule={}", selector, rule); + LOGGER.trace("Selecting matching link targets for {} with rule={}", selector, actx.rule); // We must create new set because we will remove links from it later. Set links = new HashSet<>(getLinksInChangeSituation(selector)); @@ -113,7 +108,7 @@ private List> getMatchingObjects(Collection> resolveLinks(Set getClassForType(QName type) { } private void applyMatchingRuleAssignment(Set links) { - EvaluatedAssignmentImpl evaluatedAssignment = rule.getEvaluatedAssignment(); + EvaluatedAssignmentImpl evaluatedAssignment = actx.rule.getEvaluatedAssignment(); if (evaluatedAssignment == null) { throw new IllegalStateException("'matchesRuleAssignment' filter is selected but there's no evaluated assignment" - + " known for policy rule {}" + rule); + + " known for policy rule {}" + actx.rule); } Set oids = evaluatedAssignment.getRoles().stream() .filter(EvaluatedAssignmentTargetImpl::appliesToFocus) @@ -168,7 +163,7 @@ private void applyMatchingRuleAssignment(Set links) { } private void applyMatchingPolicyRuleConstraints(Set links) { - Set oids = rule.getAllTriggers().stream() + Set oids = actx.rule.getAllTriggers().stream() .flatMap(trigger -> trigger.getTargetObjects().stream()) .map(PrismObject::getOid) .collect(Collectors.toSet()); @@ -202,14 +197,14 @@ private Set getLinksInChangeSituation(LinkTargetObjectSelec } private Set getLinkedOld() { - ObjectType objectOld = asObjectable(context.getFocusContextRequired().getObjectOld()); + ObjectType objectOld = asObjectable(actx.focusContext.getObjectOld()); return objectOld instanceof AssignmentHolderType ? new HashSet<>(objectReferenceListToPrismReferenceValues(((AssignmentHolderType) objectOld).getRoleMembershipRef())) : emptySet(); } private Set getLinkedNew() { - ObjectType objectNew = asObjectable(context.getFocusContextRequired().getObjectNew()); + ObjectType objectNew = asObjectable(actx.focusContext.getObjectNew()); return objectNew instanceof AssignmentHolderType ? new HashSet<>(objectReferenceListToPrismReferenceValues(((AssignmentHolderType) objectNew).getRoleMembershipRef())) : emptySet(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ObjectSet.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ObjectSet.java new file mode 100644 index 00000000000..dc62ab3b282 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ObjectSet.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LinkTargetObjectSelectorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSelectorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExecutionObjectType; + +/** + * a set of objects that are to be processed. + * The set can be in the form of: + * - full prism objects (for synchronous task execution) + * - prism references (for asynchronous 'single run with generated input' task execution) + * - object query (for asynchronous 'iterative scripting' task execution) + * + * @param Representation of individual objects: PrismObjectValue or PrismReferenceValue. + */ +abstract class ObjectSet { + + private static final Trace LOGGER = TraceManager.getTrace(ObjectSet.class); + + @NotNull final ActionContext actx; + @NotNull final PolicyRuleScriptExecutor beans; + @Nullable final ScriptExecutionObjectType objectSpec; + final OperationResult result; + private boolean collected; + + /** + * Individual objects that we know by OID. Besides these there could be other ones (from link sources). + * We use OID-keyed map to avoid duplicate values. + */ + final Map individualObjects = new HashMap<>(); + + ObjectSet(ActionContext actx, OperationResult result) { + this.actx = actx; + this.beans = actx.beans; + this.objectSpec = actx.action.getObject(); + this.result = result; + } + + void collect() throws CommunicationException, ObjectNotFoundException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + checkNotCollected(); + + if (objectSpec == null) { + PrismObject focus = actx.focusContext.getObjectAny(); + if (focus != null) { + addObject(focus); + } + } else { + if (objectSpec.getCurrentObject() != null) { + PrismObject focus = actx.focusContext.getObjectAny(); + if (currentObjectMatches(focus, objectSpec.getCurrentObject())) { + addObject(focus); + } + } + if (!objectSpec.getLinkTarget().isEmpty()) { + try (LinkTargetFinder targetFinder = new LinkTargetFinder(actx, result)) { + for (LinkTargetObjectSelectorType linkTargetSelector : objectSpec.getLinkTarget()) { + addObjects(targetFinder.getTargets(linkTargetSelector)); + } + } + } + if (!objectSpec.getLinkSource().isEmpty()) { + collectLinkSources(); + } + } + } + + private void checkNotCollected() { + if (collected) { + throw new IllegalStateException("Already collected"); + } else { + collected = true; + } + } + + void checkCollected() { + if (!collected) { + throw new IllegalStateException("Not collected"); + } + } + + /** + * Link sources have to be collected in implementation-specific way: as a query, + * as object references or as full objects. + */ + abstract void collectLinkSources() + throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, + SecurityViolationException, ExpressionEvaluationException; + + private boolean currentObjectMatches(PrismObject object, ObjectSelectorType selector) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, + ExpressionEvaluationException { + //noinspection unchecked + return actx.beans.repositoryService.selectorMatches(selector, (PrismObject) object, null, LOGGER, "current object"); + } + + void addObjects(Collection> objects) { + objects.forEach(this::addObject); + } + + private void addObject(PrismObject o) { + individualObjects.put(o.getOid(), toIndividualObject(o)); + } + + abstract IO toIndividualObject(PrismObject object); + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PartlyReferenceBasedObjectSet.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PartlyReferenceBasedObjectSet.java new file mode 100644 index 00000000000..3fb66831567 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PartlyReferenceBasedObjectSet.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; + +/** + * Object set partly represented as prism object references (for current object and link targets). + * Link sources could be represented as a query or references. + */ +abstract class PartlyReferenceBasedObjectSet extends ObjectSet { + + PartlyReferenceBasedObjectSet(ActionContext actx, OperationResult result) { + super(actx, result); + } + + @Override + PrismReferenceValue toIndividualObject(PrismObject object) { + return ObjectTypeUtil.createObjectRef(object, beans.prismContext).asReferenceValue(); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PolicyRuleScriptExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PolicyRuleScriptExecutor.java index 6be6edea178..e35635fe356 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PolicyRuleScriptExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PolicyRuleScriptExecutor.java @@ -6,7 +6,10 @@ */ package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; +import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.impl.scripting.ScriptingExpressionEvaluator; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; import com.evolveum.midpoint.security.api.SecurityContextManager; import org.jetbrains.annotations.NotNull; @@ -16,7 +19,6 @@ import com.evolveum.midpoint.model.impl.lens.EvaluatedPolicyRuleImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.assignments.EvaluatedAssignmentImpl; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.delta.DeltaSetTriple; @@ -30,6 +32,9 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExecutionPolicyActionType; +import java.util.ArrayList; +import java.util.List; + /** * Executes scripts defined in scriptExecution policy action. * Designed to be called during FINAL stage, just like notification action. @@ -41,50 +46,92 @@ public class PolicyRuleScriptExecutor { private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleScriptExecutor.class); + private static final String OP_EXECUTE_SCRIPTS_FROM_RULES = PolicyRuleScriptExecutor.class + ".executeScriptsFromRules"; + @Autowired PrismContext prismContext; @Autowired RelationRegistry relationRegistry; - @Autowired private SynchronousScriptExecutor synchronousScriptExecutor; - @Autowired private AsynchronousScriptExecutor asynchronousScriptExecutor; @Autowired @Qualifier("cacheRepositoryService") RepositoryService repositoryService; + @Autowired ModelService modelService; @Autowired SecurityContextManager securityContextManager; @Autowired ModelObjectResolver modelObjectResolver; + @Autowired ExpressionFactory expressionFactory; + @Autowired ScriptingExpressionEvaluator scriptingExpressionEvaluator; - public void execute(@NotNull LensContext context, Task task, OperationResult result) + public void execute(@NotNull LensContext context, Task task, OperationResult parentResult) throws SchemaException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { + if (context.hasFocusContext()) { context.recomputeFocus(); // Maybe not needed but we want to be sure (when computing linked objects) - for (EvaluatedPolicyRuleImpl rule : focusContext.getPolicyRules()) { - executeRuleScriptingActions(rule, context, task, result); + List rules = collectRelevantPolicyRules(context); + if (!rules.isEmpty()) { + executeScriptsFromCollectedRules(rules, context, task, parentResult); + } else { + LOGGER.trace("No relevant policy rules found"); } - DeltaSetTriple> triple = context.getEvaluatedAssignmentTriple(); - if (triple != null) { - // We need to apply rules from all the assignments - even those that were deleted. - for (EvaluatedAssignmentImpl assignment : triple.getAllValues()) { - for (EvaluatedPolicyRuleImpl rule : assignment.getAllTargetsPolicyRules()) { - executeRuleScriptingActions(rule, context, task, result); - } + } else { + LOGGER.trace("No focus context, no 'scriptExecution' policy actions"); + } + } + + private List collectRelevantPolicyRules(LensContext context) { + List rules = new ArrayList<>(); + collectFromFocus(rules, context); + collectFromAssignments(rules, context); + return rules; + } + + private void collectFromFocus(List rules, LensContext context) { + for (EvaluatedPolicyRuleImpl rule : context.getFocusContext().getPolicyRules()) { + collectRule(rules, rule); + } + } + + private void collectFromAssignments(List rules, LensContext context) { + DeltaSetTriple> triple = context.getEvaluatedAssignmentTriple(); + if (triple != null) { + // We need to apply rules from all the assignments - even those that were deleted. + for (EvaluatedAssignmentImpl assignment : triple.getAllValues()) { + for (EvaluatedPolicyRuleImpl rule : assignment.getAllTargetsPolicyRules()) { + collectRule(rules, rule); } } } } - private void executeRuleScriptingActions(EvaluatedPolicyRuleImpl rule, LensContext context, Task task, OperationResult result) { - if (rule.isTriggered()) { - for (ScriptExecutionPolicyActionType action : rule.getEnabledActions(ScriptExecutionPolicyActionType.class)) { - executeScriptingAction(action, rule, context, task, result); + private void collectRule(List rules, EvaluatedPolicyRuleImpl rule) { + if (rule.isTriggered() && rule.containsEnabledAction(ScriptExecutionPolicyActionType.class)) { + rules.add(rule); + } + } + + private void executeScriptsFromCollectedRules(List rules, + LensContext context, Task task, OperationResult parentResult) { + + // Must not be minor because of background OID information. + OperationResult result = parentResult.createSubresult(OP_EXECUTE_SCRIPTS_FROM_RULES); + try { + for (EvaluatedPolicyRuleImpl rule : rules) { + List enabledActions = rule.getEnabledActions(ScriptExecutionPolicyActionType.class); + LOGGER.trace("Rule {} has {} enabled script execution actions", rule, enabledActions.size()); + for (ScriptExecutionPolicyActionType action : enabledActions) { + ActionContext actx = new ActionContext(action, rule, context, task, this); + executeScriptingAction(actx, result); + } } + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); } } - private void executeScriptingAction(ScriptExecutionPolicyActionType action, EvaluatedPolicyRuleImpl rule, - LensContext context, Task task, OperationResult parentResult) { + private void executeScriptingAction(ActionContext actx, OperationResult parentResult) { LOGGER.debug("Executing policy action scripts ({}) in action: {}\non rule:{}", - action.getExecuteScript().size(), action, rule.debugDumpLazily()); - if (action.getAsynchronous() != null) { - asynchronousScriptExecutor.execute(action, rule, context, task, parentResult); + actx.action.getExecuteScript().size(), actx.action, actx.rule.debugDumpLazily()); + if (actx.action.getAsynchronousExecution() != null) { + new AsynchronousScriptExecutor(actx).submitScripts(parentResult); } else { - synchronousScriptExecutor.execute(action, rule, context, task, parentResult); + new SynchronousScriptExecutor(actx).executeScripts(parentResult); } } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/QueryBasedObjectSet.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/QueryBasedObjectSet.java new file mode 100644 index 00000000000..6deccb583d8 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/QueryBasedObjectSet.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import java.util.Arrays; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.repo.api.query.CompleteQuery; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; + +/** + * Object set represented by an object query. + */ +class QueryBasedObjectSet extends PartlyReferenceBasedObjectSet { + + private CompleteQuery linkedSourcesQuery; + + QueryBasedObjectSet(ActionContext actx, OperationResult result) { + super(actx, result); + } + + @Override + void collectLinkSources() throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, + SecurityViolationException, ExpressionEvaluationException { + assert objectSpec != null; + try (LinkSourceFinder sourceFinder = new LinkSourceFinder(actx, result)) { + linkedSourcesQuery = sourceFinder.getSourcesAsQuery(objectSpec.getLinkSource()); + } + } + + /** + * @return Set of objects as a single CompleteQuery, suitable e.g. to be included in a iterative scripting task. + */ + @NotNull + CompleteQuery asQuery() { + checkCollected(); + if (individualObjects.isEmpty()) { + if (linkedSourcesQuery != null) { + return linkedSourcesQuery; + } else { + return CompleteQuery.none(AssignmentHolderType.class, beans.prismContext); + } + } else { + CompleteQuery explicitObjectsQuery = CompleteQuery.inOid(individualObjects.values(), beans.prismContext); + if (linkedSourcesQuery != null) { + return CompleteQuery.or(Arrays.asList(linkedSourcesQuery, explicitObjectsQuery), beans.prismContext); + } else { + return explicitObjectsQuery; + } + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ReferenceBasedObjectSet.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ReferenceBasedObjectSet.java new file mode 100644 index 00000000000..5529c22787b --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ReferenceBasedObjectSet.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import java.util.Collection; +import java.util.List; + +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; + +/** + * Object set presented as a set of references. + */ +class ReferenceBasedObjectSet extends PartlyReferenceBasedObjectSet { + + ReferenceBasedObjectSet(ActionContext actx, OperationResult result) { + super(actx, result); + } + + @Override + void collectLinkSources() throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, + SecurityViolationException, ExpressionEvaluationException { + assert objectSpec != null; + try (LinkSourceFinder sourceFinder = new LinkSourceFinder(actx, result)) { + addReferences(sourceFinder.getSourcesAsReferences(objectSpec.getLinkSource())); + } + } + + private void addReferences(List references) { + references.forEach(o -> individualObjects.put(o.getOid(), o)); + } + + Collection asReferenceValues() { + return individualObjects.values(); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ScriptingTaskCreator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ScriptingTaskCreator.java index dffbee8c5e4..0b412e78fe7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ScriptingTaskCreator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ScriptingTaskCreator.java @@ -7,8 +7,82 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; +import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType.RUNNABLE; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsynchronousScriptExecutionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskRecurrenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; + /** - * + * Creates tasks of given type (single-run, iterative) for given (specified) executeScript beans. */ abstract class ScriptingTaskCreator { + + @NotNull final ActionContext actx; + @NotNull final PolicyRuleScriptExecutor beans; + + ScriptingTaskCreator(@NotNull ActionContext actx) { + this.actx = actx; + this.beans = actx.beans; + } + + /** + * Main entry point. Creates a task. + */ + abstract TaskType createTask(ExecuteScriptType executeScript, OperationResult result) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, + ExpressionEvaluationException; + + /** + * Creates empty task of given type (single run, iterative), not related to any specific script. + */ + TaskType createEmptyTask(OperationResult result) + throws SecurityViolationException, ObjectNotFoundException, SchemaException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + MidPointPrincipal principal = beans.securityContextManager.getPrincipal(); + if (principal == null) { + throw new SecurityViolationException("No current user"); + } + + AsynchronousScriptExecutionType asynchronousExecution = actx.action.getAsynchronousExecution(); + TaskType newTask; + if (asynchronousExecution.getTaskTemplateRef() != null) { + newTask = beans.modelObjectResolver.resolve(asynchronousExecution.getTaskTemplateRef(), TaskType.class, + null, "task template", actx.task, result); + } else { + newTask = new TaskType(beans.prismContext); + newTask.setName(PolyStringType.fromOrig("Execute script")); + newTask.setRecurrence(TaskRecurrenceType.SINGLE); + } + newTask.setName(PolyStringType.fromOrig(newTask.getName().getOrig() + " " + (int) (Math.random() * 10000))); + newTask.setOid(null); + newTask.setTaskIdentifier(null); + newTask.setOwnerRef(createObjectRef(principal.getFocus(), beans.prismContext)); + newTask.setExecutionStatus(RUNNABLE); + return newTask; + } + + /** + * Inserts script into task. + */ + void setScriptInTask(TaskType taskBean, ExecuteScriptType executeScript) + throws SchemaException { + //noinspection unchecked + PrismPropertyDefinition executeScriptDef = beans.prismContext.getSchemaRegistry() + .findPropertyDefinitionByElementName(SchemaConstants.SE_EXECUTE_SCRIPT); + PrismProperty executeScriptProp = executeScriptDef.instantiate(); + executeScriptProp.setRealValue(executeScript.clone()); + taskBean.asPrismObject().addExtensionItem(executeScriptProp); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunNoInputTaskCreator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunNoInputTaskCreator.java new file mode 100644 index 00000000000..64e6f3264d0 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunNoInputTaskCreator.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; + +import org.jetbrains.annotations.NotNull; + +/** + * Creates task with script execution request with no specific input added. + */ +class SingleRunNoInputTaskCreator extends AbstractSingleRunTaskCreator { + + SingleRunNoInputTaskCreator(@NotNull ActionContext actx) { + super(actx); + } + + @Override + TaskType createTask(ExecuteScriptType executeScript, OperationResult result) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + // Nothing special here. Creates task for the script "as is". + return createTaskForSingleRunScript(executeScript, result); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunTaskCreator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunTaskCreator.java new file mode 100644 index 00000000000..2b4f927e27c --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunTaskCreator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; + +import static com.evolveum.midpoint.schema.util.ExecuteScriptUtil.createInput; +import static com.evolveum.midpoint.schema.util.ExecuteScriptUtil.implantInput; + +import com.evolveum.midpoint.util.exception.*; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ValueListType; + +/** + * Creates task with script execution request having input consisting of object references derived + * from specification. + */ +class SingleRunTaskCreator extends AbstractSingleRunTaskCreator { + + SingleRunTaskCreator(@NotNull ActionContext actx) { + super(actx); + } + + @Override + @NotNull + TaskType createTask(ExecuteScriptType executeScript, OperationResult result) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, + ExpressionEvaluationException { + if (executeScript.getInput() != null) { + throw new UnsupportedOperationException("Explicit input with SINGLE_RUN task execution is not supported."); + } + + ReferenceBasedObjectSet objectSet = new ReferenceBasedObjectSet(actx, result); + objectSet.collect(); + ValueListType input = createInput(objectSet.asReferenceValues()); + ExecuteScriptType executeScriptWithInput = implantInput(executeScript, input); + + return createTaskForSingleRunScript(executeScriptWithInput, result); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SynchronousScriptExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SynchronousScriptExecutor.java index f937149a9df..5d1d76c35d8 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SynchronousScriptExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SynchronousScriptExecutor.java @@ -7,134 +7,80 @@ package com.evolveum.midpoint.model.impl.lens.projector.policy.scriptExecutor; +import static com.evolveum.midpoint.schema.util.ExecuteScriptUtil.createInputCloned; +import static com.evolveum.midpoint.schema.util.ExecuteScriptUtil.implantInput; + +import com.evolveum.midpoint.util.exception.*; + +import org.jetbrains.annotations.NotNull; + import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.impl.lens.EvaluatedPolicyRuleImpl; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.scripting.ScriptingExpressionEvaluator; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.expression.VariablesMap; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.MiscUtil; -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.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExecutionPolicyActionType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ValueListType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - /** - * + * Executes specified scripts synchronously (i.e. immediately). */ -@Component -public class SynchronousScriptExecutor { +class SynchronousScriptExecutor { private static final Trace LOGGER = TraceManager.getTrace(SynchronousScriptExecutor.class); private static final String OP_EXECUTE_SCRIPT = SynchronousScriptExecutor.class.getName() + ".executeScript"; - @Autowired private PolicyRuleScriptExecutor policyRuleScriptExecutor; - @Autowired private ScriptingExpressionEvaluator scriptingExpressionEvaluator; - @Autowired @Qualifier("cacheRepositoryService") RepositoryService repositoryService; + @NotNull private final ActionContext actx; - void execute(ScriptExecutionPolicyActionType action, EvaluatedPolicyRuleImpl rule, LensContext context, Task task, - OperationResult parentResult) { - List executeScript = action.getExecuteScript(); - for (ExecuteScriptType executeScriptBean : executeScript) { - executeScript(action, rule, context, task, parentResult, executeScriptBean); + SynchronousScriptExecutor(@NotNull ActionContext actx) { + this.actx = actx; + } + + void executeScripts(OperationResult parentResult) { + for (ExecuteScriptType executeScriptBean : actx.action.getExecuteScript()) { + executeScript(executeScriptBean, parentResult); } } - private void executeScript(ScriptExecutionPolicyActionType action, EvaluatedPolicyRuleImpl rule, LensContext context, - Task task, OperationResult parentResult, ExecuteScriptType specifiedExecuteScriptBean) { + private void executeScript(ExecuteScriptType specifiedExecuteScriptBean, OperationResult parentResult) { OperationResult result = parentResult.createSubresult(OP_EXECUTE_SCRIPT); try { - ExecuteScriptType realExecuteScriptBean; - if (specifiedExecuteScriptBean.getInput() == null && context.getFocusContext() != null) { - ValueListType input = createScriptInput(action, rule, context, context.getFocusContext(), result); - realExecuteScriptBean = specifiedExecuteScriptBean.clone().input(input); - } else { - realExecuteScriptBean = specifiedExecuteScriptBean; - } - VariablesMap initialVariables = createInitialVariables(action, rule, context); - scriptingExpressionEvaluator.evaluateExpression(realExecuteScriptBean, initialVariables, false, task, result); + ExecuteScriptType executeScriptBean = addInputIfNeeded(specifiedExecuteScriptBean, result); + VariablesMap initialVariables = createInitialVariables(); + actx.beans.scriptingExpressionEvaluator.evaluateExpression(executeScriptBean, initialVariables, + false, actx.task, result); } catch (Throwable t) { result.recordFatalError("Couldn't execute script policy action: " + t.getMessage(), t); LoggingUtils.logUnexpectedException(LOGGER, "Couldn't execute script with id={} in scriptExecution policy action '{}' (rule '{}'): {}", - t, action.getId(), action.getName(), rule.getName(), t.getMessage()); + t, actx.action.getId(), actx.action.getName(), actx.rule.getName(), t.getMessage()); } finally { result.computeStatusIfUnknown(); } } - private ValueListType createScriptInput(ScriptExecutionPolicyActionType action, EvaluatedPolicyRuleImpl rule, - LensContext context, LensFocusContext focusContext, OperationResult result) + private ExecuteScriptType addInputIfNeeded(ExecuteScriptType specifiedExecuteScriptBean, OperationResult result) throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { - ScriptExecutionObjectType object = action.getObject(); - if (object == null) { - return createInput(MiscUtil.singletonOrEmptyList(focusContext.getObjectAny())); + if (specifiedExecuteScriptBean.getInput() == null) { + FullDataBasedObjectSet objectSet = new FullDataBasedObjectSet(actx, result); + objectSet.collect(); + ValueListType input = createInputCloned(objectSet.asObjectValues()); + return implantInput(specifiedExecuteScriptBean, input); } else { - Map> objectsMap = new HashMap<>(); // using OID-keyed map to avoid duplicates - if (object.getCurrentObject() != null) { - PrismObject current = focusContext.getObjectAny(); - if (matches(current, object.getCurrentObject())) { - objectsMap.put(current.getOid(), current); - } - } - if (!object.getLinkTarget().isEmpty()) { - try (LinkTargetFinder targetFinder = new LinkTargetFinder(policyRuleScriptExecutor, context, rule, result)) { - for (LinkTargetObjectSelectorType linkTargetSelector : object.getLinkTarget()) { - addObjects(objectsMap, targetFinder.getTargets(linkTargetSelector)); - } - } - } - if (!object.getLinkSource().isEmpty()) { - try (LinkSourceFinder sourceFinder = new LinkSourceFinder(policyRuleScriptExecutor, context, result)) { - addObjects(objectsMap, sourceFinder.getSources(object.getLinkSource())); - } - } - return createInput(objectsMap.values()); + return specifiedExecuteScriptBean; } } - private boolean matches(PrismObject object, ObjectSelectorType selector) throws CommunicationException, - ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, - ExpressionEvaluationException { - //noinspection unchecked - return repositoryService.selectorMatches(selector, (PrismObject) object, null, LOGGER, "current object"); - } - - private void addObjects(Map> objectsMap, List> objects) { - objects.forEach(o -> objectsMap.put(o.getOid(), o)); - } - - private ValueListType createInput(Collection> objects) { - ValueListType input = new ValueListType(); - objects.forEach(o -> input.getValue().add(o.getValue().clone())); - return input; - } - - private VariablesMap createInitialVariables(ScriptExecutionPolicyActionType action, EvaluatedPolicyRule rule, - LensContext context) { + private VariablesMap createInitialVariables() { VariablesMap rv = new VariablesMap(); - rv.put(ExpressionConstants.VAR_POLICY_ACTION, action, ScriptExecutionPolicyActionType.class); - rv.put(ExpressionConstants.VAR_POLICY_RULE, rule, EvaluatedPolicyRule.class); - rv.put(ExpressionConstants.VAR_MODEL_CONTEXT, context, ModelContext.class); + rv.put(ExpressionConstants.VAR_POLICY_ACTION, actx.action, ScriptExecutionPolicyActionType.class); + rv.put(ExpressionConstants.VAR_POLICY_RULE, actx.rule, EvaluatedPolicyRule.class); + rv.put(ExpressionConstants.VAR_MODEL_CONTEXT, actx.context, ModelContext.class); return rv; } } diff --git a/model/model-intest/src/test/resources/member-recompute/archetype-department.xml b/model/model-intest/src/test/resources/member-recompute/archetype-department.xml index 9c627f31b03..1d1b1f77a08 100644 --- a/model/model-intest/src/test/resources/member-recompute/archetype-department.xml +++ b/model/model-intest/src/test/resources/member-recompute/archetype-department.xml @@ -33,7 +33,7 @@ - + iterative @@ -45,7 +45,7 @@ - + diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/MidpointAbstractHttpMessageConverter.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/MidpointAbstractHttpMessageConverter.java index fe045cca49a..e1f6263759d 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/MidpointAbstractHttpMessageConverter.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/MidpointAbstractHttpMessageConverter.java @@ -14,7 +14,6 @@ import java.util.function.Function; import javax.xml.namespace.QName; -import com.evolveum.midpoint.schema.util.ScriptingBeansUtil; import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; @@ -26,6 +25,7 @@ import com.evolveum.midpoint.common.LocalizationService; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ScriptingBeansUtil; import com.evolveum.midpoint.util.LocalizableMessage; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.LoggingUtils; @@ -60,7 +60,8 @@ protected boolean supports(Class clazz) { } @Override - protected T readInternal(@NotNull Class clazz, @NotNull HttpInputMessage inputMessage) + protected @NotNull T readInternal( + @NotNull Class clazz, @NotNull HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { PrismParser parser = getParser(inputMessage.getBody()); @@ -76,6 +77,7 @@ protected T readInternal(@NotNull Class clazz, @NotNull HttpInputMe // This code covers type migrations, eventually we'd like to drop old type support. object = migrateType(object, clazz); } + //noinspection ConstantConditions - we assume object is NOT null return clazz.cast(object); } catch (SchemaException e) { throw new HttpMessageNotReadableException("Failure during read", e, inputMessage); diff --git a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestConfig.java b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestConfig.java index 9d3a537013f..b50f7cd9a66 100644 --- a/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestConfig.java +++ b/model/rest-impl/src/main/java/com/evolveum/midpoint/rest/impl/RestConfig.java @@ -6,10 +6,21 @@ */ package com.evolveum.midpoint.rest.impl; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -26,10 +37,16 @@ public class RestConfig implements WebMvcConfigurer { - // TODO probably @Override addArgumentResolvers for @Converter processing? - + /** + * Registers content-types for path extension and request parameter usage. + * Not needed for header-based content negotiation and midPoint typically + * doesn't use this, but it doesn't hurt and may be handy for download URLs. + *

+ * See this + * tutorial for more about content negotiation. + */ @Override - public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { + public void configureContentNegotiation(@NotNull ContentNegotiationConfigurer configurer) { for (MediaType mediaType : MidpointYamlHttpMessageConverter.MEDIA_TYPES) { configurer.mediaType(mediaType.getSubtype(), mediaType); } @@ -53,4 +70,52 @@ public MidpointJsonHttpMessageConverter jsonConverter( PrismContext prismContext, LocalizationService localizationService) { return new MidpointJsonHttpMessageConverter(prismContext, localizationService); } + + /** + * All beans above will be first in the converter list and other Spring converters + * will be available as well. + * We want to add "catch-all" converter for cases like error output for any (even unsupported) + * content type. + */ + @Override + public void extendMessageConverters(List> converters) { + converters.add(new FallbackConverter()); + } + + private static class FallbackConverter extends AbstractHttpMessageConverter { + /** + * Supports all media types - that's the purpose of this converter. + */ + protected FallbackConverter() { + super(MediaType.ALL); + } + + /** + * Supports all object types - that's the purpose of this converter. + */ + @Override + protected boolean supports(@NotNull Class clazz) { + return true; + } + + /** + * Only for output, this can't read anything. + */ + @Override + public boolean canRead(@NotNull Class clazz, @Nullable MediaType mediaType) { + return false; + } + + @Override + protected @NotNull Object readInternal( + @NotNull Class clazz, @NotNull HttpInputMessage inputMessage) { + throw new UnsupportedOperationException("FallbackConverter is write-only"); + } + + @Override + protected void writeInternal(@NotNull Object o, @NotNull HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + outputMessage.getBody().write(o.toString().getBytes(StandardCharsets.UTF_8)); + } + } } diff --git a/repo/repo-api/pom.xml b/repo/repo-api/pom.xml index a9fb186f632..5717a9dc6eb 100644 --- a/repo/repo-api/pom.xml +++ b/repo/repo-api/pom.xml @@ -41,8 +41,8 @@ 4.2-SNAPSHOT - commons-lang - commons-lang + org.apache.commons + commons-collections4 org.apache.commons diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/CompleteQuery.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/CompleteQuery.java new file mode 100644 index 00000000000..13b179c4e87 --- /dev/null +++ b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/CompleteQuery.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.api.query; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.xml.namespace.QName; + +import org.apache.commons.collections4.CollectionUtils; +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.QueryFactory; +import com.evolveum.midpoint.prism.schema.SchemaRegistry; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * Wraps everything we need to count or look for objects. + */ +@Experimental +public class CompleteQuery implements DebugDumpable { + + @NotNull private final Class type; + private final ObjectQuery query; + private final Collection> options; + + public CompleteQuery(@NotNull Class type, ObjectQuery query, Collection> options) { + this.type = type; + this.query = query; + this.options = options; + } + + /** + * Composes complete queries into single "OR" query. Requires compatible paging and options. + * (Currently no paging nor options is allowed.) + */ + @NotNull + public static CompleteQuery or(List> completeQueries, PrismContext prismContext) { + QueryFactory queryFactory = prismContext.queryFactory(); + + Class commonType = getCommonAncestor(completeQueries); + List disjuncts = new ArrayList<>(); + for (CompleteQuery completeQuery : completeQueries) { + if (CollectionUtils.isNotEmpty(completeQuery.options)) { + throw new UnsupportedOperationException("Query options are not supported here: " + completeQuery.options); + } + ObjectQuery query = completeQuery.query; + if (query != null && query.getPaging() != null) { + throw new UnsupportedOperationException("Query paging is not supported here: " + query.getPaging()); + } + + ObjectFilter filter = query != null ? query.getFilter() : null; + Class type = completeQuery.type; + if (type.equals(commonType)) { + if (filter != null) { + disjuncts.add(filter); + } + } else { + QName typeName = prismContext.getSchemaRegistry().determineTypeForClassRequired(type); + disjuncts.add(queryFactory.createType(typeName, filter)); + } + } + ObjectQuery orQuery = queryFactory.createQuery(queryFactory.createOrOptimized(disjuncts)); + return new CompleteQuery<>(commonType, orQuery, null); + } + + @NotNull + private static Class getCommonAncestor(List> completeQueries) { + if (completeQueries.isEmpty()) { + return ObjectType.class; + } else { + Set> types = completeQueries.stream() + .map(CompleteQuery::getType) + .collect(Collectors.toSet()); + Class commonAncestor = MiscUtil.determineCommonAncestor(types); + if (commonAncestor == null || !ObjectType.class.isAssignableFrom(commonAncestor)) { + throw new IllegalStateException("Common ancestor for complete queries is unknown or not an ObjectType: " + + commonAncestor + " for " + completeQueries); + } else { + //noinspection unchecked + return (Class) commonAncestor; + } + } + } + + public static CompleteQuery inOid(Collection references, PrismContext prismContext) { + Class commonAncestor = getCommonAncestorForReferences(references, prismContext); + String[] oids = references.stream().map(PrismReferenceValue::getOid).distinct().toArray(String[]::new); + ObjectQuery query = prismContext.queryFor(commonAncestor) + .id(oids) + .build(); + return new CompleteQuery<>(commonAncestor, query, null); + } + + @NotNull + private static Class getCommonAncestorForReferences(Collection references, + PrismContext prismContext) { + Class commonAncestor1; + SchemaRegistry schemaRegistry = prismContext.getSchemaRegistry(); + Set typeNames = references.stream() + .map(PrismReferenceValue::getTargetType) + .collect(Collectors.toSet()); + Set> types = typeNames.stream() + .map(schemaRegistry::determineClassForTypeRequired) + .collect(Collectors.toSet()); + Class commonAncestor = MiscUtil.determineCommonAncestor(types); + if (commonAncestor == null || !ObjectType.class.isAssignableFrom(commonAncestor)) { + commonAncestor1 = ObjectType.class; // Let's be more relaxed here + } else { + //noinspection unchecked + commonAncestor1 = (Class) commonAncestor; + } + return commonAncestor1; + } + + public static CompleteQuery none(Class type, PrismContext prismContext) { + return new CompleteQuery<>(type, + prismContext.queryFactory().createQuery(prismContext.queryFactory().createNone()), + null); + } + + public @NotNull Class getType() { + return type; + } + + public ObjectQuery getQuery() { + return query; + } + + public Collection> getOptions() { + return options; + } + + @Override + public String debugDump(int indent) { + StringBuilder sb = new StringBuilder(); + DebugUtil.debugDumpWithLabelLn(sb, "Object type", type, indent); + DebugUtil.debugDumpWithLabelLn(sb, "Query", query, indent); + DebugUtil.debugDumpWithLabel(sb, "Options", options, indent); + return sb.toString(); + } +} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/LogicalFilter.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/LogicalFilter.java deleted file mode 100644 index 7e500c10ba1..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/LogicalFilter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -import javax.xml.namespace.QName; - -/** - * @author lazyman - */ -public abstract class LogicalFilter implements QueryFilter { - - private QName type; - - public LogicalFilter() { - this(null); - } - - public LogicalFilter(QName type) { - this.type = type; - } - - @Override - public QName getType() { - return type; - } - - @Override - public void setType(QName type) { - this.type = type; - } -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilter.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilter.java deleted file mode 100644 index 25a65a7a16c..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -import org.apache.commons.lang.Validate; -import org.w3c.dom.Element; - -import java.util.Arrays; -import java.util.List; - -/** - * @author lazyman - */ -public class NAryLogicalFilter extends LogicalFilter { - - private List filters; - private NAryLogicalFilterType filterType; - - public NAryLogicalFilter() { - } - - public NAryLogicalFilter(NAryLogicalFilterType filterType, QueryFilter... filters) { - setFilterType(filterType); - setFilters(Arrays.asList(filters)); - } - - public List getFilters() { - return filters; - } - - public NAryLogicalFilterType getFilterType() { - return filterType; - } - - public void setFilters(List filters) { - Validate.notNull(filters, "Filter list must not be null."); - Validate.isTrue(filters.size() >= 2, "You must use two or more filters."); - this.filters = filters; - } - - public void setFilterType(NAryLogicalFilterType filterType) { - Validate.notNull(filterType, "Filter type must not be null."); - this.filterType = filterType; - } - - @Override - public void toDOM(Element parent) { - //todo implement - } -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilterType.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilterType.java deleted file mode 100644 index 11a8ca0b317..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilterType.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -/** - * @author lazyman - */ -public enum NAryLogicalFilterType { - - AND("and"), OR("or"); - - private String elementName; - - NAryLogicalFilterType(String elementName) { - this.elementName = elementName; - } - - public String getElementName() { - return elementName; - } -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NotLogicalFilter.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NotLogicalFilter.java deleted file mode 100644 index 6c296f7ad1c..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NotLogicalFilter.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -import org.apache.commons.lang.Validate; -import org.w3c.dom.Element; - -/** - * @author lazyman - */ -public class NotLogicalFilter extends LogicalFilter { - - private QueryFilter filter; - - public NotLogicalFilter(QueryFilter filter) { - setFilter(filter); - } - - public QueryFilter getFilter() { - return filter; - } - - public void setFilter(QueryFilter filter) { - Validate.notNull(filter, "Filter must not be null."); - this.filter = filter; - } - - @Override - public void toDOM(Element parent) { - //todo implement - } -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/Query.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/Query.java deleted file mode 100644 index 4f9aa32dcbe..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/Query.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -import javax.xml.namespace.QName; - -/** - * @author lazyman - */ -public class Query { - - private String description; - private QName type; - private QueryFilter filter; - - public String getDescription() { - return description; - } - - public QName getType() { - return type; - } - - public QueryFilter getFilter() { - return filter; - } - - public void setDescription(String description) { - this.description = description; - } - - public void setFilter(QueryFilter filter) { - this.filter = filter; - } - - public void setType(QName type) { - this.type = type; - } -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilter.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilter.java deleted file mode 100644 index 1c8f3a958b7..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilter.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -import org.w3c.dom.Element; - -import javax.xml.namespace.QName; - -/** - * Common interface for all types of query filters. - * - * @author lazyman - */ -public interface QueryFilter { - - /** - * @return prism container qname type - */ - QName getType(); - - void setType(QName type); - - /** - * This method is used for query serialization. - * - * @param parent - */ - void toDOM(Element parent); -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilterFactory.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilterFactory.java deleted file mode 100644 index 5c8e69fe5e7..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilterFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -import org.w3c.dom.Element; - -/** - * @author lazyman - */ -public final class QueryFilterFactory { - - private QueryFilterFactory() { - } - - public static QueryFilter createQueryFilter(Element element) { - return null; - } - - //todo add factory methods... -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilter.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilter.java deleted file mode 100644 index 3d3a6274f2d..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilter.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.schema.SchemaConstantsGenerated; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.namespace.QName; - - -/** - * @author lazyman - */ -public class SimpleFilter implements QueryFilter { - - private QName type; - private ItemPath propertyPath; - private SimpleFilterType filterType; - private PrismPropertyValue value; - - public SimpleFilter() { - } - - public SimpleFilter(SimpleFilterType filterType, ItemPath propertyPath) { - this(filterType, propertyPath, null); - } - - public SimpleFilter(SimpleFilterType filterType, ItemPath propertyPath, PrismPropertyValue value) { - this.filterType = filterType; - this.propertyPath = propertyPath; - this.value = value; - } - - public SimpleFilterType getFilterType() { - return filterType; - } - - public ItemPath getPropertyPath() { - return propertyPath; - } - - public PrismPropertyValue getValue() { - return value; - } - - public void setFilterType(SimpleFilterType filterType) { - this.filterType = filterType; - } - - public void setPropertyPath(ItemPath propertyPath) { - this.propertyPath = propertyPath; - } - - public void setValue(PrismPropertyValue value) { - this.value = value; - } - - @Override - public QName getType() { - return type; - } - - public void setType(QName type) { - this.type = type; - } - - @Override - public void toDOM(Element parent) { - Document document = parent.getOwnerDocument(); - Element element = document.createElementNS(SchemaConstantsGenerated.NS_COMMON, getFilterType().getElementName()); - parent.appendChild(element); - - //todo implement - } -} diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilterType.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilterType.java deleted file mode 100644 index 4612de60303..00000000000 --- a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilterType.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.repo.api.query; - -/** - * @author lazyman - */ -public enum SimpleFilterType { - - EQ("equal"), GT("gt"), LT("lt"), GTEQ("gtEqual"), LTEQ("ltEqual"), NEQ("notEqual"), - NULL("null"), NOT_NULL("notNull"), LIKE("like"); - - private String elementName; - - SimpleFilterType(String elementName) { - this.elementName = elementName; - } - - public String getElementName() { - return elementName; - } -} diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/query/SelectorToFilterTranslator.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/query/SelectorToFilterTranslator.java new file mode 100644 index 00000000000..ea0ad553b4f --- /dev/null +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/query/SelectorToFilterTranslator.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.repo.common.query; + +import java.util.ArrayList; +import java.util.List; +import javax.xml.namespace.QName; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.query.NoneFilter; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.QueryFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectQueryUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSelectorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; + +/** + * Translates a selector (ObjectSelectorType) to appropriate ObjectFilter. + * + * See also SecurityEnforcerImpl#computeSecurityFilterPhase. + */ +public class SelectorToFilterTranslator { + + @NotNull private final ObjectSelectorType selector; + @NotNull private final List components = new ArrayList<>(); + @NotNull private final String contextDescription; + + @NotNull private final PrismContext prismContext; + @NotNull private final QueryFactory queryFactory; + @NotNull private final ExpressionFactory expressionFactory; + + @NotNull private final Task task; + @NotNull private final OperationResult result; + + @NotNull private Class targetType; + @NotNull private PrismObjectDefinition targetDefinition; + + private boolean evaluated; + + public SelectorToFilterTranslator(@NotNull ObjectSelectorType selector, @NotNull Class targetType, + @NotNull String contextDescription, @NotNull PrismContext prismContext, @NotNull ExpressionFactory expressionFactory, + @NotNull Task task, @NotNull OperationResult result) { + this.selector = selector; + this.contextDescription = contextDescription; + + this.prismContext = prismContext; + this.queryFactory = prismContext.queryFactory(); + this.expressionFactory = expressionFactory; + + this.task = task; + this.result = result; + + this.targetType = targetType; + this.targetDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(targetType); + if (this.targetDefinition == null) { + throw new IllegalArgumentException("No object definition for " + targetType); + } + } + + @NotNull + public ObjectFilter createFilter() throws SchemaException, ConfigurationException, ObjectNotFoundException, + CommunicationException, SecurityViolationException, ExpressionEvaluationException { + checkAlreadyEvaluated(); + + if (!applyType()) { + return none(); + } + applyFilter(); + applyArchetypes(); + applyOrg(); + checkNoSubtype(); + + return queryFactory.createAndOptimized(components); + } + + private void checkAlreadyEvaluated() { + if (evaluated) { + throw new IllegalStateException("A filter was already created for this selector: " + + selector + " in " + contextDescription); + } else { + evaluated = true; + } + } + + private boolean applyType() throws SchemaException { + QName typeName = selector.getType(); + if (typeName != null) { + QName qualifiedTypeName = prismContext.getSchemaRegistry().qualifyTypeName(typeName); + PrismObjectDefinition objectDef = prismContext.getSchemaRegistry().findObjectDefinitionByType(qualifiedTypeName); + if (objectDef == null) { + throw new SchemaException("Unknown object type " + typeName + " in " + contextDescription); + } + Class objectClass = objectDef.getCompileTimeClass(); + if (targetType.equals(objectClass)) { + // nothing to do + } else if (!targetType.isAssignableFrom(objectClass)) { + return false; + } else { + components.add(queryFactory.createType(qualifiedTypeName, null)); + + // Remember more specific object type and definition now + //noinspection unchecked + targetType = (Class) objectClass; + targetDefinition = objectDef; + } + } + return true; + } + + private void applyFilter() throws CommunicationException, ObjectNotFoundException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + SearchFilterType filterBean = selector.getFilter(); + if (filterBean != null) { + ObjectFilter specFilter = parseAndEvaluateFilter(filterBean); + if (specFilter != null) { + ObjectQueryUtil.assertNotRaw(specFilter, "Filter in " + contextDescription + " has undefined items. Maybe a 'type' specification is missing?"); + ObjectQueryUtil.assertPropertyOnly(specFilter, "Filter in " + contextDescription + " object is not property-only filter"); + components.add(specFilter); + } + } + } + + private ObjectFilter parseAndEvaluateFilter(SearchFilterType filterBean) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, + SecurityViolationException { + ObjectFilter filter = prismContext.getQueryConverter().createObjectFilter(targetDefinition, filterBean); + if (filter != null) { + ExpressionVariables variables = new ExpressionVariables(); // TODO + return ExpressionUtil.evaluateFilterExpressions(filter, variables, MiscSchemaUtil.getExpressionProfile(), + expressionFactory, prismContext, "expression in " + contextDescription, task, result); + } else { + return null; + } + } + + private void applyArchetypes() { + List archetypeRefs = selector.getArchetypeRef(); + if (!archetypeRefs.isEmpty()) { + List disjuncts = new ArrayList<>(); + for (ObjectReferenceType archetypeRef : archetypeRefs) { + disjuncts.add(prismContext.queryFor(targetType) + .item(AssignmentHolderType.F_ARCHETYPE_REF).ref(archetypeRef.getOid()) + .buildFilter()); + } + components.add(queryFactory.createOrOptimized(disjuncts)); + } + } + + private void applyOrg() { + ObjectReferenceType orgRef = selector.getOrgRef(); + if (orgRef != null) { + if (orgRef.getOid() != null) { + components.add( + prismContext.queryFor(ObjectType.class) + .isChildOf(orgRef.getOid()) + .buildFilter() + ); + } else { + throw new UnsupportedOperationException("orgRef without OID is not supported in " + contextDescription); + } + } + } + + private void checkNoSubtype() { + if (selector.getSubtype() != null) { + throw new UnsupportedOperationException("Subtype is not supported in " + contextDescription); + } + } + + private NoneFilter none() { + return queryFactory.createNone(); + } + + public Class getNarrowedTargetType() { + if (evaluated) { + return targetType; + } else { + throw new IllegalStateException("Selector was not evaluated yet"); + } + } +} diff --git a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java index 6fab57ccd3e..f144477b139 100644 --- a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java +++ b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java @@ -1325,6 +1325,8 @@ public boolean canSearch(String[] o /** * @return additional security filter. This filter is supposed to be added (operation "AND") to the original filter. + * + * See also {@link com.evolveum.midpoint.repo.common.query.SelectorToFilterTranslator} (should be merged eventually?) */ private F computeSecurityFilterPhase(MidPointPrincipal principal, String[] operationUrls, AuthorizationPhaseType phase, boolean includeNullPhase,