From bd360c6209bb122592ddc9c58479055e6b5c91a4 Mon Sep 17 00:00:00 2001 From: lskublik Date: Thu, 28 May 2020 10:54:34 +0200 Subject: [PATCH 1/5] removing raw option for count of shadows got from collection --- .../model/impl/controller/CollectionProcessor.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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, From ef9c585c26ac03fab48f4aeb3e267ce7491df434 Mon Sep 17 00:00:00 2001 From: lskublik Date: Thu, 28 May 2020 11:11:44 +0200 Subject: [PATCH 2/5] adding of 'elaborate' to element baseCollectionRef of CollectionRefSpecificationType --- .../src/main/resources/xml/ns/public/common/common-core-3.xsd | 1 + 1 file changed, 1 insertion(+) 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 From 0901ddaf63f4987729ac2e80d0ed1602bb53f7b4 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Thu, 28 May 2020 13:28:44 +0200 Subject: [PATCH 3/5] Improve asynchronous execution of scripting rules Full support for three modes of asynchronous execution of "execute script" policy actions (iterative, single run, single run with no input). Removed obsolete query classes from repo API. Introduced experimental CompleteQuery class to encapsulate substantial items related to a query (type, options). A part of MID-5967 implementation. --- .../midpoint/prism/query/QueryFactory.java | 23 +- .../query/builder/S_AtomicFilterEntry.java | 2 + .../midpoint/prism/schema/SchemaRegistry.java | 19 ++ .../prism/impl/query/builder/R_Filter.java | 6 + .../schema/util/ExecuteScriptUtil.java | 36 ++++ .../midpoint/schema/util/ObjectTypeUtil.java | 6 +- .../xml/ns/public/common/common-policy-3.xsd | 2 +- .../ns/public/model/scripting/scripting-3.xsd | 2 +- .../com/evolveum/midpoint/util/MiscUtil.java | 15 ++ .../midpoint/model/impl/lens/LensContext.java | 4 + .../AbstractSingleRunTaskCreator.java | 49 +++++ .../policy/scriptExecutor/ActionContext.java | 39 ++++ .../AsynchronousScriptExecutor.java | 92 ++++---- .../FullDataBasedObjectSet.java | 43 ++++ .../IterativeScriptingTaskCreator.java | 154 +++++--------- .../scriptExecutor/LinkSourceFinder.java | 158 +++++++++----- .../scriptExecutor/LinkTargetFinder.java | 37 ++-- .../policy/scriptExecutor/ObjectSet.java | 128 +++++++++++ .../PartlyReferenceBasedObjectSet.java | 29 +++ .../PolicyRuleScriptExecutor.java | 97 ++++++--- .../scriptExecutor/QueryBasedObjectSet.java | 60 ++++++ .../ReferenceBasedObjectSet.java | 42 ++++ .../scriptExecutor/ScriptingTaskCreator.java | 76 ++++++- .../SingleRunNoInputTaskCreator.java | 33 +++ .../scriptExecutor/SingleRunTaskCreator.java | 48 +++++ .../SynchronousScriptExecutor.java | 124 +++-------- .../member-recompute/archetype-department.xml | 4 +- repo/repo-api/pom.xml | 4 +- .../repo/api/query/CompleteQuery.java | 159 ++++++++++++++ .../repo/api/query/LogicalFilter.java | 36 ---- .../repo/api/query/NAryLogicalFilter.java | 55 ----- .../repo/api/query/NAryLogicalFilterType.java | 26 --- .../repo/api/query/NotLogicalFilter.java | 37 ---- .../midpoint/repo/api/query/Query.java | 44 ---- .../midpoint/repo/api/query/QueryFilter.java | 34 --- .../repo/api/query/QueryFilterFactory.java | 25 --- .../midpoint/repo/api/query/SimpleFilter.java | 83 -------- .../repo/api/query/SimpleFilterType.java | 27 --- .../query/SelectorToFilterTranslator.java | 199 ++++++++++++++++++ .../enforcer/impl/SecurityEnforcerImpl.java | 2 + 40 files changed, 1347 insertions(+), 712 deletions(-) create mode 100644 infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ExecuteScriptUtil.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/AbstractSingleRunTaskCreator.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ActionContext.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/FullDataBasedObjectSet.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ObjectSet.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/PartlyReferenceBasedObjectSet.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/QueryBasedObjectSet.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/ReferenceBasedObjectSet.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunNoInputTaskCreator.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/scriptExecutor/SingleRunTaskCreator.java create mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/CompleteQuery.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/LogicalFilter.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilter.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NAryLogicalFilterType.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/NotLogicalFilter.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/Query.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilter.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/QueryFilterFactory.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilter.java delete mode 100644 repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/query/SimpleFilterType.java create mode 100644 repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/query/SelectorToFilterTranslator.java 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-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/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/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, From 4a9592f8ba09f7f5ac1f89cdf4ec50ce499d8467 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Thu, 28 May 2020 14:59:44 +0200 Subject: [PATCH 4/5] MidpointAbstractHttpMessageConverter: added @Nullable/NotNull --- .../rest/impl/MidpointAbstractHttpMessageConverter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); From fe2bc9149906d7ebd1129febc34456d75a3505e0 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Thu, 28 May 2020 15:01:13 +0200 Subject: [PATCH 5/5] RestConfig: added fallback */* converter and more explanation comments --- .../midpoint/rest/impl/RestConfig.java | 71 ++++++++++++++++++- 1 file changed, 68 insertions(+), 3 deletions(-) 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)); + } + } }