diff --git a/build-system/pom.xml b/build-system/pom.xml index 727c429582c..d4b5e171e09 100644 --- a/build-system/pom.xml +++ b/build-system/pom.xml @@ -78,7 +78,7 @@ 2.3.0 6.0.6 8.0.0 - 2.4.14 + 2.5.6 5.22.0 5.22.0 1.3 @@ -95,6 +95,8 @@ 1.72 3.0.0-M2 1.8 + 5.1.2 + 7.1.1 @@ -223,6 +225,7 @@ org.codehaus.groovy groovy-all ${groovy.version} + pom org.python @@ -1226,7 +1229,21 @@ jetty-server 9.4.11.v20180605 - + + org.apache.qpid + qpid-broker-core + ${qpid-broker.version} + + + org.apache.qpid + qpid-broker-plugins-amqp-0-8-protocol + ${qpid-broker.version} + + + org.apache.qpid + qpid-broker-plugins-memory-store + ${qpid-broker.version} + diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java index a3b221d6caf..2da734a05f0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/page/PageBase.java @@ -46,6 +46,7 @@ import com.evolveum.midpoint.prism.query.builder.S_FilterEntryOrEmpty; import com.evolveum.midpoint.prism.util.PolyStringUtils; import com.evolveum.midpoint.repo.api.CacheDispatcher; +import com.evolveum.midpoint.repo.api.CounterManager; import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.repo.common.expression.Expression; import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; @@ -311,6 +312,9 @@ public abstract class PageBase extends WebPage implements ModelServiceLocator { @SpringBean private MidpointFunctions midpointFunctions; + + @SpringBean + private CounterManager counterManager; private List breadcrumbs; @@ -472,6 +476,10 @@ public LocalizationService getLocalizationService() { public MidpointFunctions getMidpointFunctions() { return midpointFunctions; } + + public CounterManager getCounterManager() { + return counterManager; + } @Contract(pure = true) public PrismContext getPrismContext() { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.html index 426a442cd6a..2d23861f0e0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.html @@ -18,6 +18,28 @@ + +
+ +
+

+ + + + + + +
+ + + + + + +
+
+
+
diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.java index c3b92c5c163..3d941554eae 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/configuration/InternalsCountersPanel.java @@ -1,15 +1,22 @@ package com.evolveum.midpoint.web.page.admin.configuration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.IModel; import com.evolveum.midpoint.gui.api.component.BasePanel; +import com.evolveum.midpoint.repo.api.CounterSepcification; import com.evolveum.midpoint.schema.internals.InternalCounters; import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel; public class InternalsCountersPanel extends BasePanel> { @@ -18,7 +25,14 @@ public class InternalsCountersPanel extends BasePanel private static final String ID_COUNTERS_TABLE = "countersTable"; private static final String ID_COUNTER_LABEL = "counterLabel"; private static final String ID_COUNTER_VALUE = "counterValue"; - + private static final String ID_THRESHOLD_COUNTER = "thresholdCounter"; + private static final String ID_THRESHOLD_COUNTERS_TABLE = "thresholdCountersTable"; + private static final String ID_COUNTER_TASK_LABEL = "counterTask"; + private static final String ID_COUNTER_POLICY_RULE_LABEL = "counterPolicyRule"; + private static final String ID_COUNTER_COUNT_LABEL = "counterCount"; + private static final String ID_RESET_THRESHOLD_COUNTER = "resetThresholdCounter"; + + public InternalsCountersPanel(String id) { super(id); } @@ -29,6 +43,49 @@ protected void onInitialize() { setOutputMarkupId(true); + Label thresholdCounter = new Label(ID_THRESHOLD_COUNTER, createStringResource("InternalsCountersPanel.thresholds")); + add(thresholdCounter); + + ListView thresholdCountersTable = new ListView(ID_THRESHOLD_COUNTERS_TABLE, createThresholdCounterModel()) { + private static final long serialVersionUID = 1L; + + @Override + protected void populateItem(ListItem item) { + CounterSepcification counter = item.getModelObject(); + Label task = new Label(ID_COUNTER_TASK_LABEL, counter.getTaskName()); + item.add(task); + + Label policyRule = new Label(ID_COUNTER_POLICY_RULE_LABEL, counter.getPolicyRuleName()); + item.add(policyRule); + + Label count = new Label(ID_COUNTER_COUNT_LABEL, counter.getCount()); + item.add(count); + + AjaxLink resetCounter = new AjaxLink(ID_RESET_THRESHOLD_COUNTER) { + + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + ConfirmationPanel confirmPanel = new ConfirmationPanel(getPageBase().getMainPopupBodyId(), createStringResource("InternalsCountersPanel.reset.confirm.message", counter.getTaskName(), counter.getPolicyRuleName())) { + + private static final long serialVersionUID = 1L; + + public void yesPerformed(AjaxRequestTarget target) { + getPageBase().getCounterManager().removeCounter(counter); + target.add(InternalsCountersPanel.this); + }; + }; + getPageBase().showMainPopup(confirmPanel, target); + target.add(InternalsCountersPanel.this); + } + }; + item.add(resetCounter); + } + + }; + add(thresholdCountersTable); + ListView countersTable = new ListView(ID_COUNTERS_TABLE, Arrays.asList(InternalCounters.values())) { private static final long serialVersionUID = 1L; @@ -54,4 +111,17 @@ public String getObject() { }; add(countersTable); } -} + + private IModel> createThresholdCounterModel() { + return new IModel>() { + private static final long serialVersionUID = 1L; + + @Override + public List getObject() { + Collection thresholdCounters = getPageBase().getCounterManager().listCounters(); + return new ArrayList<>(thresholdCounters); + } + }; + } + +} \ No newline at end of file diff --git a/gui/admin-gui/src/main/resources/localization/Midpoint.properties b/gui/admin-gui/src/main/resources/localization/Midpoint.properties index ecd4fba46c4..e33ffcd8896 100755 --- a/gui/admin-gui/src/main/resources/localization/Midpoint.properties +++ b/gui/admin-gui/src/main/resources/localization/Midpoint.properties @@ -4375,3 +4375,7 @@ ResourceSummaryPanel.DOWN=Down operation.com.evolveum.midpoint.web.page.admin.configuration.InternalsThreadsPanel.executeShowAllThreads=Show all threads (Gui) operation.com.evolveum.midpoint.web.page.admin.configuration.InternalsThreadsPanel.executeShowTasksThreads=Show running tasks threads (Gui) operation.com.evolveum.midpoint.web.page.admin.configuration.InternalsThreadsPanel.executeRecordTasksThreads=Record running tasks threads (Gui) +InternalsCountersPanel.thresholds=Thresholds +PageInternals.title.thresholds.counters=Thresholds counters +InternalsCountersPanel.reset.confirm.message=Do you really want to remove counters for {0} ({1}) +InternalsCountersPanel.threshold.reset.button=Reset \ No newline at end of file diff --git a/gui/admin-gui/src/main/resources/localization/Midpoint_en.properties b/gui/admin-gui/src/main/resources/localization/Midpoint_en.properties index 4e670cb1e86..fb3c1ff1b8b 100644 --- a/gui/admin-gui/src/main/resources/localization/Midpoint_en.properties +++ b/gui/admin-gui/src/main/resources/localization/Midpoint_en.properties @@ -1105,7 +1105,7 @@ PageAdmin.menu.top.configuration.expressionEvaluator=Expression evaluator PageAdmin.menu.top.configuration.importObject=Import object PageAdmin.menu.top.configuration.internals=Internals configuration PageAdmin.menu.top.configuration.objectPolicy=Object policy -PageAdmin.menu.top.configuration.globalPolicyRule=Global policy rule +PageAdmin.menu.top.configuration.globalPolicyRule=Global policy rules PageAdmin.menu.top.configuration.globalAccountSynchronization=Global account synchronization PageAdmin.menu.top.configuration.cleanupPolicy=Cleanup Policy PageAdmin.menu.top.configuration.workflow=Workflow configuration @@ -4261,19 +4261,19 @@ ApprovalPolicyActionType.details.newValue=New approval policy action LifecycleStateType.details.newValue=New lifecycle state NotificationPolicyActionType.details.newValue=New notification policy action PropertyConstraintType.details.newValue=New property constraint -objectState.details.newValue=New object state -assignmentState.details.newValue=New assignment state -hasAssignment.details.newValue=New has assignment -hasNoAssignment.details.newValue=New has no assignment -exclusion.details.newValue=New exclusion -minAssignees.details.newValue=New minimum assignees -maxAssignees.details.newValue=New maximum assignees -modification.details.newValue=New modification -assignment.details.newValue=New assignment -objectTimeValidity.details.newValue=New object time validity -assignmentTimeValidity.details.newValue=New assignment time validity -situation.details.newValue=New policy situation -transition.details.newValue=New transition +objectState.details.newValue=New 'object state' constraint +assignmentState.details.newValue=New 'assignment state' constraint +hasAssignment.details.newValue=New 'has assignment' constraint +hasNoAssignment.details.newValue=New 'has no assignment' constraint +exclusion.details.newValue=New 'exclusion' constraint +minAssignees.details.newValue=New 'minimum assignees' constraint +maxAssignees.details.newValue=New 'maximum assignees' constraint +modification.details.newValue=New 'modification' constraint +assignment.details.newValue=New 'assignment' constraint +objectTimeValidity.details.newValue=New 'object time validity' constraint +assignmentTimeValidity.details.newValue=New 'assignment time validity' constraint +situation.details.newValue=New 'situation' constraint +transition.details.newValue=New 'transition' constraint ref.details.newValue=New reference objectMaxAssigneesViolation.details.newValue=New object max assignees violation objectMinAssigneesViolation.details.newValue=New object min assignees violation diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/SynchronizationUtils.java b/infra/common/src/main/java/com/evolveum/midpoint/common/SynchronizationUtils.java index 2cd8a1486c7..c8c3e6726d0 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/SynchronizationUtils.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/SynchronizationUtils.java @@ -134,8 +134,6 @@ private static List> createSynchronizationTimestampsDelta(Prism return deltas; } - - private static List getSituationFromSameChannel( PrismObject shadow, String channel) { diff --git a/infra/common/src/test/java/com/evolveum/midpoint/common/test/UtilsTest.java b/infra/common/src/test/java/com/evolveum/midpoint/common/test/UtilsTest.java index eb06a16c704..a25d65cc771 100644 --- a/infra/common/src/test/java/com/evolveum/midpoint/common/test/UtilsTest.java +++ b/infra/common/src/test/java/com/evolveum/midpoint/common/test/UtilsTest.java @@ -25,7 +25,7 @@ import java.io.IOException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Unit tests for Util class @@ -56,7 +56,7 @@ public void cleaupUtfTest() throws FileNotFoundException, IOException { FileChannel fc = stream.getChannel(); MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); - badStr = Charset.forName("UTF-8").decode(bb).toString(); + badStr = StandardCharsets.UTF_8.decode(bb).toString(); } finally { stream.close(); diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/ItemInfo.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/ItemInfo.java index baf98626b33..54317b9ac8e 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/ItemInfo.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/ItemInfo.java @@ -217,11 +217,12 @@ private static ID augmentWithClass(ID definition, Cl return definition; } @SuppressWarnings("unchecked") - List defFromClass = schemaRegistry.findItemDefinitionsByCompileTimeClass((Class) clazz, (Class) definitionClass); - if (defFromClass.size() != 1) { + List defFromClass = schemaRegistry.findItemDefinitionsByCompileTimeClass(clazz, (Class) definitionClass); + ID singleDef = DefinitionStoreUtils.getOne(defFromClass, false, null); + if (singleDef == null) { return definition; } else { - return schemaRegistry.selectMoreSpecific(definition, defFromClass.get(0)); + return schemaRegistry.selectMoreSpecific(definition, singleDef); } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/util/RawTypeUtil.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/util/RawTypeUtil.java index 769dc7e03d6..41ba48c5e96 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/util/RawTypeUtil.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/util/RawTypeUtil.java @@ -33,11 +33,12 @@ public class RawTypeUtil { public static Item getParsedItem(ID itemDefinition, List values, QName elementQName, PrismContainerDefinition containerDef) throws SchemaException{ - Item subItem = null; + Item subItem; List parsedValues = new ArrayList<>(); - for (RawType rawValue : values){ + for (RawType rawValue : values) { if (itemDefinition == null && containerDef != null){ + //noinspection unchecked itemDefinition = (ID) ((PrismContextImpl) containerDef.getPrismContext()).getPrismUnmarshaller().locateItemDefinition(containerDef, elementQName, rawValue.getXnode()); } IV parsed = rawValue.getParsedValue(itemDefinition, elementQName); diff --git a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/path/ItemPathTest.java b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/path/ItemPathTest.java index 8d3807edd05..74011d6e50e 100644 --- a/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/path/ItemPathTest.java +++ b/infra/prism-impl/src/test/java/com/evolveum/midpoint/prism/path/ItemPathTest.java @@ -42,7 +42,7 @@ import java.io.IOException; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -228,7 +228,7 @@ public void strangeCharsTest() throws IOException { try (FileInputStream stream = new FileInputStream(file)) { FileChannel fc = stream.getChannel(); MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); - xpathStr = Charset.forName("UTF-8").decode(bb).toString(); + xpathStr = StandardCharsets.UTF_8.decode(bb).toString(); } ItemPathHolder xpath = ItemPathHolder.createForTesting(xpathStr); diff --git a/infra/schema-pure-jaxb/src/compile/resources/catalog.xml b/infra/schema-pure-jaxb/src/compile/resources/catalog.xml index 57f33489176..9460586513d 100644 --- a/infra/schema-pure-jaxb/src/compile/resources/catalog.xml +++ b/infra/schema-pure-jaxb/src/compile/resources/catalog.xml @@ -32,6 +32,7 @@ Used when building via xjc. + diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java index b048401121c..97204db72e2 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/ResourceShadowDiscriminator.java @@ -83,6 +83,10 @@ public ResourceShadowDiscriminator(ShadowDiscriminatorType accRefType, String de setKind(kind); } + public ResourceShadowDiscriminator(String resourceOid) { + this.resourceOid = resourceOid; + } + public ResourceShadowDiscriminator(String resourceOid, QName objectClass) { this.resourceOid = resourceOid; this.objectClass = objectClass; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java index 35bcdaf88d7..cffd3c8f5d1 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/ExpressionConstants.java @@ -123,4 +123,6 @@ public class ExpressionConstants { public static final QName VAR_POLICY_RULE = new QName(SchemaConstants.NS_C, "policyRule"); public static final QName VAR_POLICY_ACTION = new QName(SchemaConstants.NS_C, "policyAction"); public static final QName VAR_LOGIN_MODE = new QName(SchemaConstants.NS_C, "loginMode"); + + public static final QName VAR_MESSAGE = new QName(SchemaConstants.NS_C, "message"); } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/MidPointConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/MidPointConstants.java index cb7b67563e0..da3b5f5805b 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/MidPointConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/MidPointConstants.java @@ -25,6 +25,7 @@ public class MidPointConstants { public static final String NS_MIDPOINT_PUBLIC_PREFIX = "http://midpoint.evolveum.com/xml/ns/public"; public static final String NS_MIDPOINT_TEST_PREFIX = "http://midpoint.evolveum.com/xml/ns/test"; + public static final String EXPRESSION_LANGUAGE_URL_BASE = NS_MIDPOINT_PUBLIC_PREFIX + "/expression/language#"; public static final String NS_RA = NS_MIDPOINT_PUBLIC_PREFIX+"/resource/annotation-3"; public static final String PREFIX_NS_RA = "ra"; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java index 8531101d7ac..9194c5fb1d9 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java @@ -250,6 +250,8 @@ public abstract class SchemaConstants { public static final String NS_PROVISIONING_CHANNEL = NS_PROVISIONING + "/channels-3"; public static final QName CHANGE_CHANNEL_LIVE_SYNC = new QName(NS_PROVISIONING_CHANNEL, "liveSync"); public static final String CHANGE_CHANNEL_LIVE_SYNC_URI = QNameUtil.qNameToUri(CHANGE_CHANNEL_LIVE_SYNC); + public static final QName CHANGE_CHANNEL_ASYNC_UPDATE = new QName(NS_PROVISIONING_CHANNEL, "asyncUpdate"); + public static final String CHANGE_CHANNEL_ASYNC_UPDATE_URI = QNameUtil.qNameToUri(CHANGE_CHANNEL_ASYNC_UPDATE); public static final QName CHANGE_CHANNEL_RECON = new QName(NS_PROVISIONING_CHANNEL, "reconciliation"); public static final String CHANGE_CHANNEL_RECON_URI = QNameUtil.qNameToUri(CHANGE_CHANNEL_RECON); public static final QName CHANGE_CHANNEL_RECOMPUTE = new QName(NS_PROVISIONING_CHANNEL, "recompute"); @@ -441,6 +443,8 @@ public abstract class SchemaConstants { CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_ELEMENT_LOCAL_NAME); public static final String ACCOUNT_OBJECT_CLASS_LOCAL_NAME = "AccountObjectClass"; public static final String GROUP_OBJECT_CLASS_LOCAL_NAME = "GroupObjectClass"; + public static final ItemName RI_ACCOUNT_OBJECT_CLASS = new ItemName(MidPointConstants.NS_RI, ACCOUNT_OBJECT_CLASS_LOCAL_NAME); + public static final ItemName RI_GROUP_OBJECT_CLASS = new ItemName(MidPointConstants.NS_RI, GROUP_OBJECT_CLASS_LOCAL_NAME); public static final String UCF_FRAMEWORK_URI_BUILTIN = "http://midpoint.evolveum.com/xml/ns/public/connector/builtin-1"; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java index a87b3c7fdea..170ba2ae4e1 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/MiscSchemaUtil.java @@ -375,14 +375,14 @@ public static void reduceSearchResult(List if (results == null || results.isEmpty()) { return; } - Map> map = new HashMap<>(); + Set oidsSeen = new HashSet<>(); Iterator> iterator = results.iterator(); while (iterator.hasNext()) { PrismObject prismObject = iterator.next(); - if (map.containsKey(prismObject.getOid())) { + if (oidsSeen.contains(prismObject.getOid())) { iterator.remove(); } else { - map.put(prismObject.getOid(), prismObject); + oidsSeen.add(prismObject.getOid()); } } } 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 d5157468a50..238a1c0d067 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 @@ -571,6 +571,11 @@ public static ObjectType toObjectable(PrismObject object) { return object != null ? (ObjectType) object.asObjectable() : null; } + public static PrismObject toPrismObject(T objectable) { + //noinspection unchecked + return objectable != null ? objectable.asPrismObject() : null; + } + public static boolean containsOid(Collection values, @NotNull String oid) { return values.stream().anyMatch(v -> oid.equals(v.getOid())); } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java index 7e34bb3a866..46e0b7a1a79 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/SchemaDebugUtil.java @@ -977,6 +977,15 @@ public static String prettyPrint(OrderConstraintsType constraints) { // } // } + public static Object prettyPrintLazily(Object value) { + return new Object() { + @Override + public String toString() { + return prettyPrint(value); + } + }; + } + public static String prettyPrint(Object value) { if (value == null) { return "null"; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java index b22c9725274..eeccd71d91f 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java @@ -502,11 +502,19 @@ public static boolean isDead(ShadowType shadow) { Boolean dead = shadow.isDead(); return dead != null && dead; } - + + public static boolean isDead(PrismObject shadow) { + return isDead(shadow.asObjectable()); + } + public static boolean isExists(ShadowType shadow) { Boolean exists = shadow.isExists(); return exists == null || exists; } + + public static boolean isExists(PrismObject shadow) { + return isExists(shadow.asObjectable()); + } public static boolean matches(ShadowType shadowType, String resourceOid, ShadowKindType kind, String intent) { if (shadowType == null) { diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/UcfChangeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/UcfChangeUtil.java new file mode 100644 index 00000000000..6f44f950d4a --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/UcfChangeUtil.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.schema.util; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.DeltaFactory; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAttributesType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UcfChangeType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + +import javax.xml.namespace.QName; +import java.util.Map; + +/** + * + */ +@SuppressWarnings("unused") +public class UcfChangeUtil { + + public static UcfChangeType createForNewObject(QName objectClassName, Map attributes, + PrismContext prismContext) throws SchemaException { + ShadowType shadow = new ShadowType(prismContext); + copyAttributes(attributes, shadow.asPrismObject().findOrCreateContainer(ShadowType.F_ATTRIBUTES).getValue(), prismContext); + UcfChangeType change = new UcfChangeType(); + ObjectDelta addDelta = DeltaFactory.Object.createAddDelta(shadow.asPrismObject()); + change.setObjectClass(objectClassName); + change.setObjectDelta(DeltaConvertor.toObjectDeltaType(addDelta)); + return change; + } + + private static void copyAttributes(Map attributes, PrismContainerValue target, PrismContext prismContext) + throws SchemaException { + for (Map.Entry entry : attributes.entrySet()) { + PrismProperty attribute = prismContext.itemFactory().createProperty(entry.getKey()); + attribute.setValue(prismContext.itemFactory().createPropertyValue(entry.getValue())); + target.add(attribute); + } + } + + public static UcfChangeType create(QName objectClassName, Map identifiers, ObjectDeltaType delta, PrismContext prismContext) + throws SchemaException { + UcfChangeType change = new UcfChangeType(); + change.setObjectClass(objectClassName); + change.setIdentifiers(new ShadowAttributesType(prismContext)); + copyAttributes(identifiers, change.getIdentifiers().asPrismContainerValue(), prismContext); + change.setObjectDelta(delta); + return change; + } +} diff --git a/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml b/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml index feeda925b64..fca6dd68d5f 100644 --- a/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml +++ b/infra/schema/src/main/resources/META-INF/schemas-in-this-module.xml @@ -49,6 +49,7 @@ Notes: + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd index f70b6eefae7..f6cfdbb74ac 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-3.xsd @@ -100,6 +100,7 @@ + diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-asynchronous-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-asynchronous-3.xsd new file mode 100644 index 00000000000..8215a3db76c --- /dev/null +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-asynchronous-3.xsd @@ -0,0 +1,357 @@ + + + + + + + + + Parts related to processing of asynchronous updates (JMS, AMQP, REST, etc). + + + + + + + + + + + + + Sources of asynchronous updates. Currently, only one source is supported; in the future we might allow more + of them. + + + 4.0 + + + + + + + AMQP 0-9-1 sources. + + + + + + + Custom sources. + + + + + + + + + + + Source of asynchronous updates. + + + 4.0 + + + + + + + Name of the source. Usually a short string. + + + + + + + More detailed description of the source. + + + + + + + Java class name implementing AsyncUpdateSource interface. Usually it does not need to be specified, + as it is derived from the source element type. + + + + + + + + + + + AMQP client configuration + + + 4.0 + + + + + + + + + Connection URI. + + + + + + + User name used to authenticate to AMQP server. + + + + + + + Password used to authenticate to AMQP server. + + + + + + + AMQP virtual host; the default value is "/". + + + + + + + Name of the queue to receive messages from. + + + + + + + + + + + + + Custom message source provided by a Java class. + + + 4.0 + + + + + + + + + + + + + + + Representation of an async update message. + + + 4.0 + + + + + + + Name of the async update source through which the message came. + + + + + + + + + + + Async update carrying any data. Useful e.g. for testing. + + + 4.0 + + + + + + + + + The data. + + + true + + + + + + + + + + + + + Representation AMQP 0-9-1 message. + + + 4.0 + + + + + + + + + Message attributes. + + + + + + + Message body. For AMQP 0-9-1 this is always a binary value. + + + + + + + + + + + + + Representation AMQP 0-9-1 message attributes. + + + 4.0 + + + + + + + + + + + + + + + + + + + + + Message attributes (key-value pairs), other than the standard ones. + + + + + + + + + + Message property (key-value pair). + + + 4.0 + + + + + + + Name of the property. + + + + + + + Value of the property. + + + + + + + + + + + + + Delivery mode. Not all modes are applicable to all message types (JMS, AMQP 0-9-1, AMQP 1.0, etc). + + + + + + + + + + Persistent delivery mode. + + + + + + + + + + Non-persistent delivery mode. + + + + + + + + + + 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 cd80880c4d7..d425f97767c 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 @@ -38,6 +38,7 @@ + @@ -10743,6 +10744,48 @@ + + + + Description of the change in the external resource at the UCF level i.e. corresponding to Change object. + + + + + + + Object class. Should be provided. + + + + + + + Identifiers of the object being changed. Should be provided if not present in object or object delta. + + + + + + + Delta describing the changes made to the shadow. E.g. if the change in the resource was + to add new account, delta will contain ADD modification with the object specified. + It may be null. If the object delta is null, the current object has to be specified. + + + + + + + Current state of the object. Either it or the delta has to be specified. + + + + + + + + @@ -11096,7 +11139,7 @@ - + The reference to a type definition for this object. The reference should point @@ -11105,6 +11148,9 @@ The attributes contained in the "attributes" element below are expected to comply with the type definition specified in this element (with addition of auxiliary object class definitions). + + Formally, minOccurs is 0 here but unless a reasonable default is provided, the value + must be present. ShadowType.objectClass @@ -14258,8 +14304,192 @@ + + + +

+ Specifies restrictions for execution of expressions and scripts. This applies + to expressions in archetyped objects. + Please note that no expression execution restriction means that all evaluators + and scripting langages are allowed and there are no restrictions for evaluation + of expressions. +

+

+ Implementation note: implementation of those restriction in midPoint 4.0 is very + limited. There is no guarantee that this will work for archetypes at all. + The only supported option in 4.0 is application of the restricitons to reports + (ReportType) and only if it is configured in global system configuration. +

+
+ + 4.0 + ArchetypePolicyType.expressionExecutionRestriction + +
+
+ + + + + Specifies restrictions for execution of expressions and scripts. + Please note that no expression execution restriction means that all evaluators + and scripting langages are allowed and there are no restrictions for evaluation + of expressions. + + + + 4.0 + + + + + + + Strategy to evaluate allowed expression evaluators. + + + ExpressionExecutionRestrictionType.expressionEvaluatorStrategy + + + + + + + List of expression evaluators that are allowed. It is only applied if evaluator strategy is + set to manual. + Empty lists means that no evaluators are allowed. + + + ExpressionExecutionRestrictionType.allowedEvaluator + + + + + + + Strategy to evaluate allowed script languages. + + + ExpressionExecutionRestrictionType.expressionEvaluatorStrategy + + + + + + + List of script languages that are allowed. It is only applied if script language strategy is + set to manual. + Empty lists means that no script languages are allowed. + + + ExpressionExecutionRestrictionType.allowedScriptLanguage + + + + + + + Strategy to evaluate allowed classes that script evaluators are allowed to access. + + + ExpressionExecutionRestrictionType.scriptClassesStrategy + + + + + + + List of script classes that are allowed. It is only applied if script classes strategy is + set to manual. + Empty lists means that no script classes are allowed. + + + ExpressionExecutionRestrictionType.allowedScriptClasses + + + + + + + + + + + + TODO + + + + 4.0 + + + + + + +

+ Nothing is allowed. + E.g. no expression evaluators or scripting languages are allowed. + However, other definitions can still allow this. +

+
+ + + +
+
+ + + +

+ Only "safe" options are allowed. This is similar to "automatic" protection mode. + E.g. only those expression evaluators or scripting languages that are considered + "safe" are allowed. The definition of safe is quite vague here. Anything that has + low risk of being abused for unintended action is considered "safe". However, this + is still expression evaluation and some evaluators and scripting languages may be + Turing-complete. The behaviour also depeds on midPoint configuration and customization. + Therefore it is very hard to safeguard execution without completely sandboxing it. + Appropriate care should be applied when using this option. It may be useful for + a common case. But in some cases it may be needed to avoid this option and set up + the restrictions manually. +

+
+ + + +
+
+ + + +

+ Manual whitelist should be apllied. + E.g. only those evaluators or scripting languages that are explicitly enumerated + are allowed. +

+
+ + + +
+
+ + + +

+ Everything is allowed. + E.g. all expression evaluators or scripting languages are allowed. +

+
+ + + +
+
+
+
@@ -24234,3 +24464,4 @@ + diff --git a/infra/schema/src/main/resources/xml/ns/public/resource/capabilities-3.xsd b/infra/schema/src/main/resources/xml/ns/public/resource/capabilities-3.xsd index ea7ab029782..9695ffc214e 100644 --- a/infra/schema/src/main/resources/xml/ns/public/resource/capabilities-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/resource/capabilities-3.xsd @@ -329,7 +329,21 @@ - + + + + Describes capability to process asynchronous updates. Note that this is currently supported only + by the built-in AsyncUpdateConnector. + + + + + + + + + + diff --git a/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/TestUtil.java b/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/TestUtil.java index 4fdb56b720d..08a7a3a597b 100644 --- a/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/TestUtil.java +++ b/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/TestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2017 Evolveum + * Copyright (c) 2010-2019 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,6 +52,7 @@ import java.util.GregorianCalendar; import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -100,6 +101,8 @@ public class TestUtil { private static DatatypeFactory datatypeFactory = null; private static final Trace LOGGER = TraceManager.getTrace(TestUtil.class); + + private static final Random RND = new Random(); public static void assertPropertyValueSetEquals(Collection> actual, T... expected) { Set set = new HashSet<>(); @@ -666,4 +669,43 @@ private static void assertPermission(File f, Set permissio private static void assertNoPermission(File f, Set permissions, PosixFilePermission permission) { assertFalse(permissions.contains(permission), f.getPath() + ": unexpected permission "+permission); } + + public static ParallelTestThread[] multithread(final String TEST_NAME, MultithreadRunner lambda, int numberOfThreads, Integer randomStartDelayRange) { + ParallelTestThread[] threads = new ParallelTestThread[numberOfThreads]; + for (int i = 0; i < numberOfThreads; i++) { + threads[i] = new ParallelTestThread(i, + (ii) -> { + randomDelay(randomStartDelayRange); + LOGGER.info("{} starting", Thread.currentThread().getName()); + lambda.run(ii); + }); + threads[i].setName("Thread " + (i+1) + " of " + numberOfThreads); + threads[i].start(); + } + return threads; + } + + public static void randomDelay(Integer range) { + if (range == null) { + return; + } + try { + Thread.sleep(RND.nextInt(range)); + } catch (InterruptedException e) { + // Nothing to do, really + } + } + + public static void waitForThreads(ParallelTestThread[] threads, long timeout) throws InterruptedException { + for (int i = 0; i < threads.length; i++) { + if (threads[i].isAlive()) { + System.out.println("Waiting for " + threads[i]); + threads[i].join(timeout); + } + Throwable threadException = threads[i].getException(); + if (threadException != null) { + throw new AssertionError("Test thread "+i+" failed: "+threadException.getMessage(), threadException); + } + } + } } diff --git a/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java b/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java index a7d84ef56db..0792a8de06f 100644 --- a/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java +++ b/infra/util/src/main/java/com/evolveum/midpoint/util/QNameUtil.java @@ -285,7 +285,7 @@ public static QName resolveNs(QName a, Collection col){ return found; } - public static boolean matchAny(QName nameToFind, Collection names) { + public static boolean matchAny(QName nameToFind, Collection names) { // we no longer use resolveNs any more here, as the 'names' can contain duplicate qnames (resolveNs would complain on it) if (names == null) { return false; diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java index 1c3630440f1..77f6de41f5b 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelService.java @@ -51,12 +51,7 @@ import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.CompareResultType; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ImportOptionsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorHostType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; /** @@ -726,6 +721,10 @@ Collection> me String mergeConfigurationName, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ConfigurationException, ObjectAlreadyExistsException, ExpressionEvaluationException, CommunicationException, PolicyViolationException, SecurityViolationException; + void notifyChange(ResourceObjectShadowChangeDescriptionType changeDescription, Task task, OperationResult parentResult) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ObjectNotFoundException, ObjectAlreadyExistsException, ExpressionEvaluationException, PolicyViolationException; + @NotNull PrismContext getPrismContext(); } diff --git a/model/model-client/src/compile/resources/jax-ws-catalog.xml b/model/model-client/src/compile/resources/jax-ws-catalog.xml index 638d464d4db..0cfb653e04e 100644 --- a/model/model-client/src/compile/resources/jax-ws-catalog.xml +++ b/model/model-client/src/compile/resources/jax-ws-catalog.xml @@ -32,6 +32,7 @@ Used when building via xjc. + diff --git a/model/model-client/src/main/resources/META-INF/jax-ws-catalog.xml b/model/model-client/src/main/resources/META-INF/jax-ws-catalog.xml index 847e7998038..bf90e1476cd 100644 --- a/model/model-client/src/main/resources/META-INF/jax-ws-catalog.xml +++ b/model/model-client/src/main/resources/META-INF/jax-ws-catalog.xml @@ -32,6 +32,7 @@ Used at run time by SOAP clients. + diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java index c80f8a2ddf3..ae8442e18e0 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/functions/BasicExpressionFunctions.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -82,7 +83,7 @@ public class BasicExpressionFunctions { public static final String NAME_SEPARATOR = " "; - private final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + private final Charset UTF8_CHARSET = StandardCharsets.UTF_8; public static final Trace LOGGER = TraceManager.getTrace(BasicExpressionFunctions.class); diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/AbstractCachingScriptEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/AbstractCachingScriptEvaluator.java new file mode 100644 index 00000000000..6b10914a348 --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/AbstractCachingScriptEvaluator.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.model.common.expression.script; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; +import com.evolveum.midpoint.model.common.expression.script.ScriptEvaluator; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionUtil; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.xml.XsdTypeMapper; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionSyntaxException; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.MidPointConstants; +import com.evolveum.midpoint.schema.internals.InternalCounters; +import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionReturnTypeType; + +/** + * Expression evaluator that is using javax.script (JSR-223) engine. + * + * @author Radovan Semancik + * @param compiled code + * + */ +public abstract class AbstractCachingScriptEvaluator implements ScriptEvaluator { + + private static final Trace LOGGER = TraceManager.getTrace(AbstractCachingScriptEvaluator.class); + + private final PrismContext prismContext; + private final Protector protector; + private final LocalizationService localizationService; + + private final Map scriptCache; + + public AbstractCachingScriptEvaluator(PrismContext prismContext, Protector protector, + LocalizationService localizationService) { + this.prismContext = prismContext; + this.protector = protector; + this.scriptCache = new ConcurrentHashMap<>(); + this.localizationService = localizationService; + } + + public PrismContext getPrismContext() { + return prismContext; + } + + public Protector getProtector() { + return protector; + } + + public LocalizationService getLocalizationService() { + return localizationService; + } + + public Map getScriptCache() { + return scriptCache; + } + + @Override + public List evaluate(ScriptExpressionEvaluatorType expressionType, + ExpressionVariables variables, ItemDefinition outputDefinition, + Function additionalConvertor, + ScriptExpressionReturnTypeType suggestedReturnType, + ObjectResolver objectResolver, Collection functions, + String contextDescription, Task task, OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, ExpressionSyntaxException, CommunicationException, ConfigurationException, SecurityViolationException { + + String codeString = expressionType.getCode(); + if (codeString == null) { + throw new ExpressionEvaluationException("No script code in " + contextDescription); + } + + boolean allowEmptyValues = false; + if (expressionType.isAllowEmptyValues() != null) { + allowEmptyValues = expressionType.isAllowEmptyValues(); + } + + C compiledScript = getCompiledScript(codeString, contextDescription); + + Object evalRawResult; + try { + InternalMonitor.recordCount(InternalCounters.SCRIPT_EXECUTION_COUNT); + evalRawResult = evaluateScript(compiledScript, variables, objectResolver, functions, contextDescription, task, result); + } catch (Throwable e) { + throw localizationService.translate( + new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, + e, ExceptionUtil.getUserFriendlyMessage(e))); + } + + if (outputDefinition == null) { + // No outputDefinition means "void" return type, we can return right now + return null; + } + + QName xsdReturnType = outputDefinition.getTypeName(); + + Class javaReturnType = XsdTypeMapper.toJavaType(xsdReturnType); + if (javaReturnType == null) { + javaReturnType = prismContext.getSchemaRegistry().getCompileTimeClass(xsdReturnType); + } + + if (javaReturnType == null && (outputDefinition instanceof PrismContainerDefinition)) { + // This is the case when we need a container, but we do not have compile-time class for that + // E.g. this may be container in object extension (MID-5080) + javaReturnType = (Class) PrismContainerValue.class; + } + + if (javaReturnType == null) { + // TODO quick and dirty hack - because this could be because of enums defined in schema extension (MID-2399) + // ...and enums (xsd:simpleType) are not parsed into ComplexTypeDefinitions + javaReturnType = (Class) String.class; + } + LOGGER.trace("expected return type: XSD={}, Java={}", xsdReturnType, javaReturnType); + + List pvals = new ArrayList<>(); + + // TODO: what about PrismContainer and + // PrismReference? Shouldn't they be processed in the same way as + // PrismProperty? + if (evalRawResult instanceof Collection) { + for (Object evalRawResultElement : (Collection)evalRawResult) { + T evalResult = convertScalarResult(javaReturnType, additionalConvertor, evalRawResultElement, contextDescription); + if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) { + pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext)); + } + } + } else if (evalRawResult instanceof PrismProperty) { + pvals.addAll((Collection) PrismValueCollectionsUtil.cloneCollection(((PrismProperty)evalRawResult).getValues())); + } else { + T evalResult = convertScalarResult(javaReturnType, additionalConvertor, evalRawResult, contextDescription); + if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) { + pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext)); + } + } + + return pvals; + } + + protected C getCompiledScript(String codeString, String contextDescription) throws ExpressionEvaluationException { + C compiledScript = scriptCache.get(codeString); + if (compiledScript != null) { + return compiledScript; + } + InternalMonitor.recordCount(InternalCounters.SCRIPT_COMPILE_COUNT); + try { + compiledScript = compileScript(codeString, contextDescription); + } catch (Exception e) { + throw new ExpressionEvaluationException(e.getMessage() + " while compiling " + contextDescription, e); + } + scriptCache.put(codeString, compiledScript); + return compiledScript; + } + + protected abstract C compileScript(String codeString, String contextDescription) throws Exception; + + protected abstract Object evaluateScript(C compiledScript, ExpressionVariables variables, + ObjectResolver objectResolver, Collection functions, String contextDescription, + Task task, OperationResult result) + throws Exception; + + protected Map getVariablesMap(ExpressionVariables variables, ObjectResolver objectResolver, + Collection functions, String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + return ScriptExpressionUtil.prepareScriptVariables(variables, objectResolver, functions, contextDescription, getPrismContext(), task, result); + } + + private T convertScalarResult(Class expectedType, Function additionalConvertor, Object rawValue, String contextDescription) throws ExpressionEvaluationException { + try { + T convertedValue = ExpressionUtil.convertValue(expectedType, additionalConvertor, rawValue, protector, prismContext); + return convertedValue; + } catch (IllegalArgumentException e) { + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); + } + } + +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/GroovyScriptEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/GroovyScriptEvaluator.java new file mode 100644 index 00000000000..72357d49dcd --- /dev/null +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/GroovyScriptEvaluator.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.model.common.expression.script; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import javax.xml.namespace.QName; + +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.runtime.InvokerHelper; + +import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; +import com.evolveum.midpoint.model.common.expression.script.ScriptEvaluator; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionUtil; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.xml.XsdTypeMapper; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionSyntaxException; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.MidPointConstants; +import com.evolveum.midpoint.schema.internals.InternalCounters; +import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionReturnTypeType; + +import groovy.lang.Binding; +import groovy.lang.GroovyClassLoader; +import groovy.lang.Script; + +/** + * Expression evaluator that is using javax.script (JSR-223) engine. + * + * @author Radovan Semancik + * + */ +public class GroovyScriptEvaluator extends AbstractCachingScriptEvaluator { + + public static final String LANGUAGE_NAME = "Groovy"; + public static final String LANGUAGE_URL = MidPointConstants.EXPRESSION_LANGUAGE_URL_BASE + LANGUAGE_NAME; + + private static final Trace LOGGER = TraceManager.getTrace(GroovyScriptEvaluator.class); + + private GroovyClassLoader groovyLoader; + + public GroovyScriptEvaluator(PrismContext prismContext, Protector protector, + LocalizationService localizationService) { + super(prismContext, protector, localizationService); + + CompilerConfiguration compilerConfiguration = new CompilerConfiguration(CompilerConfiguration.DEFAULT); + groovyLoader = new GroovyClassLoader(GroovyScriptEvaluator.class.getClassLoader(), compilerConfiguration); + } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#getLanguageName() + */ + @Override + public String getLanguageName() { + return LANGUAGE_NAME; + } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#getLanguageUrl() + */ + @Override + public String getLanguageUrl() { + return LANGUAGE_URL; + } + + + @Override + protected Class compileScript(String codeString, String contextDescription) + throws ExpressionEvaluationException { + return groovyLoader.parseClass(codeString, contextDescription); + } + + + @Override + protected Object evaluateScript(Class compiledScriptClass, ExpressionVariables variables, + ObjectResolver objectResolver, Collection functions, String contextDescription, Task task, + OperationResult result) throws Exception { + + if (!Script.class.isAssignableFrom(compiledScriptClass)) { + throw new ExpressionEvaluationException("Expected groovy script class, but got "+compiledScriptClass); + } + + Binding binding = new Binding(getVariablesMap(variables, objectResolver, functions, contextDescription, task, result)); + + Script scriptObject = InvokerHelper.createScript(compiledScriptClass, binding); + + return scriptObject.run(); + } + +} diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionUtil.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionUtil.java index 61c405ca093..b5a527e7b5b 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionUtil.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/ScriptExpressionUtil.java @@ -28,6 +28,7 @@ import com.evolveum.midpoint.repo.common.expression.ExpressionSyntaxException; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.CommunicationException; @@ -65,6 +66,11 @@ public static Map prepareScriptVariables(ExpressionVariables vari scriptVariables.put(variableName, variableValue); } } + + String prismContextName = ExpressionConstants.VAR_PRISM_CONTEXT.getLocalPart(); + if (!scriptVariables.containsKey(prismContextName)) { + scriptVariables.put(prismContextName, prismContext); + } return scriptVariables; } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java index 5cea495d49d..8c7f85f684b 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java @@ -32,6 +32,7 @@ import com.evolveum.midpoint.common.LocalizationService; import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; +import com.evolveum.midpoint.model.common.expression.script.AbstractCachingScriptEvaluator; import com.evolveum.midpoint.model.common.expression.script.ScriptEvaluator; import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionUtil; import com.evolveum.midpoint.prism.*; @@ -64,113 +65,46 @@ * @author Radovan Semancik * */ -public class Jsr223ScriptEvaluator implements ScriptEvaluator { - - private static final String LANGUAGE_URL_BASE = MidPointConstants.NS_MIDPOINT_PUBLIC_PREFIX + "/expression/language#"; +public class Jsr223ScriptEvaluator extends AbstractCachingScriptEvaluator { private static final Trace LOGGER = TraceManager.getTrace(Jsr223ScriptEvaluator.class); private final ScriptEngine scriptEngine; - private final PrismContext prismContext; - private final Protector protector; - private final LocalizationService localizationService; - - private final Map scriptCache; public Jsr223ScriptEvaluator(String engineName, PrismContext prismContext, Protector protector, LocalizationService localizationService) { + super(prismContext, protector, localizationService); + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); scriptEngine = scriptEngineManager.getEngineByName(engineName); if (scriptEngine == null) { throw new SystemException("The JSR-223 scripting engine for '"+engineName+"' was not found"); } - this.prismContext = prismContext; - this.protector = protector; - this.scriptCache = new ConcurrentHashMap<>(); - this.localizationService = localizationService; } + @Override - public List evaluate(ScriptExpressionEvaluatorType expressionType, - ExpressionVariables variables, ItemDefinition outputDefinition, - Function additionalConvertor, - ScriptExpressionReturnTypeType suggestedReturnType, - ObjectResolver objectResolver, Collection functions, - String contextDescription, Task task, OperationResult result) throws ExpressionEvaluationException, - ObjectNotFoundException, ExpressionSyntaxException, CommunicationException, ConfigurationException, SecurityViolationException { - - Bindings bindings = convertToBindings(variables, objectResolver, functions, contextDescription, task, result); - - String codeString = expressionType.getCode(); - if (codeString == null) { - throw new ExpressionEvaluationException("No script code in " + contextDescription); - } - - boolean allowEmptyValues = false; - if (expressionType.isAllowEmptyValues() != null) { - allowEmptyValues = expressionType.isAllowEmptyValues(); - } - - CompiledScript compiledScript = createCompiledScript(codeString, contextDescription); - - Object evalRawResult; - try { - InternalMonitor.recordCount(InternalCounters.SCRIPT_EXECUTION_COUNT); - evalRawResult = compiledScript.eval(bindings); - } catch (Throwable e) { - throw localizationService.translate( - new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, - e, ExceptionUtil.getUserFriendlyMessage(e))); - } - - if (outputDefinition == null) { - // No outputDefinition means "void" return type, we can return right now - return null; - } - - QName xsdReturnType = outputDefinition.getTypeName(); - - Class javaReturnType = XsdTypeMapper.toJavaType(xsdReturnType); - if (javaReturnType == null) { - javaReturnType = prismContext.getSchemaRegistry().getCompileTimeClass(xsdReturnType); - } + protected CompiledScript compileScript(String codeString, String contextDescription) throws Exception { + return ((Compilable)scriptEngine).compile(codeString); + } + + @Override + protected Object evaluateScript(CompiledScript compiledScript, ExpressionVariables variables, + ObjectResolver objectResolver, Collection functions, String contextDescription, + Task task, OperationResult result) throws Exception { - if (javaReturnType == null && (outputDefinition instanceof PrismContainerDefinition)) { - // This is the case when we need a container, but we do not have compile-time class for that - // E.g. this may be container in object extension (MID-5080) - javaReturnType = (Class) PrismContainerValue.class; - } - - if (javaReturnType == null) { - // TODO quick and dirty hack - because this could be because of enums defined in schema extension (MID-2399) - // ...and enums (xsd:simpleType) are not parsed into ComplexTypeDefinitions - javaReturnType = (Class) String.class; - } - LOGGER.trace("expected return type: XSD={}, Java={}", xsdReturnType, javaReturnType); - - List pvals = new ArrayList<>(); - - // TODO: what about PrismContainer and - // PrismReference? Shouldn't they be processed in the same way as - // PrismProperty? - if (evalRawResult instanceof Collection) { - for (Object evalRawResultElement : (Collection)evalRawResult) { - T evalResult = convertScalarResult(javaReturnType, additionalConvertor, evalRawResultElement, contextDescription); - if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) { - pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext)); - } - } - } else if (evalRawResult instanceof PrismProperty) { - pvals.addAll((Collection) PrismValueCollectionsUtil.cloneCollection(((PrismProperty)evalRawResult).getValues())); - } else { - T evalResult = convertScalarResult(javaReturnType, additionalConvertor, evalRawResult, contextDescription); - if (allowEmptyValues || !ExpressionUtil.isEmpty(evalResult)) { - pvals.add((V) ExpressionUtil.convertToPrismValue(evalResult, outputDefinition, contextDescription, prismContext)); - } - } - - return pvals; + Bindings bindings = convertToBindings(variables, objectResolver, functions, contextDescription, task, result); + return compiledScript.eval(bindings); } + + private Bindings convertToBindings(ExpressionVariables variables, ObjectResolver objectResolver, + Collection functions, String contextDescription, Task task, OperationResult result) + throws ExpressionSyntaxException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + Bindings bindings = scriptEngine.createBindings(); + bindings.putAll(getVariablesMap(variables, objectResolver, functions, contextDescription, task, result)); + return bindings; + } + public Object evaluateReportScript(String codeString, ExpressionVariables variables, ObjectResolver objectResolver, Collection functions, String contextDescription, OperationResult result) throws ExpressionEvaluationException, @@ -188,7 +122,7 @@ public Object evaluateReportScript(String codeString, ExpressionVariables va // allowEmptyValues = expressionType.isAllowEmptyValues(); // } - CompiledScript compiledScript = createCompiledScript(codeString, contextDescription); + CompiledScript compiledScript = getCompiledScript(codeString, contextDescription); Object evalRawResult; try { @@ -203,37 +137,6 @@ public Object evaluateReportScript(String codeString, ExpressionVariables va return evalRawResult; } - private CompiledScript createCompiledScript(String codeString, String contextDescription) throws ExpressionEvaluationException { - CompiledScript compiledScript = scriptCache.get(codeString); - if (compiledScript != null) { - return compiledScript; - } - try { - InternalMonitor.recordCount(InternalCounters.SCRIPT_COMPILE_COUNT); - compiledScript = ((Compilable)scriptEngine).compile(codeString); - } catch (ScriptException e) { - throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); - } - scriptCache.put(codeString, compiledScript); - return compiledScript; - } - - private T convertScalarResult(Class expectedType, Function additionalConvertor, Object rawValue, String contextDescription) throws ExpressionEvaluationException { - try { - T convertedValue = ExpressionUtil.convertValue(expectedType, additionalConvertor, rawValue, protector, prismContext); - return convertedValue; - } catch (IllegalArgumentException e) { - throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); - } - } - - private Bindings convertToBindings(ExpressionVariables variables, ObjectResolver objectResolver, - Collection functions, - String contextDescription, Task task, OperationResult result) throws ExpressionSyntaxException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - Bindings bindings = scriptEngine.createBindings(); - bindings.putAll(ScriptExpressionUtil.prepareScriptVariables(variables, objectResolver, functions, contextDescription, prismContext, task, result)); - return bindings; - } /* (non-Javadoc) * @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#getLanguageName() @@ -248,7 +151,7 @@ public String getLanguageName() { */ @Override public String getLanguageUrl() { - return LANGUAGE_URL_BASE + getLanguageName(); + return MidPointConstants.EXPRESSION_LANGUAGE_URL_BASE + getLanguageName(); } } diff --git a/model/model-common/src/main/resources/ctx-model-common.xml b/model/model-common/src/main/resources/ctx-model-common.xml index b5140870637..8f735619097 100644 --- a/model/model-common/src/main/resources/ctx-model-common.xml +++ b/model/model-common/src/main/resources/ctx-model-common.xml @@ -26,4 +26,8 @@ + + diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java index 468e50669f7..9be1c68458f 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/AbstractScriptTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2018 Evolveum + * Copyright (c) 2010-2019 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,6 +50,7 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.test.util.ParallelTestThread; import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.PrettyPrinter; @@ -133,6 +134,38 @@ public void testExpressionStringVariables() throws Exception { ), "FOOBAR"); } + + /** + * Make sure that the script engine can work well in parallel and that + * individual script runs do not influence each other. + */ + @Test + public void testExpressionStringVariablesParallel() throws Exception { + final String TEST_NAME = "testExpressionStringVariablesParallel"; + + // WHEN + + ParallelTestThread[] threads = TestUtil.multithread(TEST_NAME, + (threadIndex) -> { + + String foo = "FOO"+threadIndex; + String bar = "BAR"+threadIndex; + + evaluateAndAssertStringScalarExpresssion( + "expression-string-variables.xml", + "testExpressionStringVariablesParallel-"+threadIndex, + ExpressionVariables.create( + new QName(NS_X, "foo"), foo, + new QName(NS_Y, "bar"), bar + ), + foo + bar); + + }, 10, 10); + + // THEN + TestUtil.waitForThreads(threads, 60000L); + + } @Test diff --git a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestGroovyExpressions.java b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestGroovyExpressions.java index 36a45f18b4a..4613cfc0003 100644 --- a/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestGroovyExpressions.java +++ b/model/model-common/src/test/java/com/evolveum/midpoint/model/common/expression/script/TestGroovyExpressions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2019 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ public class TestGroovyExpressions extends AbstractScriptTest { */ @Override protected ScriptEvaluator createEvaluator(PrismContext prismContext, Protector protector) { - return new Jsr223ScriptEvaluator("groovy", prismContext, protector, localizationService); + return new GroovyScriptEvaluator(prismContext, protector, localizationService); } /* (non-Javadoc) diff --git a/model/model-impl/pom.xml b/model/model-impl/pom.xml index 2b48ec0ba27..776e09909c8 100644 --- a/model/model-impl/pom.xml +++ b/model/model-impl/pom.xml @@ -264,7 +264,15 @@ com.fasterxml.jackson.core jackson-databind - + + org.springframework.boot + spring-boot-starter-amqp + + + com.rabbitmq + amqp-client + ${rabbit-amqp-client.version} + diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java index f9ef107013a..e624ea966b1 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelCrudService.java @@ -15,37 +15,17 @@ */ package com.evolveum.midpoint.model.impl; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.xml.namespace.QName; - +import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.model.api.TaskService; -import com.evolveum.midpoint.prism.ConsistencyCheckScope; -import com.evolveum.midpoint.prism.crypto.Protector; - -import com.evolveum.midpoint.prism.delta.*; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.ConsistencyCheckScope; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.query.ObjectPaging; +import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher; -import com.evolveum.midpoint.provisioning.api.GenericConnectorException; -import com.evolveum.midpoint.provisioning.api.ResourceEventDescription; -import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.cache.RepositoryCache; -import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ObjectDeltaOperation; import com.evolveum.midpoint.schema.SelectorOptions; @@ -53,28 +33,26 @@ import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.util.exception.SystemException; +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.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectShadowChangeDescriptionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; -import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +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.List; /** * Simple version of model service exposing CRUD-like operations. This is common facade for webservice and REST services. - * It takes care of all the "details" of externalized obejcts such as applying correct definitions and so on. + * It takes care of all the "details" of externalized objects such as applying correct definitions and so on. + * + * Other methods (not strictly related to CRUD operations) requiring some pre-processing may come here later; if needed + * for both WS and REST services. * * @author Radovan Semancik * @@ -82,32 +60,17 @@ @Component public class ModelCrudService { - String CLASS_NAME_WITH_DOT = ModelCrudService.class.getName() + "."; - String ADD_OBJECT = CLASS_NAME_WITH_DOT + "addObject"; - String MODIFY_OBJECT = CLASS_NAME_WITH_DOT + "modifyObject"; - String DELETE_OBJECT = CLASS_NAME_WITH_DOT + "deleteObject"; - + private String CLASS_NAME_WITH_DOT = ModelCrudService.class.getName() + "."; + private String ADD_OBJECT = CLASS_NAME_WITH_DOT + "addObject"; + private String MODIFY_OBJECT = CLASS_NAME_WITH_DOT + "modifyObject"; + private String DELETE_OBJECT = CLASS_NAME_WITH_DOT + "deleteObject"; private static final Trace LOGGER = TraceManager.getTrace(ModelCrudService.class); - @Autowired(required = true) - ModelService modelService; - - @Autowired - TaskService taskService; - - @Autowired(required = true) - PrismContext prismContext; - - @Autowired(required = true) - @Qualifier("cacheRepositoryService") - RepositoryService repository; - - @Autowired(required = true) - private Protector protector; - - @Autowired(required = true) - private ChangeNotificationDispatcher dispatcher; + @Autowired ModelService modelService; + @Autowired TaskService taskService; + @Autowired PrismContext prismContext; + @Autowired @Qualifier("cacheRepositoryService") RepositoryService repository; public PrismObject getObject(Class clazz, String oid, Collection> options, Task task, OperationResult parentResult) @@ -122,77 +85,6 @@ public List> searchObjects(Class type, return modelService.searchObjects(type, query, options, task, parentResult); } - public void notifyChange(ResourceObjectShadowChangeDescriptionType changeDescription, OperationResult parentResult, Task task) throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ObjectNotFoundException, GenericConnectorException, ObjectAlreadyExistsException, ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException{ - - String oldShadowOid = changeDescription.getOldShadowOid(); - ResourceEventDescription eventDescription = new ResourceEventDescription(); - - PrismObject oldShadow; - LOGGER.trace("resolving old object"); - if (!StringUtils.isEmpty(oldShadowOid)){ - oldShadow = getObject(ShadowType.class, oldShadowOid, SelectorOptions.createCollection(GetOperationOptions.createDoNotDiscovery()), task, parentResult); - eventDescription.setOldShadow(oldShadow); - LOGGER.trace("old object resolved to: {}", oldShadow.debugDump()); - } else{ - LOGGER.trace("Old shadow null"); - } - - PrismObject currentShadow = null; - ShadowType currentShadowType = changeDescription.getCurrentShadow(); - LOGGER.trace("resolving current shadow"); - if (currentShadowType != null){ - prismContext.adopt(currentShadowType); - currentShadow = currentShadowType.asPrismObject(); - LOGGER.trace("current shadow resolved to {}", currentShadow.debugDump()); - } - - eventDescription.setCurrentShadow(currentShadow); - - ObjectDeltaType deltaType = changeDescription.getObjectDelta(); - ObjectDelta delta = null; - - PrismObject shadowToAdd; - if (deltaType != null){ - - delta = prismContext.deltaFactory().object().createEmptyDelta(ShadowType.class, deltaType.getOid(), - ChangeType.toChangeType(deltaType.getChangeType())); - - if (delta.getChangeType() == ChangeType.ADD) { -// LOGGER.trace("determined ADD change "); - if (deltaType.getObjectToAdd() == null){ - LOGGER.trace("No object to add specified. Check your delta. Add delta must contain object to add"); - throw new IllegalArgumentException("No object to add specified. Check your delta. Add delta must contain object to add"); -// return handleTaskResult(task); - } - Object objToAdd = deltaType.getObjectToAdd(); - if (!(objToAdd instanceof ShadowType)){ - LOGGER.trace("Wrong object specified in change description. Expected on the the shadow type, but got " + objToAdd.getClass().getSimpleName()); - throw new IllegalArgumentException("Wrong object specified in change description. Expected on the the shadow type, but got " + objToAdd.getClass().getSimpleName()); -// return handleTaskResult(task); - } - prismContext.adopt((ShadowType)objToAdd); - - shadowToAdd = ((ShadowType) objToAdd).asPrismObject(); - LOGGER.trace("object to add: {}", shadowToAdd.debugDump()); - delta.setObjectToAdd(shadowToAdd); - } else { - Collection modifications = DeltaConvertor.toModifications(deltaType.getItemDelta(), prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class)); - delta.getModifications().addAll(modifications); - } - } - Collection> deltas = new ArrayList<>(); - deltas.add(delta); - ModelImplUtils.encrypt(deltas, protector, null, parentResult); - eventDescription.setDelta(delta); - - eventDescription.setSourceChannel(changeDescription.getChannel()); - - dispatcher.notifyEvent(eventDescription, task, parentResult); - parentResult.computeStatus(); - task.setResult(parentResult); - } - - /** *

* Add new object. @@ -447,82 +339,4 @@ public void modifyObject(Class type, String oid, RepositoryCache.exit(); } } - - public PrismObject findShadowOwner(String accountOid, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException { - return modelService.findShadowOwner(accountOid, task, parentResult); - } - - public List> listResourceObjects(String resourceOid, QName objectClass, - ObjectPaging paging, Task task, OperationResult parentResult) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - return modelService.listResourceObjects(resourceOid, objectClass, paging, task, parentResult); - } - - public void importFromResource(String resourceOid, QName objectClass, Task task, OperationResult parentResult) - throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - modelService.importFromResource(resourceOid, objectClass, task, parentResult); - } - - public OperationResult testResource(String resourceOid, Task task) throws ObjectNotFoundException { - return modelService.testResource(resourceOid, task); - } - - - //TASK AREA - public boolean suspendTask(String taskOid, long waitForStop, Task operationTask, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - return taskService.suspendTask(taskOid, waitForStop, operationTask, parentResult); - } - - public void suspendAndDeleteTask(String taskOid, long waitForStop, boolean alsoSubtasks, Task operationTask, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - taskService.suspendAndDeleteTask(taskOid, waitForStop, alsoSubtasks, operationTask, parentResult); - } - - public void resumeTask(String taskOid, Task operationTask, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - taskService.resumeTask(taskOid, operationTask, parentResult); - } - - public void scheduleTaskNow(String taskOid, Task operationTask, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - taskService.scheduleTaskNow(taskOid, operationTask, parentResult); - } - - @SuppressWarnings("unused") - public PrismObject getTaskByIdentifier(String identifier, Collection> options, Task operationTask, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException, CommunicationException { - return taskService.getTaskByIdentifier(identifier, options, operationTask, parentResult); - } - - public boolean deactivateServiceThreads(long timeToWait, Task operationTask, OperationResult parentResult) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - return taskService.deactivateServiceThreads(timeToWait, operationTask, parentResult); - } - - public void reactivateServiceThreads(Task operationTask, OperationResult parentResult) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - taskService.reactivateServiceThreads(operationTask, parentResult); - } - - @SuppressWarnings("unused") - public boolean getServiceThreadsActivationState() { - return taskService.getServiceThreadsActivationState(); - } - - public void stopSchedulers(Collection nodeIdentifiers, Task operationTask, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - taskService.stopSchedulers(nodeIdentifiers, operationTask, parentResult); - } - - public boolean stopSchedulersAndTasks(Collection nodeIdentifiers, long waitTime, Task operationTask, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - return taskService.stopSchedulersAndTasks(nodeIdentifiers, waitTime, operationTask, parentResult); - } - - public void startSchedulers(Collection nodeIdentifiers, Task operationTask, OperationResult parentResult) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - taskService.startSchedulers(nodeIdentifiers, operationTask, parentResult); - } - - public void synchronizeTasks(Task operationTask, OperationResult parentResult) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException { - taskService.synchronizeTasks(operationTask, parentResult); - } - - @SuppressWarnings("unused") - public List getAllTaskCategories() { - return taskService.getAllTaskCategories(); - } - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java index b66899a8141..27a2c9c3094 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelRestService.java @@ -575,7 +575,7 @@ public Response deleteObject(@PathParam("type") String type, @PathParam("id") St Response response; try { if (clazz.isAssignableFrom(TaskType.class)) { - model.suspendAndDeleteTask(id, WAIT_FOR_TASK_STOP, true, task, parentResult); + taskService.suspendAndDeleteTask(id, WAIT_FOR_TASK_STOP, true, task, parentResult); parentResult.computeStatus(); finishRequest(task); if (parentResult.isSuccess()) { @@ -647,7 +647,7 @@ public Response notifyChange(ResourceObjectShadowChangeDescriptionType changeDes Response response; try { - model.notifyChange(changeDescription, parentResult, task); + modelService.notifyChange(changeDescription, task, parentResult); response = RestServiceUtil.createResponse(Response.Status.OK, parentResult); // return Response.ok().build(); // String oldShadowOid = changeDescription.getOldShadowOid(); @@ -679,7 +679,7 @@ public Response findShadowOwner(@PathParam("oid") String shadowOid, @Context Mes Response response; try { - PrismObject user = model.findShadowOwner(shadowOid, task, parentResult); + PrismObject user = modelService.findShadowOwner(shadowOid, task, parentResult); // response = Response.ok().entity(user).build(); response = RestServiceUtil.createResponse(Response.Status.OK, user, parentResult); } catch (Exception ex) { @@ -769,7 +769,7 @@ public Response importFromResource(@PathParam("resourceOid") String resourceOid, QName objClass = new QName(MidPointConstants.NS_RI, objectClass); Response response; try { - model.importFromResource(resourceOid, objClass, task, parentResult); + modelService.importFromResource(resourceOid, objClass, task, parentResult); response = RestServiceUtil.createResponse(Response.Status.SEE_OTHER, (uriInfo.getBaseUriBuilder().path(this.getClass(), "getObject") .build(ObjectTypes.TASK.getRestType(), task.getOid())), parentResult); // response = Response.seeOther((uriInfo.getBaseUriBuilder().path(this.getClass(), "getObject") @@ -795,7 +795,7 @@ public Response testResource(@PathParam("resourceOid") String resourceOid, @Cont Response response; OperationResult testResult = null; try { - testResult = model.testResource(resourceOid, task); + testResult = modelService.testResource(resourceOid, task); response = RestServiceUtil.createResponse(Response.Status.OK, testResult, parentResult); // response = Response.ok(testResult).build(); } catch (Exception ex) { @@ -819,7 +819,7 @@ public Response suspendTask(@PathParam("oid") String taskOid, @Context MessageCo Response response; try { - model.suspendTask(taskOid, WAIT_FOR_TASK_STOP, task, parentResult); + taskService.suspendTask(taskOid, WAIT_FOR_TASK_STOP, task, parentResult); parentResult.computeStatus(); response = RestServiceUtil.createResponse(Response.Status.NO_CONTENT, task, parentResult); } catch (Exception ex) { @@ -865,7 +865,7 @@ public Response resumeTask(@PathParam("oid") String taskOid, @Context MessageCon Response response; try { - model.resumeTask(taskOid, task, parentResult); + taskService.resumeTask(taskOid, task, parentResult); parentResult.computeStatus(); response = RestServiceUtil.createResponse(Response.Status.ACCEPTED, parentResult); } catch (Exception ex) { @@ -886,7 +886,7 @@ public Response scheduleTaskNow(@PathParam("oid") String taskOid, @Context Messa Response response; try { - model.scheduleTaskNow(taskOid, task, parentResult); + taskService.scheduleTaskNow(taskOid, task, parentResult); parentResult.computeStatus(); response = RestServiceUtil.createResponse(Response.Status.NO_CONTENT, parentResult); } catch (Exception ex) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelWebService.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelWebService.java index ca6ef979338..11bd24b1885 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelWebService.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ModelWebService.java @@ -170,7 +170,7 @@ public void findShadowOwner(String accountOid, Holder userHolder, Hold auditLogin(task); OperationResult operationResult = task.getResult(); try { - PrismObject user = model.findShadowOwner(accountOid, task, operationResult); + PrismObject user = modelService.findShadowOwner(accountOid, task, operationResult); handleOperationResult(operationResult, result); if (user != null) { userHolder.value = user.asObjectable(); @@ -191,7 +191,7 @@ public OperationResultType testResource(String resourceOid) throws FaultMessage Task task = createTaskInstance(TEST_RESOURCE); auditLogin(task); try { - OperationResult testResult = model.testResource(resourceOid, task); + OperationResult testResult = modelService.testResource(resourceOid, task); return handleOperationResult(testResult); } catch (Exception ex) { LoggingUtils.logException(LOGGER, "# MODEL testResource() failed", ex); @@ -403,7 +403,7 @@ public TaskType importFromResource(String resourceOid, QName objectClass) OperationResult operationResult = task.getResult(); try { - model.importFromResource(resourceOid, objectClass, task, operationResult); + modelService.importFromResource(resourceOid, objectClass, task, operationResult); operationResult.computeStatus(); return handleTaskResult(task); } catch (Exception ex) { @@ -426,12 +426,14 @@ public TaskType notifyChange(ResourceObjectShadowChangeDescriptionType changeDes OperationResult parentResult = task.getResult(); try { - model.notifyChange(changeDescription, parentResult, task); - } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException | SecurityViolationException | ObjectAlreadyExistsException | ExpressionEvaluationException | RuntimeException | Error | PolicyViolationException | PreconditionViolationException ex) { - LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex); - auditLogout(task); - throwFault(ex, parentResult); - } + modelService.notifyChange(changeDescription, task, parentResult); + } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException | + SecurityViolationException | ObjectAlreadyExistsException | ExpressionEvaluationException | + RuntimeException | Error | PolicyViolationException ex) { + LoggingUtils.logException(LOGGER, "# MODEL notifyChange() failed", ex); + auditLogout(task); + throwFault(ex, parentResult); + } LOGGER.info("notify change ended."); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java index c7f88a0fa95..55ceee82c64 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java @@ -29,22 +29,22 @@ import com.evolveum.midpoint.model.impl.importer.ImportAccountsFromResourceTaskHandler; import com.evolveum.midpoint.model.impl.importer.ObjectImporter; import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.messaging.MessageProcessor; import com.evolveum.midpoint.model.impl.scripting.ExecutionContext; import com.evolveum.midpoint.model.impl.scripting.ScriptingExpressionEvaluator; -import com.evolveum.midpoint.model.impl.security.NodeAuthenticationToken; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.DiffUtil; +import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.*; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.*; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; -import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; -import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.provisioning.api.*; import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepoAddOptions; import com.evolveum.midpoint.repo.api.RepositoryService; @@ -80,13 +80,13 @@ import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ExecuteScriptType; import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingExpressionType; import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import javax.xml.datatype.XMLGregorianCalendar; @@ -156,7 +156,8 @@ public class ModelController implements ModelService, TaskService, WorkflowServi @Autowired private EmulatedSearchProvider emulatedSearchProvider; @Autowired private CacheRegistry cacheRegistry; @Autowired private ClockworkMedic clockworkMedic; - + @Autowired private ChangeNotificationDispatcher dispatcher; + @Autowired private MessageProcessor messageProcessor; @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; @@ -2323,4 +2324,69 @@ public String getTaskThreadsDump(@NotNull String taskOid, @NotNull Task task, @N //endregion + public void notifyChange(ResourceObjectShadowChangeDescriptionType changeDescription, Task task, OperationResult parentResult) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ObjectNotFoundException, ObjectAlreadyExistsException, ExpressionEvaluationException, PolicyViolationException { + + String oldShadowOid = changeDescription.getOldShadowOid(); + ResourceEventDescription eventDescription = new ResourceEventDescription(); + + PrismObject oldShadow; + LOGGER.trace("resolving old object"); + if (!StringUtils.isEmpty(oldShadowOid)) { + oldShadow = getObject(ShadowType.class, oldShadowOid, SelectorOptions.createCollection(GetOperationOptions.createDoNotDiscovery()), task, parentResult); + eventDescription.setOldShadow(oldShadow); + LOGGER.trace("old object resolved to: {}", oldShadow.debugDumpLazily()); + } else { + LOGGER.trace("Old shadow null"); + } + + PrismObject currentShadow = null; + ShadowType currentShadowType = changeDescription.getCurrentShadow(); + LOGGER.trace("resolving current shadow"); + if (currentShadowType != null) { + prismContext.adopt(currentShadowType); + currentShadow = currentShadowType.asPrismObject(); + LOGGER.trace("current shadow resolved to {}", currentShadow.debugDumpLazily()); + } + + eventDescription.setCurrentShadow(currentShadow); + + ObjectDeltaType deltaType = changeDescription.getObjectDelta(); + + if (deltaType != null) { + + PrismObject shadowToAdd; + ObjectDelta delta = prismContext.deltaFactory().object().createEmptyDelta(ShadowType.class, deltaType.getOid(), + ChangeType.toChangeType(deltaType.getChangeType())); + + if (delta.getChangeType() == ChangeType.ADD) { + if (deltaType.getObjectToAdd() == null) { + LOGGER.trace("No object to add specified. Check your delta. Add delta must contain object to add"); + throw new IllegalArgumentException("No object to add specified. Check your delta. Add delta must contain object to add"); + } + Object objToAdd = deltaType.getObjectToAdd(); + if (!(objToAdd instanceof ShadowType)) { + LOGGER.trace("Wrong object specified in change description. Expected on the the shadow type, but got " + objToAdd.getClass().getSimpleName()); + throw new IllegalArgumentException("Wrong object specified in change description. Expected on the the shadow type, but got " + objToAdd.getClass().getSimpleName()); + } + prismContext.adopt((ShadowType)objToAdd); + + shadowToAdd = ((ShadowType) objToAdd).asPrismObject(); + LOGGER.trace("object to add: {}", shadowToAdd.debugDump()); + delta.setObjectToAdd(shadowToAdd); + } else { + Collection modifications = DeltaConvertor.toModifications(deltaType.getItemDelta(), prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class)); + delta.addModifications(modifications); + } + ModelImplUtils.encrypt(Collections.singletonList(delta), protector, null, parentResult); + eventDescription.setDelta(delta); + } + + eventDescription.setSourceChannel(changeDescription.getChannel()); + + dispatcher.notifyEvent(eventDescription, task, parentResult); + parentResult.computeStatus(); + task.setResult(parentResult); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java index 5538190966f..25ebdfb5978 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java @@ -36,6 +36,7 @@ import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.messaging.MessageWrapper; import com.evolveum.midpoint.model.impl.sync.CorrelationConfirmationEvaluator; import com.evolveum.midpoint.model.impl.sync.SynchronizationContext; import com.evolveum.midpoint.model.impl.sync.SynchronizationServiceUtils; @@ -52,6 +53,10 @@ import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; +import com.evolveum.midpoint.schema.processor.ResourceAttribute; +import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition; +import com.evolveum.midpoint.schema.processor.ResourceSchema; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.schema.util.*; @@ -88,6 +93,7 @@ import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.*; @@ -1815,4 +1821,14 @@ public void applyDefinition(T object) public S_ItemEntry deltaFor(Class objectClass) throws SchemaException { return prismContext.deltaFor(objectClass); } + + // temporary + public MessageWrapper wrap(AsyncUpdateMessageType message) { + return new MessageWrapper(message); + } + + // temporary + public Map getMessageBodyAsMap(AsyncUpdateMessageType message) throws IOException { + return wrap(message).getBodyAsMap(); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/i2/MessageProcessingTaskHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/i2/MessageProcessingTaskHandler.java new file mode 100644 index 00000000000..9c7884c4146 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/i2/MessageProcessingTaskHandler.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.model.impl.i2; + +import com.evolveum.midpoint.model.impl.ModelConstants; +import com.evolveum.midpoint.task.api.*; +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.TaskPartitionDefinitionType; +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import com.rabbitmq.client.DeliverCallback; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * + */ +@Component +public class MessageProcessingTaskHandler implements TaskHandler { + + private static final transient Trace LOGGER = TraceManager.getTrace(MessageProcessingTaskHandler.class); + + public static final String HANDLER_URI = ModelConstants.NS_SYNCHRONIZATION_TASK_PREFIX + "/message-processing/handler-3"; + + @Autowired private TaskManager taskManager; + + @PostConstruct + private void initialize() { + taskManager.registerHandler(HANDLER_URI, this); + System.out.println("URI = " + HANDLER_URI); + } + + @Override + public TaskRunResult run(RunningTask task, TaskPartitionDefinitionType partitionDefinition) { + + ConnectionFactory connectionFactory = new ConnectionFactory(); + +// connectionFactory.setU +// connectionFactory.setHost("192.168.56.101"); +// factory.setUsername("guest"); +// factory.setPassword("guest"); +// factory.setVirtualHost("/"); +// String queueName = "sampleQueue"; +// +// try (Connection connection = factory.newConnection(); +// Channel channel = connection.createChannel()) { +// +// DeliverCallback deliverCallback = (consumerTag, message) -> { +// byte[] body = message.getBody(); +// +// System.out.println("Body: " + new String(body)); +// channel.basicAck(message.getEnvelope().getDeliveryTag(), false); +// task.incrementProgressAndStoreStatsIfNeeded(); +// }; +// channel.basicConsume(queueName, false, deliverCallback, consumerTag -> {}); +// +// while (task.canRun()) { +// Thread.sleep(1000L); +// } +// } catch (Throwable t) { +// LoggingUtils.logUnexpectedException(LOGGER, "Exception on RabbitMQ", t); +// } + + TaskRunResult rv = new TaskRunResult(); + rv.setRunResultStatus(TaskRunResult.TaskRunResultStatus.FINISHED); + rv.setOperationResult(task.getResult()); + return rv; + } + + @Override + public String getCategoryName(Task task) { + return TaskCategory.LIVE_SYNCHRONIZATION; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java index 919feca1d65..cd6d51450f2 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java @@ -19,38 +19,26 @@ import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; -import java.util.HashSet; import java.util.List; -import java.util.Set; import javax.xml.datatype.XMLGregorianCalendar; -import com.evolveum.midpoint.model.api.ProgressInformation; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.model.api.ProgressInformation; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.lens.projector.credentials.ProjectionCredentialsProcessor; -import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentProcessor; -import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentTripleEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentHolderProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentProcessor; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.repo.api.PreconditionViolationException; -import com.evolveum.midpoint.repo.common.CounterManager; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.internals.InternalCounters; import com.evolveum.midpoint.schema.internals.InternalMonitor; @@ -67,6 +55,9 @@ import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingOptionsType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingTypeType; /** * Projector recomputes the context. It takes the context with a few basic data as input. It uses all the policies @@ -99,9 +90,7 @@ public class Projector { @Autowired private Clock clock; @Autowired private ClockworkMedic medic; - @Autowired private CounterManager counterManager; - - private static final Trace LOGGER = TraceManager.getTrace(Projector.class); + private static final Trace LOGGER = TraceManager.getTrace(Projector.class); /** * Runs one projection wave, starting at current execution wave. diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleSuspendTaskExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleSuspendTaskExecutor.java index a29e8462733..d3ec4c2a4f7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleSuspendTaskExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleSuspendTaskExecutor.java @@ -22,8 +22,9 @@ import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.model.api.context.ModelElementContext; -import com.evolveum.midpoint.repo.common.CounterManager; -import com.evolveum.midpoint.repo.common.CounterSepcification; +import com.evolveum.midpoint.repo.api.CounterManager; +import com.evolveum.midpoint.repo.api.CounterSepcification; +import com.evolveum.midpoint.repo.cache.CacheCounterManager; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugDumpable; @@ -48,7 +49,6 @@ public class PolicyRuleSuspendTaskExecutor { private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleSuspendTaskExecutor.class); @Autowired private CounterManager counterManager; -// @Autowired private TaskManager taskManager; public void execute(@NotNull ModelContext context, Task task, OperationResult result) throws ThresholdPolicyViolationException, ObjectNotFoundException, SchemaException { ModelElementContext focusCtx = context.getFocusContext(); @@ -58,7 +58,7 @@ public void execute(@NotNull ModelContext context, Tas } for (EvaluatedPolicyRule policyRule : focusCtx.getPolicyRules()) { - CounterSepcification counterSpec = counterManager.getCounterSpec(task, policyRule.getPolicyRuleIdentifier(), policyRule.getPolicyRule()); + CounterSepcification counterSpec = counterManager.getCounterSpec(task.getTaskType(), policyRule.getPolicyRuleIdentifier(), policyRule.getPolicyRule()); LOGGER.trace("Found counter specification {} for {}", counterSpec, DebugUtil.debugDumpLazily(policyRule)); int counter = 1; @@ -72,20 +72,7 @@ public void execute(@NotNull ModelContext context, Tas counterSpec.setCount(counter); } } - - //TODO : not supported yet -// Collection projectionCtxs = context.getProjectionContexts(); -// for (ModelProjectionContext projectionCtx : projectionCtxs) { -// Collection evaluatedPolicyRules = projectionCtx.getPolicyRules(); -// for (EvaluatedPolicyRule policyRule : evaluatedPolicyRules) { -// LOGGER.info("projction policy rules: {}", policyRule); -// counter = checkEvaluatedPolicyRule(task, policyRule, counter, result); -// } -// -// } - - - + } private synchronized int checkEvaluatedPolicyRule(Task task, EvaluatedPolicyRule policyRule, int counter, OperationResult result) throws ThresholdPolicyViolationException, ObjectNotFoundException, SchemaException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/messaging/MessageProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/messaging/MessageProcessor.java new file mode 100644 index 00000000000..f27b31f5b24 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/messaging/MessageProcessor.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.model.impl.messaging; + +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.repo.common.expression.*; +import com.evolveum.midpoint.schema.SchemaConstantsGenerated; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.api.SecurityContextManager; +import com.evolveum.midpoint.task.api.Task; +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.*; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +@Component +public class MessageProcessor { + + private static final Trace LOGGER = TraceManager.getTrace(MessageProcessor.class); + + public static final String DOT_CLASS = MessageProcessor.class.getName() + "."; + + @Autowired ModelService modelService; + @Autowired ExpressionFactory expressionFactory; + @Autowired PrismContext prismContext; + @Autowired SecurityContextManager securityContextManager; + +// public void processMessage(DataMessageType message, MessageProcessingConfigurationType processing, +// ResourceType resource, Task task, OperationResult parentResult) +// throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, +// ConfigurationException, ExpressionEvaluationException, PreconditionViolationException, PolicyViolationException, +// ObjectAlreadyExistsException { +// OperationResult result = parentResult.createSubresult(DOT_CLASS + "processMessage"); +// try { +// ExpressionVariables variables = createVariables(message, resource); +// if (processing.getConsumerExpression() != null && processing.getTransformerExpression() != null) { +// throw new IllegalStateException("Both consumerExpression and transformerExpression cannot be specified at once"); +// } +// if (processing.getConsumerExpression() != null) { +// evaluateExpression(processing.getConsumerExpression(), variables, "consumer expression", task, result); +// } else { +// List descriptions = evaluateExpression(processing.getTransformerExpression(), +// variables, "transformer expression", task, result); +// LOGGER.trace("Change description computation returned {} description(s)", descriptions.size()); +// for (ResourceObjectShadowChangeDescriptionType description : descriptions) { +// modelService.notifyChange(description, task, result); +// } +// } +// result.computeStatusIfUnknown(); +// } catch (Throwable t) { +// result.recordFatalError("Couldn't process message: " + t.getMessage(), t); +// throw t; +// } +// } +// +// @NotNull +// private List evaluateExpression(ExpressionType expressionBean, ExpressionVariables variables, String contextDescription, +// Task task, OperationResult result) +// throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, +// ConfigurationException, SecurityViolationException { +// Expression, PrismPropertyDefinition> expression = expressionFactory.makePropertyExpression(expressionBean, +// SchemaConstantsGenerated.C_RESOURCE_OBJECT_SHADOW_CHANGE_DESCRIPTION, contextDescription, task, result); +// ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, variables, contextDescription, task, result); +// PrismValueDeltaSetTriple> exprResultTriple = +// ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, context, task, result); +// List list = new ArrayList<>(); +// for (PrismPropertyValue pv : exprResultTriple.getZeroSet()) { +// list.add(pv.getRealValue()); +// } +// return list; +// } +// +// @NotNull +// private ExpressionVariables createVariables(DataMessageType message, +// ResourceType resource) { +// ExpressionVariables variables = new ExpressionVariables(); +// variables.addVariableDefinition(ExpressionConstants.VAR_MESSAGE, new MessageWrapper(message)); +// variables.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, resource); +// return variables; +// } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/messaging/MessageWrapper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/messaging/MessageWrapper.java new file mode 100644 index 00000000000..755a20c38ca --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/messaging/MessageWrapper.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.model.impl.messaging; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.Amqp091MessageAttributesType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.Amqp091MessageType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateMessageType; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * + */ +public class MessageWrapper { + + @NotNull private final AsyncUpdateMessageType message; + + private static final TypeReference MAP_TYPE = new MapTypeReference(); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static class MapTypeReference extends TypeReference> { + } + + public MessageWrapper(@NotNull AsyncUpdateMessageType message) { + this.message = message; + } + + public Amqp091MessageAttributesType getAttributes() { + return ((Amqp091MessageType) message).getAttributes(); + } + + public byte[] getBody() { + return ((Amqp091MessageType) message).getBody(); + } + + public String getText() { + return new String(((Amqp091MessageType) message).getBody(), StandardCharsets.UTF_8); + } + + public AsyncUpdateMessageType getOriginalMessage() { + return message; + } + + public Map getBodyAsMap() throws IOException { + String json = getText(); + return MAPPER.readValue(json, MAP_TYPE); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/CorrelationConfirmationEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/CorrelationConfirmationEvaluator.java index f6aab4ea75a..224393156d6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/CorrelationConfirmationEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/CorrelationConfirmationEvaluator.java @@ -280,9 +280,9 @@ private boolean matchUserCorrelationRule(Class focusTyp public boolean matchFocusByCorrelationRule(SynchronizationContext syncCtx, PrismObject focus) { - if (!syncCtx.hasApplicablePolicy()){ + if (!syncCtx.hasApplicablePolicy()) { LOGGER.warn( - "Resource does not support synchronization. Skipping evaluation correlation/confirmation for {} and {}", + "Resource does not support synchronization. Skipping evaluation correlation/confirmation for {} and {}", focus, syncCtx.getApplicableShadow()); return false; } @@ -294,7 +294,7 @@ public boolean matchFocusByCorrelationRule(Synchronization //TODO: can we expect that systemConfig and resource are always present? if (matchUserCorrelationRule(syncCtx.getFocusClass(), syncCtx.getApplicableShadow(), focus, syncCtx.getResource().asObjectable(), - syncCtx.getSystemConfiguration().asObjectable(), conditionalFilter, syncCtx.getTask(), syncCtx.getResult())){ + syncCtx.getSystemConfiguration().asObjectable(), conditionalFilter, syncCtx.getTask(), syncCtx.getResult())) { LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} match user: {}", syncCtx.getApplicableShadow(), focus); return true; } @@ -303,8 +303,7 @@ public boolean matchFocusByCorrelationRule(Synchronization throw new SystemException("Failed to match user using correlation rule. " + ex.getMessage(), ex); } - LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} does not match user: {}", new Object[] { - syncCtx.getApplicableShadow(), focus }); + LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} does not match user: {}", syncCtx.getApplicableShadow(), focus); return false; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/LiveSyncTaskHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/LiveSyncTaskHandler.java index d1f58e80fd0..28a963dfe8c 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/LiveSyncTaskHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/LiveSyncTaskHandler.java @@ -15,6 +15,12 @@ */ package com.evolveum.midpoint.model.impl.sync; +import javax.annotation.PostConstruct; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; import com.evolveum.midpoint.model.impl.ModelConstants; @@ -22,8 +28,6 @@ import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.repo.api.PreconditionViolationException; -import com.evolveum.midpoint.repo.common.CounterManager; -import com.evolveum.midpoint.repo.common.util.RepoCommonUtils; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; @@ -31,7 +35,13 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.schema.util.ExceptionUtil; -import com.evolveum.midpoint.task.api.*; +import com.evolveum.midpoint.task.api.RunningTask; +import com.evolveum.midpoint.task.api.StatisticsCollectionStrategy; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskCategory; +import com.evolveum.midpoint.task.api.TaskHandler; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.task.api.TaskRunResult; import com.evolveum.midpoint.task.api.TaskRunResult.TaskRunResultStatus; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; @@ -43,17 +53,10 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.CriticalityType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExecutionModeType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskPartitionDefinitionType; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; - /** * The task handler for a live synchronization. * @@ -71,8 +74,7 @@ public class LiveSyncTaskHandler implements TaskHandler { @Autowired private TaskManager taskManager; @Autowired private ProvisioningService provisioningService; @Autowired private PrismContext prismContext; - @Autowired private CounterManager counterManager; - + private static final transient Trace LOGGER = TraceManager.getTrace(LiveSyncTaskHandler.class); @PostConstruct @@ -94,8 +96,7 @@ public StatisticsCollectionStrategy getStatisticsCollectionStrategy() { @Override public TaskRunResult run(RunningTask task, TaskPartitionDefinitionType partition) { LOGGER.trace("LiveSyncTaskHandler.run starting"); - -// counterManager.registerCounter(task, true); + OperationResult opResult = new OperationResult(OperationConstants.LIVE_SYNC); TaskRunResult runResult = new TaskRunResult(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java index 6579936d052..cd497c61fa4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/ReconciliationTaskHandler.java @@ -53,7 +53,6 @@ import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.cache.RepositoryCache; -import com.evolveum.midpoint.repo.common.CounterManager; import com.evolveum.midpoint.repo.common.task.AbstractSearchIterativeTaskHandler; import com.evolveum.midpoint.repo.common.task.TaskHandlerUtil; import com.evolveum.midpoint.schema.GetOperationOptions; @@ -134,7 +133,6 @@ public class ReconciliationTaskHandler implements WorkBucketAwareTaskHandler { @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; - @Autowired private CounterManager counterManager; @Autowired private AssignmentCollector assignmentCollector; @Autowired private SystemObjectCache systemObjectCache; @@ -696,11 +694,10 @@ private void reactShadowGone(PrismObject shadow, PrismObject shadowDelta = shadow.getPrismContext().deltaFactory().object() - .createDeleteDelta(ShadowType.class, shadow.getOid() - ); + .createDeleteDelta(ShadowType.class, shadow.getOid()); change.setObjectDelta(shadowDelta); // Need to also set current shadow. This will get reflected in "old" object in lens context - change.setCurrentShadow(shadow); + change.setCurrentShadow(shadow); // todo why current and not old [pmed]? ModelImplUtils.clearRequestee(task); changeNotificationDispatcher.notifyChange(change, task, result); } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java index faaca478bd6..a603fdc5c08 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java @@ -38,8 +38,6 @@ import com.evolveum.midpoint.common.Clock; import com.evolveum.midpoint.common.SynchronizationUtils; import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.ModelInteractionService; -import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; @@ -83,7 +81,6 @@ import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.exception.SystemException; -import com.evolveum.midpoint.util.exception.ThresholdPolicyViolationException; import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -95,7 +92,6 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSynchronizationSorterType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSynchronizationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationExecutionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; @@ -130,8 +126,6 @@ public class SynchronizationServiceImpl implements SynchronizationService { @Autowired private PrismContext prismContext; @Autowired private Clock clock; - @Autowired private ModelInteractionService modelInteractionService; - @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @@ -143,7 +137,7 @@ public void notifyChange(ResourceObjectShadowChangeDescrip boolean logDebug = isLogDebug(change); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("SYNCHRONIZATION: received change notification\n:{}", change.debugDump(1)); + LOGGER.trace("SYNCHRONIZATION: received change notification:\n{}", change.debugDump(1)); } else { if (logDebug) { LOGGER.debug("SYNCHRONIZATION: received change notification {}", change); @@ -163,9 +157,7 @@ public void notifyChange(ResourceObjectShadowChangeDescrip SynchronizationEventInformation eventInfo = new SynchronizationEventInformation(applicableShadow, change.getSourceChannel(), task); - LensContext modelCtx = null; try { - PrismObject configuration = systemObjectCache.getSystemConfiguration(subResult); SynchronizationContext syncCtx = loadSynchronizationContext(applicableShadow, currentShadow, change.getResource(), change.getSourceChannel(), configuration, task, subResult); syncCtx.setUnrelatedChange(change.isUnrelatedChange()); @@ -180,10 +172,8 @@ public void notifyChange(ResourceObjectShadowChangeDescrip return; } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Synchronization is enabled, focus class: {}, found applicable policy: {}", - syncCtx.getFocusClass(), syncCtx.getPolicyName()); - } + LOGGER.trace("Synchronization is enabled, focus class: {}, found applicable policy: {}", syncCtx.getFocusClass(), + syncCtx.getPolicyName()); setupSituation(syncCtx, eventInfo, change); @@ -198,8 +188,7 @@ public void notifyChange(ResourceObjectShadowChangeDescrip change.setCurrentShadow(newCurrentShadow); syncCtx.setCurrentShadow(newCurrentShadow); } - modelCtx = reactToChange(syncCtx, change, - logDebug, eventInfo); + reactToChange(syncCtx, change, logDebug, eventInfo); eventInfo.record(task); subResult.computeStatus(); @@ -229,9 +218,8 @@ public SynchronizationContext loadSynchronizationContex Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - SynchronizationContext syncCtx = new SynchronizationContext(applicableShadow, currentShadow, resource, sourceChanel, task, result); + SynchronizationContext syncCtx = new SynchronizationContext<>(applicableShadow, currentShadow, resource, sourceChanel, task, result); syncCtx.setSystemConfiguration(configuration); - SynchronizationType synchronization = resource.asObjectable().getSynchronization(); if (synchronization == null) { @@ -243,12 +231,14 @@ public SynchronizationContext loadSynchronizationContex syncCtx.setForceIntentChange(true); LOGGER.trace("Setting synchronization situation to synchronization context: {}", synchronizationDiscriminator.getSynchronizationSituation()); syncCtx.setSituation(synchronizationDiscriminator.getSynchronizationSituation()); - F owner = (F) syncCtx.getCurrentOwner(); + F owner = syncCtx.getCurrentOwner(); if (owner != null && alreadyLinked(owner, syncCtx.getApplicableShadow())) { LOGGER.trace("Setting owner to synchronization context: {}", synchronizationDiscriminator.getOwner()); + //noinspection unchecked syncCtx.setCurrentOwner((F) synchronizationDiscriminator.getOwner()); } LOGGER.trace("Setting correlated owner to synchronization context: {}", synchronizationDiscriminator.getOwner()); + //noinspection unchecked syncCtx.setCorrelatedOwner((F) synchronizationDiscriminator.getOwner()); } @@ -292,12 +282,13 @@ private ObjectSynchronizationDiscriminatorType evaluateSyn return null; } ExpressionType classificationExpression = synchronizationSorterType.getExpression(); - String desc = "syncrhonization divider type "; + String desc = "synchronization divider type "; ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, syncCtx.getApplicableShadow(), null, syncCtx.getResource(), syncCtx.getSystemConfiguration(), null); variables.addVariableDefinition(ExpressionConstants.VAR_CHANNEL, syncCtx.getChanel()); try { ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + //noinspection unchecked PrismPropertyDefinition discriminatorDef = prismContext.getSchemaRegistry() .findPropertyDefinitionByElementName(new QName(SchemaConstants.NS_C, "objectSynchronizationDiscriminator")); PrismPropertyValue evaluateDiscriminator = ExpressionUtil.evaluateExpression(variables, discriminatorDef, @@ -340,7 +331,7 @@ private boolean checkSynchronizationPolicy(Synchronization executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, subResult); subResult.recordSuccess(); eventInfo.record(task); - LOGGER.debug("SYNCHRONIZATION: UNRELATED CHNAGE for {}", syncCtx.getApplicableShadow()); + LOGGER.debug("SYNCHRONIZATION: UNRELATED CHANGE for {}", syncCtx.getApplicableShadow()); return false; } @@ -381,7 +372,7 @@ private boolean checkProtected(SynchronizationContext s subResult.recordSuccess(); eventInfo.setProtected(); eventInfo.record(task); - LOGGER.debug("SYNCHRONIZATION: DONE (dry run) for protected shadow {}", syncCtx.getApplicableShadow()); + LOGGER.debug("SYNCHRONIZATION: DONE for protected shadow {}", syncCtx.getApplicableShadow()); return false; } return true; @@ -480,7 +471,7 @@ private void validate(ResourceObjectShadowChangeDescription change) { // TODO TODO TODO TODO // } - /** + /** * XXX: in situation when one account belongs to two different idm users * (repository returns only first user, method * {@link com.evolveum.midpoint.model.api.ModelService#findShadowOwner(String, Task, OperationResult)} @@ -571,15 +562,7 @@ private void setupSituation(SynchronizationContext sync } private boolean isCorrelatedOwnerSameAsCurrentOwner(F expectedOwner, F currentOwnerType) { - if (expectedOwner == null) { - return true; - } - - if (currentOwnerType == null) { - return true; - } - - return (expectedOwner.getOid().equals(currentOwnerType.getOid())); + return expectedOwner == null || currentOwnerType == null || expectedOwner.getOid().equals(currentOwnerType.getOid()); } private String getOidFromChange(ResourceObjectShadowChangeDescription change) { @@ -610,15 +593,6 @@ public boolean matchUserCorrelationRule(PrismObject synchronizationContext = loadSynchronizationContext(shadow, shadow, resourceType.asPrismObject(), task.getChannel(), configuration, task, result); - Class focusClass; - // TODO is this correct? The problem is that synchronizationPolicy can - // be null... - if (synchronizationContext.hasApplicablePolicy()) { - focusClass = synchronizationContext.getFocusClass(); - } else { - //noinspection unchecked - focusClass = (Class) focus.asObjectable().getClass(); - } return correlationConfirmationEvaluator.matchFocusByCorrelationRule(synchronizationContext, focus); } @@ -667,7 +641,7 @@ private void determineSituationWithCorrelation(Synchroniza ResourceType resource = change.getResource().asObjectable(); validateResourceInShadow(resourceShadow.asObjectable(), resource); - SynchronizationSituationType state = null; + SynchronizationSituationType state; LOGGER.trace("SYNCHRONIZATION: CORRELATION: Looking for list of {} objects based on correlation rule.", syncCtx.getFocusClass().getSimpleName()); List> users = correlationConfirmationEvaluator.findFocusesByCorrelationRule(syncCtx.getFocusClass(), @@ -730,7 +704,6 @@ private void validateResourceInShadow(ShadowType shadow, ResourceType resource) } /** - * @param change * @return method checks change type in object delta if available, otherwise * returns {@link ChangeType#ADD} */ @@ -742,35 +715,22 @@ private ChangeType getModificationType(ResourceObjectShadowChangeDescription cha return ChangeType.ADD; } - private LensContext reactToChange(SynchronizationContext syncCtx, + private void reactToChange(SynchronizationContext syncCtx, ResourceObjectShadowChangeDescription change, boolean logDebug, SynchronizationEventInformation eventInfo) - throws ConfigurationException, ObjectNotFoundException, SchemaException, - PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, - CommunicationException, SecurityViolationException { + throws ConfigurationException, ObjectNotFoundException, SchemaException { SynchronizationSituationType newSituation = syncCtx.getSituation(); - findReactionDefinition(syncCtx); if (syncCtx.getReaction() == null) { LOGGER.trace("No reaction is defined for situation {} in {}", syncCtx.getSituation(), syncCtx.getResource()); eventInfo.setNewSituation(newSituation); - return null; + return; } - // seems to be unused so commented it out [med] - // PrismObject shadow = null; - // if (change.getCurrentShadow() != null) { - // shadow = change.getCurrentShadow(); - // } else if (change.getOldShadow() != null) { - // shadow = change.getOldShadow(); - // } - Boolean doReconciliation = syncCtx.isDoReconciliation(); if (doReconciliation == null) { - // We have to do reconciliation if we have got a full shadow and no - // delta. - // There is no other good way how to reflect the changes from the - // shadow. + // We have to do reconciliation if we have got a full shadow and no delta. + // There is no other good way how to reflect the changes from the shadow. if (change.getObjectDelta() == null) { doReconciliation = true; } @@ -786,7 +746,7 @@ private LensContext reactToChange(SynchronizationContex OperationResult parentResult = syncCtx.getResult(); Task task = syncCtx.getTask(); if (willSynchronize) { - lensContext = createLensContext(syncCtx, change, syncCtx.getReaction(), options, parentResult); + lensContext = createLensContext(syncCtx, change, options, parentResult); } if (LOGGER.isTraceEnabled() && lensContext != null) { @@ -841,15 +801,12 @@ private LensContext reactToChange(SynchronizationContex } eventInfo.setNewSituation(newSituation); - return lensContext; - } @NotNull private LensContext createLensContext(SynchronizationContext syncCtx, - ResourceObjectShadowChangeDescription change, SynchronizationReactionType reactionDefinition, - ModelExecuteOptions options, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ConfigurationException { + ResourceObjectShadowChangeDescription change, ModelExecuteOptions options, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException { LensContext context = contextFactory.createSyncContext(syncCtx.getFocusClass(), change); context.setLazyAuditRequest(true); @@ -863,38 +820,38 @@ private LensContext createLensContext(SynchronizationCo context.rememberResource(resource); PrismObject shadow = getShadowFromChange(change); - if (InternalsConfig.consistencyChecks) + if (shadow == null) { + throw new IllegalStateException("No shadow in change: " + change); + } + if (InternalsConfig.consistencyChecks) { shadow.checkConsistence(); + } // Projection context - ShadowKindType kind = getKind(shadow, syncCtx.getKind()); String intent = getIntent(shadow, syncCtx.getIntent()); - boolean thombstone = isThombstone(change); - ResourceShadowDiscriminator descr = new ResourceShadowDiscriminator(resource.getOid(), kind, intent, thombstone); - LensProjectionContext projectionContext = context.createProjectionContext(descr); + boolean tombstone = isThombstone(change); + ResourceShadowDiscriminator discriminator = new ResourceShadowDiscriminator(resource.getOid(), kind, intent, tombstone); + LensProjectionContext projectionContext = context.createProjectionContext(discriminator); projectionContext.setResource(resource); projectionContext.setOid(getOidFromChange(change)); projectionContext.setSynchronizationSituationDetected(syncCtx.getSituation()); projectionContext.setShadowExistsInRepo(syncCtx.isShadowExistsInRepo()); // insert object delta if available in change - ObjectDelta delta = change.getObjectDelta(); + ObjectDelta delta = change.getObjectDelta(); if (delta != null) { - projectionContext.setSyncDelta((ObjectDelta) delta); + projectionContext.setSyncDelta(delta); } else { projectionContext.setSyncAbsoluteTrigger(true); } // we insert account if available in change - PrismObject currentAccount = shadow; - if (currentAccount != null) { - projectionContext.setLoadedObject(currentAccount); - if (!thombstone) { - projectionContext.setFullShadow(true); - } - projectionContext.setFresh(true); + projectionContext.setLoadedObject(shadow); + if (!tombstone) { + projectionContext.setFullShadow(true); } + projectionContext.setFresh(true); if (delta != null && delta.isDelete()) { projectionContext.setExists(false); @@ -908,12 +865,12 @@ private LensContext createLensContext(SynchronizationCo if (syncCtx.getCurrentOwner() != null) { F focusType = syncCtx.getCurrentOwner(); LensFocusContext focusContext = context.createFocusContext(); + //noinspection unchecked PrismObject focusOld = (PrismObject) focusType.asPrismObject(); focusContext.setLoadedObject(focusOld); } // Global stuff - if (syncCtx.getObjectTemplateRef() != null) { ObjectTemplateType objectTemplate = repositoryService .getObject(ObjectTemplateType.class, syncCtx.getObjectTemplateRef().getOid(), null, parentResult) @@ -934,8 +891,7 @@ private PrismObject getShadowFromChange(ResourceObjectShadowChangeDe return null; } - private ShadowKindType getKind(PrismObject shadow, - ShadowKindType objectSynchronizationKind) { + private ShadowKindType getKind(PrismObject shadow, ShadowKindType objectSynchronizationKind) { ShadowKindType shadowKind = shadow.asObjectable().getKind(); if (shadowKind != null) { return shadowKind; @@ -975,10 +931,6 @@ private boolean isSynchronize(SynchronizationReactionType reactionDefinition) { return !reactionDefinition.getAction().isEmpty(); } - private void findReactionDefinition(SynchronizationContext syncCtx) throws ConfigurationException { - - } - /** * Saves situation, timestamps, kind and intent (if needed) */ @@ -1055,6 +1007,7 @@ private boolean shouldSaveIntent(SynchronizationContext if (syncCtx.isForceIntentChange()) { String objectSyncIntent = syncCtx.getIntent(); + //noinspection RedundantIfStatement if (!MiscSchemaUtil.equalsIntent(shadow.getIntent(), objectSyncIntent)) { return true; } @@ -1063,10 +1016,9 @@ private boolean shouldSaveIntent(SynchronizationContext return false; } - private void executeActions(SynchronizationContext syncCtx, - LensContext context, BeforeAfterType order, - boolean logDebug, Task task, OperationResult parentResult) - throws ConfigurationException, SchemaException { + private void executeActions(SynchronizationContext syncCtx, LensContext context, + BeforeAfterType order, boolean logDebug, Task task, OperationResult parentResult) + throws ConfigurationException, SchemaException { for (SynchronizationActionType actionDef : syncCtx.getReaction().getAction()) { if ((actionDef.getOrder() == null && order == BeforeAfterType.BEFORE) @@ -1084,13 +1036,14 @@ private void executeActions(SynchronizationContext sync Action action = actionManager.getActionInstance(handlerUri); if (action == null) { - LOGGER.warn("Couldn't create action with uri '{}' in resource {}, skipping action.", - new Object[] { handlerUri, syncCtx.getResource() }); + LOGGER.warn("Couldn't create action with uri '{}' in resource {}, skipping action.", handlerUri, + syncCtx.getResource()); continue; } // TODO: legacy userTemplate + // todo parameters are not really used; consider removing [pmed] Map parameters = null; if (actionDef.getParameters() != null) { // TODO: process parameters @@ -1102,7 +1055,7 @@ private void executeActions(SynchronizationContext sync } else { LOGGER.trace("SYNCHRONIZATION: ACTION: Executing: {}.", action.getClass()); } - SynchronizationSituation situation = new SynchronizationSituation(syncCtx.getCurrentOwner(), syncCtx.getCorrelatedOwner(), syncCtx.getSituation()); + SynchronizationSituation situation = new SynchronizationSituation<>(syncCtx.getCurrentOwner(), syncCtx.getCorrelatedOwner(), syncCtx.getSituation()); action.handle(context, situation, parameters, task, parentResult); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizeAccountResultHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizeAccountResultHandler.java index 4bcb7da44ca..980ea3db28a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizeAccountResultHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizeAccountResultHandler.java @@ -165,7 +165,7 @@ protected boolean handleObjectInternal(PrismObject accountShadow, Ru } // We are going to pretend that all of the objects were just created. - // That will efficiently import them to the IDM repository + // That will effectively import them to the IDM repository ResourceObjectShadowChangeDescription change = new ResourceObjectShadowChangeDescription(); change.setSourceChannel(QNameUtil.qNameToUri(sourceChannel)); @@ -176,18 +176,14 @@ protected boolean handleObjectInternal(PrismObject accountShadow, Ru if (forceAdd) { // We should provide shadow in the state before the change. But we are - // pretending that it has - // not existed before, so we will not provide it. - ObjectDelta shadowDelta = accountShadow.getPrismContext().deltaFactory().object().create( - ShadowType.class, ChangeType.ADD); - //PrismObject shadowToAdd = refinedAccountDefinition.getObjectDefinition().parseObjectType(newShadowType); - PrismObject shadowToAdd = newShadowType.asPrismObject(); - shadowDelta.setObjectToAdd(shadowToAdd); - shadowDelta.setOid(newShadowType.getOid()); + // pretending that it has not existed before, so we will not provide it. + ObjectDelta shadowDelta = accountShadow.getPrismContext().deltaFactory().object() + .create(ShadowType.class, ChangeType.ADD); + shadowDelta.setObjectToAdd(accountShadow); + shadowDelta.setOid(accountShadow.getOid()); change.setObjectDelta(shadowDelta); // Need to also set current shadow. This will get reflected in "old" object in lens context change.setCurrentShadow(accountShadow); - } else { // No change, therefore the delta stays null. But we will set the current change.setCurrentShadow(accountShadow); @@ -196,9 +192,7 @@ protected boolean handleObjectInternal(PrismObject accountShadow, Ru try { change.checkConsistence(); } catch (RuntimeException ex) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Check consistence failed: {}\nChange:\n{}", ex, change.debugDump()); - } + LOGGER.trace("Check consistence failed: {}\nChange:\n{}", ex, change.debugDumpLazily()); throw ex; } @@ -206,7 +200,7 @@ protected boolean handleObjectInternal(PrismObject accountShadow, Ru ModelImplUtils.clearRequestee(workerTask); objectChangeListener.notifyChange(change, workerTask, result); - LOGGER.info("#### notify chnage finished."); + LOGGER.debug("#### notify change finished"); // No exception thrown here. The error is indicated in the result. Will be processed by superclass. return workerTask.canRun(); diff --git a/model/model-impl/src/main/resources/ctx-model.xml b/model/model-impl/src/main/resources/ctx-model.xml index 33ed2f5a58d..97cc399362f 100644 --- a/model/model-impl/src/main/resources/ctx-model.xml +++ b/model/model-impl/src/main/resources/ctx-model.xml @@ -41,6 +41,9 @@ + + @@ -78,9 +81,8 @@ - - diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorDummyFake.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorDummyFake.java index 947bc4f76ec..88716160acd 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorDummyFake.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestConnectorDummyFake.java @@ -109,7 +109,7 @@ public void test010ListConnectors() throws Exception { result.computeStatus(); TestUtil.assertSuccess("getObject result", result); - assertEquals("Unexpected number of connectors", 10, connectors.size()); + assertEquals("Unexpected number of connectors", 11, connectors.size()); for(PrismObject connector: connectors) { display("Connector", connector); ConnectorType connectorType = connector.asObjectable(); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/MockAsyncUpdateSource.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/MockAsyncUpdateSource.java new file mode 100644 index 00000000000..ee22000a567 --- /dev/null +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/MockAsyncUpdateSource.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.model.intest.async; + +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateMessageListener; +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateSource; +import com.evolveum.midpoint.provisioning.ucf.api.ListeningActivity; +import com.evolveum.midpoint.provisioning.ucf.impl.builtin.async.AsyncUpdateConnectorInstance; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AnyDataAsyncUpdateMessageType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateMessageType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateSourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UcfChangeType; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * See also MockAsyncUpdateSource in provisioning-impl. + */ +@SuppressWarnings("unused") +public class MockAsyncUpdateSource implements AsyncUpdateSource { + + private static final Trace LOGGER = TraceManager.getTrace(MockAsyncUpdateSource.class); + + private final Queue messages = new LinkedList<>(); + + public static final MockAsyncUpdateSource INSTANCE = new MockAsyncUpdateSource(); + + private static class DummyListeningActivityImpl implements ListeningActivity { + @Override + public void stop() { + // no-op + } + } + + public static MockAsyncUpdateSource create(AsyncUpdateSourceType configuration, AsyncUpdateConnectorInstance connectorInstance) { + LOGGER.info("create() method called"); + return INSTANCE; + } + + @Override + public ListeningActivity startListening(AsyncUpdateMessageListener listener) throws SchemaException { + LOGGER.info("startListening() method called"); + for (AsyncUpdateMessageType message : messages) { + listener.onMessage(message); + } + return new DummyListeningActivityImpl(); + } + + @Override + public void test(OperationResult parentResult) { + LOGGER.info("test() method called"); + } + + public void prepareMessage(UcfChangeType changeDescription) { + AnyDataAsyncUpdateMessageType message = new AnyDataAsyncUpdateMessageType(); + message.setData(changeDescription); + prepareMessage(message); + } + + public void prepareMessage(AsyncUpdateMessageType message) { + messages.offer(message); + } + + public void reset() { + messages.clear(); + } + + @Override + public String toString() { + return "MockAsyncUpdateSource{" + + "messages:" + messages.size() + + '}'; + } +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestAsyncUpdateGrouperJson.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestAsyncUpdateGrouperJson.java new file mode 100644 index 00000000000..a71d2ada676 --- /dev/null +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestAsyncUpdateGrouperJson.java @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.model.intest.async; + +import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.util.MidPointTestConstants; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.io.IOUtils; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Tests async updates using Grouper JSON messages. + * + * Currently uses caching. + */ +@ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class TestAsyncUpdateGrouperJson extends AbstractInitializedModelIntegrationTest { + + public static final File TEST_DIR = new File(MidPointTestConstants.TEST_RESOURCES_DIR, "async/grouper-amqp091"); + + protected static final File RESOURCE_GROUPER_FILE = new File(TEST_DIR, "resource-grouper-amqp091.xml"); + protected static final String RESOURCE_GROUPER_ID = "Grouper"; + protected static final String RESOURCE_GROUPER_OID = "bbb9900a-b53d-4453-b60b-908725e3950e"; + + public static final String BANDERSON_USERNAME = "banderson"; + public static final String JLEWIS685_USERNAME = "jlewis685"; + public static final String ALUMNI_NAME = "ref:alumni"; + public static final String STAFF_NAME = "ref:staff"; + + public static final String GROUPER_USER_INTENT = "subject"; + public static final String GROUPER_GROUP_INTENT = "group"; + + protected PrismObject resourceGrouper; + + private static final File CHANGE_100 = new File(TEST_DIR, "change-100-banderson-add-supergroup.json"); + private static final File CHANGE_110 = new File(TEST_DIR, "change-110-alumni-add.json"); + private static final File CHANGE_110a = new File(TEST_DIR, "change-110a-staff-add.json"); + private static final File CHANGE_200 = new File(TEST_DIR, "change-200-banderson-add-alumni.json"); + private static final File CHANGE_210 = new File(TEST_DIR, "change-210-banderson-add-staff.json"); + private static final File CHANGE_220 = new File(TEST_DIR, "change-220-jlewis685-add-alumni.json"); + private static final File CHANGE_230 = new File(TEST_DIR, "change-230-jlewis685-add-supergroup.json"); + private static final File CHANGE_240 = new File(TEST_DIR, "change-240-banderson-add-staff.json"); + private static final File CHANGE_250 = new File(TEST_DIR, "change-250-banderson-delete-alumni.json"); + private static final File CHANGE_310 = new File(TEST_DIR, "change-310-staff-delete.json"); + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + resourceGrouper = importAndGetObjectFromFile(ResourceType.class, RESOURCE_GROUPER_FILE, RESOURCE_GROUPER_OID, + initTask, initResult); + } + + @Test + public void test000Sanity() throws Exception { + final String TEST_NAME = "test000Sanity"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + + OperationResult testResultGrouper = modelService.testResource(RESOURCE_GROUPER_OID, task); + TestUtil.assertSuccess(testResultGrouper); + } + + private Task createTestTask(String TEST_NAME) { + return taskManager.createTaskInstance(TestAsyncUpdateGrouperJson.class.getName() + "." + TEST_NAME); + } + + /** + * The first MEMBERSHIP_ADD event for banderson (supergroup) + */ + @Test + public void test100AddAnderson() throws Exception { + final String TEST_NAME = "test100AddAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_100)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(); + } + + private AsyncUpdateMessageType getAmqp091Message(File file) throws IOException { + Amqp091MessageType rv = new Amqp091MessageType(); + String json = String.join("\n", IOUtils.readLines(new FileReader(file))); + rv.setBody(json.getBytes(StandardCharsets.UTF_8)); + return rv; + } + + /** + * GROUP_ADD event for ref:alumni and ref:staff. + */ + @Test + public void test110AddAlumniAndStaff() throws Exception { + final String TEST_NAME = "test110AddAlumniAndStaff"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_110)); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_110a)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertOrgByName(ALUMNI_NAME, "after") + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ENTITLEMENT) +// .assertIntent(GROUPER_GROUP_INTENT) + .assertResource(RESOURCE_GROUPER_OID); + + assertOrgByName(STAFF_NAME, "after") + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ENTITLEMENT) +// .assertIntent(GROUPER_GROUP_INTENT) + .assertResource(RESOURCE_GROUPER_OID); + } + + /** + * Adding ref:alumni membership for banderson. + */ + @Test + public void test200AddAlumniForAnderson() throws Exception { + final String TEST_NAME = "test200AddAlumniForAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_200)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .display("shadow after") + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME); + } + + /** + * Adding ref:staff membership for banderson. + */ + @Test + public void test210AddStaffForAnderson() throws Exception { + final String TEST_NAME = "test210AddStaffForAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_210)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME, STAFF_NAME); + } + + /** + * Adding ref:alumni membership for jlewis685. But this is the first occurrence of jlewis685! + */ + @Test + public void test220AddAlumniForLewis() throws Exception { + final String TEST_NAME = "test220AddAlumniForLewis"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_220)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME); + } + + /** + * Adding supergroup to jlewis (notification-only change). Should be idempotent. + */ + @Test + public void test230AddLewis() throws Exception { + final String TEST_NAME = "test230AddLewis"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_230)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME); + } + + /** + * Adding ref:staff membership for banderson (again). Should be idempotent. + */ + @Test + public void test240AddStaffForAnderson() throws Exception { + final String TEST_NAME = "test240AddStaffForAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_240)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME, STAFF_NAME); + } + + /** + * Deleting ref:alumni membership for banderson. + */ + @Test + public void test250DeleteAlumniForAnderson() throws Exception { + final String TEST_NAME = "test250DeleteAlumniForAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_250)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(STAFF_NAME); + } + + /** + * Deleting etc:staff. + */ + @Test + public void test310DeleteStaff() throws Exception { + final String TEST_NAME = "test310DeleteStaff"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + assertObjectByName(OrgType.class, STAFF_NAME, task, result); + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_310)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertNoObjectByName(OrgType.class, STAFF_NAME, task, result); + } +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestAsyncUpdateUcf.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestAsyncUpdateUcf.java new file mode 100644 index 00000000000..ca598ec9622 --- /dev/null +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestAsyncUpdateUcf.java @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.model.intest.async; + +import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.util.MidPointTestConstants; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UcfChangeType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import java.io.File; + +/** + * Tests model.notifyChange using real AMQP messages. + * + * Currently uses caching. And plain messages (no transformation). + */ +@ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class TestAsyncUpdateUcf extends AbstractInitializedModelIntegrationTest { + + public static final File TEST_DIR = new File(MidPointTestConstants.TEST_RESOURCES_DIR, "async/ucf"); + + protected static final File RESOURCE_GROUPER_FILE = new File(TEST_DIR, "resource-grouper-ucf-internal.xml"); + protected static final String RESOURCE_GROUPER_ID = "Grouper"; + protected static final String RESOURCE_GROUPER_OID = "bbb9900a-b53d-4453-b60b-908725e3950e"; + + public static final String BANDERSON_USERNAME = "banderson"; + public static final String JLEWIS685_USERNAME = "jlewis685"; + public static final String KWHITE_USERNAME = "kwhite"; + public static final String ALUMNI_NAME = "ref:alumni"; + public static final String STAFF_NAME = "ref:staff"; + + public static final String GROUPER_USER_INTENT = "subject"; + public static final String GROUPER_GROUP_INTENT = "group"; + + protected PrismObject resourceGrouper; + + private static final File CHANGE_100 = new File(TEST_DIR, "change-100-banderson-add.xml"); + private static final File CHANGE_110 = new File(TEST_DIR, "change-110-alumni-add.xml"); + private static final File CHANGE_110a = new File(TEST_DIR, "change-110a-staff-add.xml"); + private static final File CHANGE_120 = new File(TEST_DIR, "change-120-kwhite-identifiers-only.xml"); + private static final File CHANGE_200 = new File(TEST_DIR, "change-200-banderson-add-alumni-full-shadow.xml"); + private static final File CHANGE_210 = new File(TEST_DIR, "change-210-banderson-add-staff.xml"); + private static final File CHANGE_220 = new File(TEST_DIR, "change-220-jlewis685-add-alumni.xml"); + private static final File CHANGE_230 = new File(TEST_DIR, "change-230-jlewis685-identifiers-only.xml"); + private static final File CHANGE_300 = new File(TEST_DIR, "change-300-banderson-delete.xml"); + private static final File CHANGE_310 = new File(TEST_DIR, "change-310-staff-delete.xml"); + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + resourceGrouper = importAndGetObjectFromFile(ResourceType.class, RESOURCE_GROUPER_FILE, RESOURCE_GROUPER_OID, + initTask, initResult); + } + + @Test + public void test000Sanity() throws Exception { + final String TEST_NAME = "test000Sanity"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + + OperationResult testResultGrouper = modelService.testResource(RESOURCE_GROUPER_OID, task); + TestUtil.assertSuccess(testResultGrouper); + } + + private Task createTestTask(String TEST_NAME) { + return taskManager.createTaskInstance(TestAsyncUpdateUcf.class.getName() + "." + TEST_NAME); + } + + /** + * Shadow ADD delta for banderson. + */ + @Test + public void test100AddAnderson() throws Exception { + final String TEST_NAME = "test100AddAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_100).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(); + } + + /** + * Shadow ADD deltas for ref:alumni and ref:staff. + */ + @Test + public void test110AddAlumniAndStaff() throws Exception { + final String TEST_NAME = "test110AddAlumni"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_110).parseRealValue(UcfChangeType.class)); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_110a).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertOrgByName(ALUMNI_NAME, "after") + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ENTITLEMENT) +// .assertIntent(GROUPER_GROUP_INTENT) + .assertResource(RESOURCE_GROUPER_OID); + + assertOrgByName(STAFF_NAME, "after") + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ENTITLEMENT) +// .assertIntent(GROUPER_GROUP_INTENT) + .assertResource(RESOURCE_GROUPER_OID); + } + + /** + * Identifiers-only message for kwhite. + */ + @Test + public void test120AddWhite() throws Exception { + final String TEST_NAME = "test120AddWhite"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_120).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(KWHITE_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ACCOUNT) + // .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(); + } + + /** + * Adding ref:alumni membership for banderson "the old way" (i.e. by providing full current shadow). + */ + @Test + public void test200AddAlumniForAnderson() throws Exception { + final String TEST_NAME = "test200AddAlumniForAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_200).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .assertKind(ShadowKindType.ACCOUNT) +// .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .display("shadow after") + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME); + } + + /** + * Adding ref:staff membership for banderson using delta. + */ + @Test + public void test210AddStaffForAnderson() throws Exception { + final String TEST_NAME = "test210AddStaffForAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_210).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) +// .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME, STAFF_NAME); + } + + /** + * Adding ref:alumni membership for jlewis685 "the new way" (i.e. by a delta). But this is the first occurrence of jlewis685! + */ + @Test + public void test220AddAlumniForLewis() throws Exception { + final String TEST_NAME = "test220AddAlumniForLewis"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_220).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) +// .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME); + } + + /** + * Mentions jlewis again (notification-only change). Should be idempotent. + */ + @Test + public void test230MentionLewis() throws Exception { + final String TEST_NAME = "test230MentionLewis"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_230).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display("shadow after") + .assertKind(ShadowKindType.ACCOUNT) +// .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .end() + .end() + .end() + .assertOrganizationalUnits(ALUMNI_NAME); + } + + /** + * Deleting banderson. + */ + @Test + public void test300DeleteAnderson() throws Exception { + final String TEST_NAME = "test300DeleteAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_300).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + //.assertOrganizationalUnits(ALUMNI_NAME) + .links() + .assertNone(); + } + + /** + * Deleting etc:staff. + */ + @Test + public void test310DeleteStaff() throws Exception { + final String TEST_NAME = "test310DeleteStaff"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = createTestTask(TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + assertObjectByName(OrgType.class, STAFF_NAME, task, result); + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(CHANGE_310).parseRealValue(UcfChangeType.class)); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertNoObjectByName(OrgType.class, STAFF_NAME, task, result); + } +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestNotifyChange.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestNotifyChange.java new file mode 100644 index 00000000000..92da9d14da1 --- /dev/null +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/async/TestNotifyChange.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.model.intest.async; + +import com.evolveum.icf.dummy.resource.DummyResource; +import com.evolveum.icf.dummy.resource.DummySyncStyle; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; +import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.DeltaFactory; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition; +import com.evolveum.midpoint.schema.processor.ResourceSchema; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.DummyResourceContoller; +import com.evolveum.midpoint.test.util.MidPointTestConstants; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectShadowChangeDescriptionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import java.io.File; + +/** + * Tests model.notifyChange using manually constructed ResourceObjectShadowChangeDescriptionType objects. + */ +@ContextConfiguration(locations = {"classpath:ctx-model-intest-test-main.xml"}) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class TestNotifyChange extends AbstractInitializedModelIntegrationTest { + + public static final File TEST_DIR = new File(MidPointTestConstants.TEST_RESOURCES_DIR, "async/notify-change"); + + protected static final File RESOURCE_GROUPER_FILE = new File(TEST_DIR, "resource-grouper.xml"); + protected static final String RESOURCE_GROUPER_ID = "Grouper"; + protected static final String RESOURCE_GROUPER_OID = "bbb9900a-b53d-4453-b60b-908725e3950e"; + + public static final String BANDERSON_USERNAME = "banderson"; + public static final String JLEWIS685_USERNAME = "jlewis685"; + public static final String ALUMNI_NAME = "ref:alumni"; + public static final String STAFF_NAME = "ref:staff"; + + public static final String GROUPER_USER_INTENT = "subject"; + public static final String GROUPER_GROUP_INTENT = "group"; + + protected static DummyResource dummyResourceGrouper; + protected static DummyResourceContoller dummyResourceCtlGrouper; + protected ResourceType resourceDummyGrouperType; + protected PrismObject resourceDummyGrouper; + + protected static final File SHADOW_BANDERSON_FILE = new File(TEST_DIR, "shadow-banderson.xml"); + protected static final File SHADOW_BANDERSON_WITH_GROUPS_FILE = new File(TEST_DIR, "shadow-banderson-with-groups.xml"); + protected static final File SHADOW_JLEWIS685_FILE = new File(TEST_DIR, "shadow-jlewis685.xml"); + protected static final File SHADOW_ALUMNI_FILE = new File(TEST_DIR, "shadow-alumni.xml"); + protected static final File SHADOW_STAFF_FILE = new File(TEST_DIR, "shadow-staff.xml"); + + private String lewisShadowOid; + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + // Resources + dummyResourceCtlGrouper = DummyResourceContoller.create(RESOURCE_GROUPER_ID, resourceDummyGrouper); + dummyResourceGrouper = dummyResourceCtlGrouper.getDummyResource(); + dummyResourceGrouper.setSyncStyle(DummySyncStyle.SMART); + dummyResourceGrouper.populateWithDefaultSchema(); + + resourceDummyGrouper = importAndGetObjectFromFile(ResourceType.class, RESOURCE_GROUPER_FILE, RESOURCE_GROUPER_OID, initTask, initResult); + resourceDummyGrouperType = resourceDummyGrouper.asObjectable(); + dummyResourceCtlGrouper.setResource(resourceDummyGrouper); + } + + @Test + public void test000Sanity() throws Exception { + final String TEST_NAME = "test000Sanity"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = taskManager.createTaskInstance(TestNotifyChange.class.getName() + "." + TEST_NAME); + + OperationResult testResultGrouper = modelService.testResource(RESOURCE_GROUPER_OID, task); + TestUtil.assertSuccess(testResultGrouper); + } + + /** + * MEMBER_ADD event for banderson. + */ + @Test + public void test100AddAnderson() throws Exception { + final String TEST_NAME = "test100AddAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = taskManager.createTaskInstance(TestNotifyChange.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + PrismObject bandersonShadow = prismContext.parseObject(SHADOW_BANDERSON_FILE); + ResourceObjectShadowChangeDescriptionType change = new ResourceObjectShadowChangeDescriptionType(); + ObjectDelta addDelta = DeltaFactory.Object.createAddDelta(bandersonShadow); + change.setObjectDelta(DeltaConvertor.toObjectDeltaType(addDelta)); + change.setChannel(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); + + // WHEN + + modelService.notifyChange(change, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .display() + .assertKind(ShadowKindType.ACCOUNT) + .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID); + } + + /** + * MEMBER_ADD event for jlewis685. + */ + @Test + public void test105AddLewis() throws Exception { + final String TEST_NAME = "test105AddLewis"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = taskManager.createTaskInstance(TestNotifyChange.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + PrismObject lewisShadow = prismContext.parseObject(SHADOW_JLEWIS685_FILE); + ResourceObjectShadowChangeDescriptionType change = new ResourceObjectShadowChangeDescriptionType(); + ObjectDelta addDelta = DeltaFactory.Object.createAddDelta(lewisShadow); + change.setObjectDelta(DeltaConvertor.toObjectDeltaType(addDelta)); + change.setChannel(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); + + // WHEN + + modelService.notifyChange(change, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + lewisShadowOid = assertUserAfterByUsername(JLEWIS685_USERNAME) + .displayWithProjections() + .links() + .single() + .resolveTarget() + .assertKind(ShadowKindType.ACCOUNT) + .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .display() + .end() + .getOid(); + System.out.println("lewis shadow OID = " + lewisShadowOid); + } + + /** + * GROUP_ADD event for ref:alumni. + */ + @Test + public void test110AddAlumni() throws Exception { + final String TEST_NAME = "test110AddAlumni"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = taskManager.createTaskInstance(TestNotifyChange.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + PrismObject alumniShadow = prismContext.parseObject(SHADOW_ALUMNI_FILE); + ResourceObjectShadowChangeDescriptionType change = new ResourceObjectShadowChangeDescriptionType(); + ObjectDelta addDelta = DeltaFactory.Object.createAddDelta(alumniShadow); + change.setObjectDelta(DeltaConvertor.toObjectDeltaType(addDelta)); + change.setChannel(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); + + // WHEN + + modelService.notifyChange(change, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertOrgByName(ALUMNI_NAME, "after") + .displayWithProjections() + .links() + .single() + .resolveTarget() + .assertKind(ShadowKindType.ENTITLEMENT) + .assertIntent(GROUPER_GROUP_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .display(); + } + + /** + * GROUP_ADD event for ref:staff. + */ + @Test + public void test120AddStaff() throws Exception { + final String TEST_NAME = "test120AddStaff"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = taskManager.createTaskInstance(TestNotifyChange.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + PrismObject staffShadow = prismContext.parseObject(SHADOW_STAFF_FILE); + ResourceObjectShadowChangeDescriptionType change = new ResourceObjectShadowChangeDescriptionType(); + ObjectDelta addDelta = DeltaFactory.Object.createAddDelta(staffShadow); + change.setObjectDelta(DeltaConvertor.toObjectDeltaType(addDelta)); + change.setChannel(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); + + // WHEN + + modelService.notifyChange(change, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertOrgByName(STAFF_NAME, "after") + .displayWithProjections() + .links() + .single() + .resolveTarget() + .assertKind(ShadowKindType.ENTITLEMENT) + .assertIntent(GROUPER_GROUP_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .display(); + } + + /** + * Adding ref:alumni and ref:staff membership for banderson "the old way" (i.e. by providing full current shadow). + */ + @Test + public void test200AddGroupsForAnderson() throws Exception { + final String TEST_NAME = "test200AddGroupsForAnderson"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = taskManager.createTaskInstance(TestNotifyChange.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + PrismObject bandersonShadow = prismContext.parseObject(SHADOW_BANDERSON_WITH_GROUPS_FILE); + ResourceObjectShadowChangeDescriptionType change = new ResourceObjectShadowChangeDescriptionType(); + change.setCurrentShadow(bandersonShadow.asObjectable()); + change.setChannel(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); + + // WHEN + + modelService.notifyChange(change, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .displayWithProjections() + .assertOrganizationalUnits(ALUMNI_NAME, STAFF_NAME) + .links() + .single() + .resolveTarget() + .assertKind(ShadowKindType.ACCOUNT) + .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .display("shadow after"); + } + + /** + * Adding ref:alumni membership for jlewis685 "the new way" (i.e. by a delta). + */ + @Test + public void test210AddGroupsForLewis() throws Exception { + final String TEST_NAME = "test210AddGroupsForLewis"; + TestUtil.displayTestTitle(this, TEST_NAME); + Task task = taskManager.createTaskInstance(TestNotifyChange.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + // GIVEN + + ResourceSchema schema = RefinedResourceSchemaImpl.getResourceSchema(resourceDummyGrouper, prismContext); + assert schema != null; + ResourceAttributeDefinition privilegeDefinition = schema.findDefaultObjectClassDefinition(ShadowKindType.ACCOUNT) + .findAttributeDefinition(DummyResourceContoller.DUMMY_ENTITLEMENT_PRIVILEGE_NAME); + ObjectDelta delta = prismContext.deltaFor(ShadowType.class) + .item(ItemPath.create(ShadowType.F_ATTRIBUTES, DummyResourceContoller.DUMMY_ENTITLEMENT_PRIVILEGE_NAME), privilegeDefinition) + .add(ALUMNI_NAME) + .asObjectDeltaCast(lewisShadowOid); + + ResourceObjectShadowChangeDescriptionType change = new ResourceObjectShadowChangeDescriptionType(); + change.setObjectDelta(DeltaConvertor.toObjectDeltaType(delta)); + change.setOldShadowOid(lewisShadowOid); + change.setChannel(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); + + // WHEN + + modelService.notifyChange(change, task, result); + + // THEN + + result.computeStatus(); + TestUtil.assertSuccess(result); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .displayWithProjections() + .assertOrganizationalUnits(ALUMNI_NAME) + .links() + .single() + .resolveTarget() + .assertKind(ShadowKindType.ACCOUNT) + .assertIntent(GROUPER_USER_INTENT) + .assertResource(RESOURCE_GROUPER_OID) + .display("shadow after"); + } + +} diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-100-banderson-add-supergroup.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-100-banderson-add-supergroup.json new file mode 100644 index 00000000000..5c860864a62 --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-100-banderson-add-supergroup.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "00000000000000000000000000000001", + "changeOccurred": false, + "createdOnMicros": 1551884863420000, + "subjectId": "banderson", + "id": "94320942304930294023940329403294", + "sequenceNumber": "100", + "eventType": "MEMBERSHIP_ADD", + "groupName": "etc:midpointGroups" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-110-alumni-add.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-110-alumni-add.json new file mode 100644 index 00000000000..9d1b02e2f44 --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-110-alumni-add.json @@ -0,0 +1,15 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "displayName": "ref:alumni", + "changeOccurred": false, + "createdOnMicros": 1551884850499000, + "parentStemId": "9a7ce40af6c546148b41eec81b8ca18d", + "id": "00000000000000000000000000000002", + "sequenceNumber": "110", + "eventType": "GROUP_ADD", + "name": "ref:alumni" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-110a-staff-add.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-110a-staff-add.json new file mode 100644 index 00000000000..fc28ace4d31 --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-110a-staff-add.json @@ -0,0 +1,15 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "displayName": "ref:staff", + "changeOccurred": false, + "createdOnMicros": 1551884850500000, + "parentStemId": "9a7ce40af6c546148b41eec81b8ca18d", + "id": "00000000000000000000000000000003", + "sequenceNumber": "111", + "eventType": "GROUP_ADD", + "name": "ref:staff" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-200-banderson-add-alumni.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-200-banderson-add-alumni.json new file mode 100644 index 00000000000..f59559b5914 --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-200-banderson-add-alumni.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "00000000000000000000000000000002", + "changeOccurred": false, + "createdOnMicros": 1551884899000000, + "subjectId": "banderson", + "id": "43294320943029403294023113333333", + "sequenceNumber": "200", + "eventType": "MEMBERSHIP_ADD", + "groupName": "ref:alumni" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-210-banderson-add-staff.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-210-banderson-add-staff.json new file mode 100644 index 00000000000..7ab2556f35c --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-210-banderson-add-staff.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "00000000000000000000000000000003", + "changeOccurred": false, + "createdOnMicros": 1551884899000200, + "subjectId": "banderson", + "id": "35053020594395347781783127847322", + "sequenceNumber": "210", + "eventType": "MEMBERSHIP_ADD", + "groupName": "ref:staff" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-220-jlewis685-add-alumni.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-220-jlewis685-add-alumni.json new file mode 100644 index 00000000000..566ba44e865 --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-220-jlewis685-add-alumni.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "00000000000000000000000000000002", + "changeOccurred": false, + "createdOnMicros": 1551884899000200, + "subjectId": "jlewis685", + "id": "32193201930193029105543238888888", + "sequenceNumber": "220", + "eventType": "MEMBERSHIP_ADD", + "groupName": "ref:alumni" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-230-jlewis685-add-supergroup.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-230-jlewis685-add-supergroup.json new file mode 100644 index 00000000000..886b470168e --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-230-jlewis685-add-supergroup.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "00000000000000000000000000000001", + "changeOccurred": false, + "createdOnMicros": 1551884863420050, + "subjectId": "jlewis685", + "id": "f4234329049432090112333310abc321", + "sequenceNumber": "230", + "eventType": "MEMBERSHIP_ADD", + "groupName": "etc:midpointGroups" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-240-banderson-add-staff.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-240-banderson-add-staff.json new file mode 100644 index 00000000000..cbb43daa70f --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-240-banderson-add-staff.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "00000000000000000000000000000003", + "changeOccurred": false, + "createdOnMicros": 1551884899000000, + "subjectId": "banderson", + "id": "43294320943029403294023113333333", + "sequenceNumber": "240", + "eventType": "MEMBERSHIP_ADD", + "groupName": "ref:staff" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-250-banderson-delete-alumni.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-250-banderson-delete-alumni.json new file mode 100644 index 00000000000..d6a3c933a1b --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-250-banderson-delete-alumni.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "00000000000000000000000000000002", + "changeOccurred": false, + "createdOnMicros": 1551884899000000, + "subjectId": "banderson", + "id": "43294320943029403294023113333333", + "sequenceNumber": "250", + "eventType": "MEMBERSHIP_DELETE", + "groupName": "ref:alumni" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/change-310-staff-delete.json b/model/model-intest/src/test/resources/async/grouper-amqp091/change-310-staff-delete.json new file mode 100644 index 00000000000..becaadeacee --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/change-310-staff-delete.json @@ -0,0 +1,15 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "displayName": "ref:staff", + "changeOccurred": false, + "createdOnMicros": 1551884850500000, + "parentStemId": "9a7ce40af6c546148b41eec81b8ca18d", + "id": "00000000000000000000000000000003", + "sequenceNumber": "310", + "eventType": "GROUP_DELETE", + "name": "ref:staff" + } + ] +} \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/grouper-amqp091/resource-grouper-amqp091.xml b/model/model-intest/src/test/resources/async/grouper-amqp091/resource-grouper-amqp091.xml new file mode 100644 index 00000000000..3e28c4101c1 --- /dev/null +++ b/model/model-intest/src/test/resources/async/grouper-amqp091/resource-grouper-amqp091.xml @@ -0,0 +1,330 @@ + + + + + + Grouper Resource + + + + connectorType + AsyncUpdateConnector + + + + + + + com.evolveum.midpoint.model.intest.async.MockAsyncUpdateSource + + + + + + + + + + + + + + + + + + icfs:uid + icfs:name + icfs:name + icfs:name + + + + + + + + + + + + + + + icfs:uid + icfs:name + icfs:name + icfs:name + + + + + + + + + + + + + + + + account + subject + ri:AccountObjectClass + true + + icfs:name + + + name + + + + + ri:group + + + organizationalUnit + + + + + + entitlement + group + ri:GroupObjectClass + true + + icfs:name + + + name + + + + + + + + true + account + subject + UserType + + + name + + + $account/attributes/name + + + + + + linked + true + + + deleted + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + true + entitlement + group + OrgType + + + name + + + $account/attributes/name + + + + + + linked + true + + + deleted + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#deleteFocus + + + + unlinked + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + + + + + + + + + passive + + diff --git a/model/model-intest/src/test/resources/async/notify-change/resource-grouper.xml b/model/model-intest/src/test/resources/async/notify-change/resource-grouper.xml new file mode 100644 index 00000000000..88e61c3ee82 --- /dev/null +++ b/model/model-intest/src/test/resources/async/notify-change/resource-grouper.xml @@ -0,0 +1,176 @@ + + + + + + Grouper Resource + + + + + connectorType + com.evolveum.icf.dummy.connector.DummyConnector + + + connectorVersion + 2.0 + + + + + + + Grouper + + + + + + account + subject + ri:AccountObjectClass + true + + icfs:name + + + name + + + + + ri:privileges + + + organizationalUnit + + + + + + entitlement + group + ri:CustomprivilegeObjectClass + true + + icfs:name + + + name + + + + + + + + true + account + subject + UserType + + + name + + + $account/attributes/name + + + + + + linked + true + + + deleted + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + true + entitlement + group + OrgType + + + name + + + $account/attributes/name + + + + + + linked + true + + + deleted + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#deleteFocus + + + + unlinked + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + + + + true + + + + + passive + + diff --git a/model/model-intest/src/test/resources/async/notify-change/shadow-alumni.xml b/model/model-intest/src/test/resources/async/notify-change/shadow-alumni.xml new file mode 100644 index 00000000000..031430ca7ad --- /dev/null +++ b/model/model-intest/src/test/resources/async/notify-change/shadow-alumni.xml @@ -0,0 +1,29 @@ + + + + ref:alumni + + ri:CustomprivilegeObjectClass + entitlement + group + + ref:alumni + e91de1a2ba17474a871655064eeaf186 + + \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/notify-change/shadow-banderson-with-groups.xml b/model/model-intest/src/test/resources/async/notify-change/shadow-banderson-with-groups.xml new file mode 100644 index 00000000000..58536dcd3f4 --- /dev/null +++ b/model/model-intest/src/test/resources/async/notify-change/shadow-banderson-with-groups.xml @@ -0,0 +1,31 @@ + + + + banderson + + ri:AccountObjectClass + account + subject + + banderson + banderson + ref:alumni + ref:staff + + \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/notify-change/shadow-banderson.xml b/model/model-intest/src/test/resources/async/notify-change/shadow-banderson.xml new file mode 100644 index 00000000000..1efdfff5b5c --- /dev/null +++ b/model/model-intest/src/test/resources/async/notify-change/shadow-banderson.xml @@ -0,0 +1,29 @@ + + + + banderson + + ri:AccountObjectClass + account + subject + + banderson + banderson + + \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/notify-change/shadow-jlewis685.xml b/model/model-intest/src/test/resources/async/notify-change/shadow-jlewis685.xml new file mode 100644 index 00000000000..b38411f1233 --- /dev/null +++ b/model/model-intest/src/test/resources/async/notify-change/shadow-jlewis685.xml @@ -0,0 +1,29 @@ + + + + jlewis685 + + ri:AccountObjectClass + account + subject + + jlewis685 + jlewis685 + + \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/notify-change/shadow-staff.xml b/model/model-intest/src/test/resources/async/notify-change/shadow-staff.xml new file mode 100644 index 00000000000..dd3468c0942 --- /dev/null +++ b/model/model-intest/src/test/resources/async/notify-change/shadow-staff.xml @@ -0,0 +1,29 @@ + + + + ref:staff + + ri:CustomprivilegeObjectClass + entitlement + group + + ref:staff + 432895fed53280163308a8230019583b + + \ No newline at end of file diff --git a/model/model-intest/src/test/resources/async/ucf/change-100-banderson-add.xml b/model/model-intest/src/test/resources/async/ucf/change-100-banderson-add.xml new file mode 100644 index 00000000000..b0406db5be2 --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-100-banderson-add.xml @@ -0,0 +1,34 @@ + + + + ri:AccountObjectClass + + add + ShadowType + + + banderson + banderson + + + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-110-alumni-add.xml b/model/model-intest/src/test/resources/async/ucf/change-110-alumni-add.xml new file mode 100644 index 00000000000..8ccfd7b71a5 --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-110-alumni-add.xml @@ -0,0 +1,34 @@ + + + + ri:GroupObjectClass + + add + ShadowType + + + ref:alumni + 00000000000000000000000000000002 + + + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-110a-staff-add.xml b/model/model-intest/src/test/resources/async/ucf/change-110a-staff-add.xml new file mode 100644 index 00000000000..a04a7dab264 --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-110a-staff-add.xml @@ -0,0 +1,34 @@ + + + + ri:GroupObjectClass + + add + ShadowType + + + ref:staff + 00000000000000000000000000000003 + + + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-120-kwhite-identifiers-only.xml b/model/model-intest/src/test/resources/async/ucf/change-120-kwhite-identifiers-only.xml new file mode 100644 index 00000000000..b155d4b1f83 --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-120-kwhite-identifiers-only.xml @@ -0,0 +1,26 @@ + + + + ri:AccountObjectClass + + kwhite + kwhite + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-200-banderson-add-alumni-full-shadow.xml b/model/model-intest/src/test/resources/async/ucf/change-200-banderson-add-alumni-full-shadow.xml new file mode 100644 index 00000000000..24b0f1141fc --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-200-banderson-add-alumni-full-shadow.xml @@ -0,0 +1,29 @@ + + + + ri:AccountObjectClass + + + banderson + banderson + ref:alumni + + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-210-banderson-add-staff.xml b/model/model-intest/src/test/resources/async/ucf/change-210-banderson-add-staff.xml new file mode 100644 index 00000000000..eefe189e592 --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-210-banderson-add-staff.xml @@ -0,0 +1,36 @@ + + + + ri:AccountObjectClass + + banderson + banderson + + + modify + ShadowType + + add + attributes/ri:group + ref:staff + + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-220-jlewis685-add-alumni.xml b/model/model-intest/src/test/resources/async/ucf/change-220-jlewis685-add-alumni.xml new file mode 100644 index 00000000000..585bc893480 --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-220-jlewis685-add-alumni.xml @@ -0,0 +1,37 @@ + + + + + ri:AccountObjectClass + + jlewis685 + jlewis685 + + + modify + ShadowType + + add + attributes/ri:group + ref:alumni + + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-230-jlewis685-identifiers-only.xml b/model/model-intest/src/test/resources/async/ucf/change-230-jlewis685-identifiers-only.xml new file mode 100644 index 00000000000..5dec759e9ae --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-230-jlewis685-identifiers-only.xml @@ -0,0 +1,26 @@ + + + + ri:AccountObjectClass + + jlewis685 + jlewis685 + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-300-banderson-delete.xml b/model/model-intest/src/test/resources/async/ucf/change-300-banderson-delete.xml new file mode 100644 index 00000000000..2cf7f4e4ddb --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-300-banderson-delete.xml @@ -0,0 +1,30 @@ + + + + ri:AccountObjectClass + + banderson + + + delete + ShadowType + + diff --git a/model/model-intest/src/test/resources/async/ucf/change-310-staff-delete.xml b/model/model-intest/src/test/resources/async/ucf/change-310-staff-delete.xml new file mode 100644 index 00000000000..dfebb79de09 --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/change-310-staff-delete.xml @@ -0,0 +1,29 @@ + + + + ri:GroupObjectClass + + 00000000000000000000000000000003 + + + delete + + diff --git a/model/model-intest/src/test/resources/async/ucf/resource-grouper-ucf-internal.xml b/model/model-intest/src/test/resources/async/ucf/resource-grouper-ucf-internal.xml new file mode 100644 index 00000000000..921be67facd --- /dev/null +++ b/model/model-intest/src/test/resources/async/ucf/resource-grouper-ucf-internal.xml @@ -0,0 +1,217 @@ + + + + + + Grouper Resource + + + + connectorType + AsyncUpdateConnector + + + + + + + com.evolveum.midpoint.model.intest.async.MockAsyncUpdateSource + + + + + + + + + + + + + + icfs:uid + icfs:name + icfs:name + icfs:name + + + + + + + + + + + + + + + icfs:uid + icfs:name + icfs:name + icfs:name + + + + + + + + + + + + + + + + account + subject + ri:AccountObjectClass + true + + icfs:name + + + name + + + + + ri:group + + + organizationalUnit + + + + + + entitlement + group + ri:GroupObjectClass + true + + icfs:name + + + name + + + + + + + + true + account + subject + UserType + + + name + + + $account/attributes/name + + + + + + linked + true + + + deleted + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + true + entitlement + group + OrgType + + + name + + + $account/attributes/name + + + + + + linked + true + + + deleted + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#deleteFocus + + + + unlinked + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + + + + + + + + + passive + + diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index d788ae69c22..aed3895b39e 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -50,6 +50,7 @@ import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.path.*; +import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.TaskDebugUtil; @@ -288,7 +289,7 @@ public abstract class AbstractModelIntegrationTest extends AbstractIntegrationTe @Autowired protected SecurityContextManager securityContextManager; @Autowired protected MidpointFunctions libraryMidpointFunctions; @Autowired protected ValuePolicyProcessor valuePolicyProcessor; - + @Autowired(required = false) @Qualifier("modelObjectResolver") protected ObjectResolver modelObjectResolver; @@ -1792,6 +1793,28 @@ protected void assertNoObject(Class type, String oid, } } + protected void assertObjectByName(Class type, String name, Task task, OperationResult result) + throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, + ExpressionEvaluationException, ObjectNotFoundException { + SearchResultList> objects = modelService + .searchObjects(type, prismContext.queryFor(type).item(ObjectType.F_NAME).eqPoly(name).build(), null, task, + result); + if (objects.isEmpty()) { + fail("Expected that " + type + " " + name + " did exist but it did not"); + } + } + + protected void assertNoObjectByName(Class type, String name, Task task, OperationResult result) + throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, + ExpressionEvaluationException, ObjectNotFoundException { + SearchResultList> objects = modelService + .searchObjects(type, prismContext.queryFor(type).item(ObjectType.F_NAME).eqPoly(name).build(), null, task, + result); + if (!objects.isEmpty()) { + fail("Expected that " + type + " " + name + " did not exists but it did: " + objects); + } + } + protected void assertNoShadow(String username, PrismObject resource, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException { ObjectQuery query = createAccountShadowQuery(username, resource); @@ -5625,7 +5648,16 @@ protected OrgAsserter assertOrgAfter(String oid) throws ObjectNotFoundExce asserter.assertOid(oid); return asserter; } - + + protected OrgAsserter assertOrgByName(String name, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + PrismObject org = findObjectByName(OrgType.class, name); + assertNotNull("No org with name '"+name+"'", org); + OrgAsserter asserter = OrgAsserter.forOrg(org, message); + initializeAsserter(asserter); + asserter.assertName(name); + return asserter; + } + protected RoleAsserter assertRole(String oid, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { PrismObject role = getObject(RoleType.class, oid); RoleAsserter asserter = assertRole(role, message); diff --git a/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportService.java b/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportService.java index 7645f8e4afb..7db3b4fc761 100644 --- a/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportService.java +++ b/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportService.java @@ -46,6 +46,7 @@ Collection> searchObjects(ObjectQuery query, Collection> options) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; Collection> evaluateScript(String script, Map parameters) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; + Object evaluate(String script, Map parameters) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; Collection evaluateAuditScript(String script, Map parameters) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/JRMidpointCompiler.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/JRMidpointCompiler.java new file mode 100644 index 00000000000..25055d2fc00 --- /dev/null +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/JRMidpointCompiler.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010-2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.report.impl; + +import java.io.File; +import java.io.Serializable; + +import net.sf.jasperreports.crosstabs.JRCrosstab; +import net.sf.jasperreports.engine.JRDataset; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JasperReport; +import net.sf.jasperreports.engine.JasperReportsContext; +import net.sf.jasperreports.engine.design.JRAbstractCompiler; +import net.sf.jasperreports.engine.design.JRCompilationSourceCode; +import net.sf.jasperreports.engine.design.JRCompilationUnit; +import net.sf.jasperreports.engine.design.JRCompiler; +import net.sf.jasperreports.engine.design.JRSourceCompileTask; +import net.sf.jasperreports.engine.design.JasperDesign; +import net.sf.jasperreports.engine.fill.JREvaluator; + +/** + * @author katka + * + */ +public class JRMidpointCompiler extends JRAbstractCompiler { + + + /** + * @param jasperReportsContext + * @param needsSourceFiles + */ + public JRMidpointCompiler(JasperReportsContext jasperReportsContext) { + super(jasperReportsContext, false); + // TODO Auto-generated constructor stub + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRCompiler#loadEvaluator(net.sf.jasperreports.engine.JasperReport) + */ + @Override + public JREvaluator loadEvaluator(JasperReport jasperReport) throws JRException { + return new JRMidpointEvaluator(jasperReport); + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRCompiler#loadEvaluator(net.sf.jasperreports.engine.JasperReport, net.sf.jasperreports.crosstabs.JRCrosstab) + */ + @Override + public JREvaluator loadEvaluator(JasperReport jasperReport, JRCrosstab crosstab) throws JRException { + return new JRMidpointEvaluator(jasperReport); + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRCompiler#loadEvaluator(net.sf.jasperreports.engine.JasperReport, net.sf.jasperreports.engine.JRDataset) + */ + @Override + public JREvaluator loadEvaluator(JasperReport jasperReport, JRDataset dataset) throws JRException { + return new JRMidpointEvaluator(jasperReport, dataset); + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRAbstractCompiler#loadEvaluator(java.io.Serializable, java.lang.String) + */ + @Override + protected JREvaluator loadEvaluator(Serializable compileData, String unitName) throws JRException { + return new JRMidpointEvaluator(compileData, unitName); + + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRAbstractCompiler#checkLanguage(java.lang.String) + */ + @Override + protected void checkLanguage(String language) throws JRException { + if (!"midPoint".equals(language)) { + throw new JRException("asdasd"); + } + + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRAbstractCompiler#generateSourceCode(net.sf.jasperreports.engine.design.JRSourceCompileTask) + */ + @Override + protected JRCompilationSourceCode generateSourceCode(JRSourceCompileTask sourceTask) throws JRException { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRAbstractCompiler#compileUnits(net.sf.jasperreports.engine.design.JRCompilationUnit[], java.lang.String, java.io.File) + */ + @Override + protected String compileUnits(JRCompilationUnit[] units, String classpath, File tempDirFile) throws JRException { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see net.sf.jasperreports.engine.design.JRAbstractCompiler#getSourceFileName(java.lang.String) + */ + @Override + protected String getSourceFileName(String unitName) { + // TODO Auto-generated method stub + return unitName; + } + +} diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/JRMidpointEvaluator.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/JRMidpointEvaluator.java new file mode 100644 index 00000000000..e8562f7a211 --- /dev/null +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/JRMidpointEvaluator.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2010-2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.report.impl; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.xml.namespace.QName; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluator; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluatorFactory; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluatorFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.report.api.ReportService; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.ibm.icu.text.ChineseDateFormat.Field; + +import net.sf.jasperreports.engine.JRDataset; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JRExpression; +import net.sf.jasperreports.engine.JRExpressionChunk; +import net.sf.jasperreports.engine.JRField; +import net.sf.jasperreports.engine.JRRuntimeException; +import net.sf.jasperreports.engine.JasperReport; +import net.sf.jasperreports.engine.fill.ExpressionValues; +import net.sf.jasperreports.engine.fill.FillExpressionDefaultValues; +import net.sf.jasperreports.engine.fill.FillExpressionEstimatedValues; +import net.sf.jasperreports.engine.fill.FillExpressionOldValues; +import net.sf.jasperreports.engine.fill.JREvaluator; +import net.sf.jasperreports.engine.fill.JRExpressionEvalException; +import net.sf.jasperreports.engine.fill.JRFillField; +import net.sf.jasperreports.engine.fill.JRFillParameter; +import net.sf.jasperreports.engine.fill.JRFillVariable; + +/** + * @author katka + * + */ +public class JRMidpointEvaluator extends JREvaluator { + + private static final transient Trace LOGGER = TraceManager.getTrace(JRMidpointEvaluator.class); + + private Serializable compileData = null; + private String unitName = null; + + private ReportService reportService; + + private JasperReport jasperReport; + private JRDataset dataset; + + private Map parametersMap; + private Map fieldsMap; + private Map variablesMap; + + + public JRMidpointEvaluator(Serializable compileData, String unitName) { + this.compileData = compileData; + this.unitName = unitName; + } + + public JRMidpointEvaluator(JasperReport jasperReprot, JRDataset dataset) { + this.jasperReport = jasperReprot; + this.dataset = dataset; + } + + public JRMidpointEvaluator(JasperReport jasperReprot) { + this.jasperReport = jasperReprot; + } + + @Override + protected void customizedInit(Map parametersMap, Map fieldsMap, + Map variablesMap) throws JRException { + LOGGER.info("cutomized init: "); + LOGGER.info("parametersMap : {}", parametersMap); + LOGGER.info("fieldsMap : {}", fieldsMap); + LOGGER.info("variablesMap : {}", variablesMap); + + this.parametersMap = parametersMap; + this.fieldsMap = fieldsMap; + this.variablesMap = variablesMap; + + reportService = SpringApplicationContext.getBean(ReportService.class); + } + + @Override + public Object evaluate(JRExpression expression) throws JRExpressionEvalException { + if (expression == null) { + return null; + } + JRExpressionChunk[] ch = expression.getChunks(); + + Map parameters = new HashMap<>(); + + String groovyCode = ""; + + for (JRExpressionChunk chunk : expression.getChunks()) { + if (chunk == null) { + break; + } + + switch (chunk.getType()){ + case JRExpressionChunk.TYPE_FIELD: + groovyCode += chunk.getText(); + JRFillField field = fieldsMap.get(chunk.getText()); + parameters.put(new QName(field.getName()), field.getValue()); + break; + case JRExpressionChunk.TYPE_PARAMETER: + groovyCode += chunk.getText(); + JRFillParameter param = parametersMap.get(chunk.getText()); + parameters.put(new QName(param.getName()), param.getValue()); + break; + case JRExpressionChunk.TYPE_VARIABLE: + groovyCode += chunk.getText(); + JRFillVariable var = variablesMap.get(chunk.getText()); + parameters.put(new QName(var.getName()), var.getValue()); + break; + case JRExpressionChunk.TYPE_TEXT: + groovyCode += chunk.getText(); + break; + default : + groovyCode += chunk.getText(); + + } + + } + + + if (reportService != null) { + try { + return reportService.evaluate(groovyCode, parameters); + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException + | ConfigurationException | SecurityViolationException e) { + throw new JRRuntimeException(e.getMessage(), e); + } + } + + byte type = ch[0].getType(); + return "tralalal"; + } + + @Override + protected Object evaluate(int id) throws Throwable { + LOGGER.info("evaluate: {}", id); + return null; + + } + + @Override + protected Object evaluateOld(int id) throws Throwable { + LOGGER.info("evaluateOld: {}", id); + return null; + } + + @Override + protected Object evaluateEstimated(int id) throws Throwable { + LOGGER.info("evaluateEstimated: {}", id); + return null; + } + +} diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java index b09097c9a84..444ca120267 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java @@ -223,6 +223,36 @@ public Collection> evaluateScript(S return results; } + + @Override + public Object evaluate(String script, + Map parameters) throws SchemaException, ExpressionEvaluationException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + ExpressionVariables variables = new ExpressionVariables(); + variables.addVariableDefinitions(parameters); + + // special variable for audit report + variables.addVariableDefinition(new QName("auditParams"), getConvertedParams(parameters)); + + Task task = taskManager.createTaskInstance(ReportService.class.getName() + ".evaluateScript"); + OperationResult parentResult = task.getResult(); + + Collection functions = createFunctionLibraries(); + + Jsr223ScriptEvaluator scripts = new Jsr223ScriptEvaluator("Groovy", prismContext, + prismContext.getDefaultProtector(), localizationService); + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, task.getResult())); + Object o; + try{ + o = scripts.evaluateReportScript(script, variables, objectResolver, functions, "desc", + parentResult); + } finally{ + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + return o; + + } protected PrismContainerValue convertResultingObject(Object obj) { if (obj instanceof PrismObject) { diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/SpringApplicationContext.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/SpringApplicationContext.java new file mode 100644 index 00000000000..7415709be04 --- /dev/null +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/SpringApplicationContext.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.report.impl; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * @author katka + * + */ +@Component +public class SpringApplicationContext implements ApplicationContextAware { + + private static ApplicationContext appCtx; + + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + SpringApplicationContext.appCtx = applicationContext; + } + + public static T getBean(Class beanClass) { + return appCtx.getBean(beanClass); + } + +} diff --git a/model/report-impl/src/main/resources/jasperreports.properties b/model/report-impl/src/main/resources/jasperreports.properties index 5de5363fc1c..343149db9c4 100644 --- a/model/report-impl/src/main/resources/jasperreports.properties +++ b/model/report-impl/src/main/resources/jasperreports.properties @@ -1,3 +1,5 @@ net.sf.jasperreports.query.executer.factory.mql=com.evolveum.midpoint.report.impl.MidPointQueryExecutorFactory -net.sf.jasperreports.subreport.runner.factory=net.sf.jasperreports.engine.fill.JRContinuationSubreportRunnerFactory \ No newline at end of file +net.sf.jasperreports.subreport.runner.factory=net.sf.jasperreports.engine.fill.JRContinuationSubreportRunnerFactory + +net.sf.jasperreports.compiler.midPoint=com.evolveum.midpoint.report.impl.JRMidpointCompiler \ No newline at end of file diff --git a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java index c537de0d20b..e67542a2123 100644 --- a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java +++ b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java @@ -202,6 +202,23 @@ String addObject(PrismObject object, OperationProvisio int synchronize(ResourceShadowDiscriminator shadowCoordinates, Task task, TaskPartitionDefinitionType taskPartition, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException; + /** + * Starts listening for asynchronous updates for a given resource. + * Returns "listening activity handle" that will be used to stop the listening activity. + * + * Note that although it is possible to specify other parameters in addition to resource OID (e.g. objectClass), these + * settings are not supported now. + */ + String startListeningForAsyncUpdates(ResourceShadowDiscriminator shadowCoordinates, Task task, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + ExpressionEvaluationException; + + /** + * Stops the given listening activity. + */ + void stopListeningForAsyncUpdates(String listeningActivityHandle, Task task, OperationResult parentResult) + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + ExpressionEvaluationException; /** * Search for objects. Searches through all object types. Returns a list of @@ -218,7 +235,7 @@ int synchronize(ResourceShadowDiscriminator shadowCoordinates, Task task, TaskPa * @param query * search query * @param task - *@param parentResult + * @param parentResult * parent OperationResult (in/out) @return all objects of specified type that match search criteria (subject * to paging) * diff --git a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java index eaa72ee4634..4a51e94e29b 100644 --- a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java +++ b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java @@ -13,7 +13,7 @@ public class ResourceEventDescription implements Serializable, DebugDumpable{ private PrismObject oldShadow; private PrismObject currentShadow; - private ObjectDelta delta; + private ObjectDelta delta; private String sourceChannel; // private PrismObject resource; @@ -26,7 +26,7 @@ public PrismObject getOldShadow() { return oldShadow; } - public ObjectDelta getDelta() { + public ObjectDelta getDelta() { return delta; } diff --git a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventListener.java b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventListener.java index fc415ace0d9..000a09dfc94 100644 --- a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventListener.java +++ b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventListener.java @@ -15,21 +15,13 @@ */ package com.evolveum.midpoint.provisioning.api; -import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.exception.*; public interface ResourceEventListener extends ProvisioningListener { - public void notifyEvent(ResourceEventDescription eventDescription, Task task, OperationResult parentResult) throws SchemaException, CommunicationException, ConfigurationException, - SecurityViolationException, ObjectNotFoundException, GenericConnectorException, ObjectAlreadyExistsException, - ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException; + void notifyEvent(ResourceEventDescription eventDescription, Task task, OperationResult parentResult) throws SchemaException, + CommunicationException, ConfigurationException, SecurityViolationException, ObjectNotFoundException, + GenericConnectorException, ObjectAlreadyExistsException, ExpressionEvaluationException, PolicyViolationException; } diff --git a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectChangeListener.java b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectChangeListener.java index 4e91c549f61..b23b3bc8c20 100644 --- a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectChangeListener.java +++ b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectChangeListener.java @@ -47,6 +47,6 @@ public interface ResourceObjectChangeListener extends ProvisioningListener { * @param change * change description */ - public void notifyChange(ResourceObjectShadowChangeDescription change, Task task, OperationResult parentResult); + void notifyChange(ResourceObjectShadowChangeDescription change, Task task, OperationResult parentResult); } diff --git a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectShadowChangeDescription.java b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectShadowChangeDescription.java index bce915d7004..32433f096ce 100644 --- a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectShadowChangeDescription.java +++ b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceObjectShadowChangeDescription.java @@ -56,7 +56,7 @@ public class ResourceObjectShadowChangeDescription implements DebugDumpable, Ser * E.g. discovering that the object is missing, or a conflicting object already exists. * * It is expected that reactions to the unrelated changes will be lighter, faster, - * with lower overhead and without abmition to provide full synchronization. + * with lower overhead and without ambition to provide full synchronization. */ private boolean unrelatedChange = false; diff --git a/provisioning/provisioning-impl/pom.xml b/provisioning/provisioning-impl/pom.xml index 63bfdbddf79..5d74fa40b86 100644 --- a/provisioning/provisioning-impl/pom.xml +++ b/provisioning/provisioning-impl/pom.xml @@ -127,7 +127,7 @@ javax.annotation-api - + javax.xml.bind jaxb-api @@ -271,7 +271,28 @@ spring-aop test - + + org.apache.qpid + qpid-broker-core + test + + + org.apache.qpid + qpid-broker-plugins-amqp-0-8-protocol + test + + + org.apache.qpid + qpid-broker-plugins-memory-store + test + + + com.rabbitmq + amqp-client + ${rabbit-amqp-client.version} + test + + net.tirasa.connid diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/AsyncUpdateListeningRegistry.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/AsyncUpdateListeningRegistry.java new file mode 100644 index 00000000000..ca2a859d5d9 --- /dev/null +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/AsyncUpdateListeningRegistry.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.impl; + +import com.evolveum.midpoint.provisioning.ucf.api.ListeningActivity; +import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Keeps track of active async update listening activities. + * Externally visible methods are synchronized to ensure thread safety. + */ +@Component +public class AsyncUpdateListeningRegistry { + + private AtomicLong counter = new AtomicLong(0); + + private Map listeningActivities = new HashMap<>(); + + @NotNull + synchronized String registerListeningActivity(ListeningActivity activity) { + String handle = String.valueOf(counter.incrementAndGet()); + listeningActivities.put(handle, activity); + return handle; + } + + synchronized ListeningActivity getListeningActivity(@NotNull String handle) { + ListeningActivity listeningActivity = listeningActivities.get(handle); + if (listeningActivity == null) { + throw new IllegalArgumentException("Listening activity handle " + handle + " is unknown at this moment"); + } + listeningActivities.remove(handle); + return listeningActivity; + } +} diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ChangeNotificationDispatcherImpl.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ChangeNotificationDispatcherImpl.java index 6055ef698ee..ed76a033a30 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ChangeNotificationDispatcherImpl.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ChangeNotificationDispatcherImpl.java @@ -119,37 +119,26 @@ public void unregisterNotificationListener(ResourceEventListener listener) { } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.provisioning.api.ResourceObjectChangeNotificationManager#unregisterNotificationListener(com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener) - */ @Override public synchronized void unregisterNotificationListener(ResourceOperationListener listener) { changeListeners.remove(listener); } - /* (non-Javadoc) - * @see com.evolveum.midpoint.provisioning.api.ResourceObjectChangeNotificationManager#unregisterNotificationListener(com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener) - */ @Override public synchronized void unregisterNotificationListener(ResourceObjectChangeListener listener) { operationListeners.remove(listener); } - @Override public void notifyChange(ResourceObjectShadowChangeDescription change, Task task, OperationResult parentResult) { Validate.notNull(change, "Change description of resource object shadow must not be null."); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("SYNCHRONIZATION change notification\n{} ", change.debugDump()); - } + LOGGER.trace("SYNCHRONIZATION change notification\n{} ", change.debugDumpLazily()); if (InternalsConfig.consistencyChecks) change.checkConsistence(); if ((null != changeListeners) && (!changeListeners.isEmpty())) { for (ResourceObjectChangeListener listener : new ArrayList<>(changeListeners)) { // sometimes there is registration/deregistration from within - //LOGGER.trace("Listener: {}", listener.getClass().getSimpleName()); try { listener.notifyChange(change, task, parentResult); } catch (RuntimeException e) { @@ -165,17 +154,11 @@ public void notifyChange(ResourceObjectShadowChangeDescription change, Task task } } - /* (non-Javadoc) - * @see com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener#notifyFailure(com.evolveum.midpoint.provisioning.api.ResourceObjectShadowFailureDescription, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) - */ @Override - public void notifyFailure(ResourceOperationDescription failureDescription, - Task task, OperationResult parentResult) { + public void notifyFailure(ResourceOperationDescription failureDescription, Task task, OperationResult parentResult) { Validate.notNull(failureDescription, "Operation description of resource object shadow must not be null."); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Resource operation failure notification\n{} ", failureDescription.debugDump()); - } + LOGGER.trace("Resource operation failure notification\n{} ", failureDescription.debugDumpLazily()); failureDescription.checkConsistence(); @@ -185,8 +168,7 @@ public void notifyFailure(ResourceOperationDescription failureDescription, try { listener.notifyFailure(failureDescription, task, parentResult); } catch (RuntimeException e) { - LOGGER.error("Exception {} thrown by operation failure listener {}: {}-{}", new Object[]{ - e.getClass(), listener.getName(), e.getMessage(), e }); + LOGGER.error("Exception {} thrown by operation failure listener {}: {}-{}", e.getClass(), listener.getName(), e.getMessage(), e); parentResult.createSubresult(CLASS_NAME_WITH_DOT + "notifyFailure") .recordWarning("Operation failure listener has thrown unexpected exception", e); } @@ -196,17 +178,11 @@ public void notifyFailure(ResourceOperationDescription failureDescription, } } - /* (non-Javadoc) - * @see com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener#notifyFailure(com.evolveum.midpoint.provisioning.api.ResourceObjectShadowFailureDescription, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) - */ @Override - public void notifySuccess(ResourceOperationDescription successDescription, - Task task, OperationResult parentResult) { + public void notifySuccess(ResourceOperationDescription successDescription, Task task, OperationResult parentResult) { Validate.notNull(successDescription, "Operation description of resource object shadow must not be null."); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Resource operation success notification\n{} ", successDescription.debugDump()); - } + LOGGER.trace("Resource operation success notification\n{} ", successDescription.debugDumpLazily()); successDescription.checkConsistence(); @@ -216,8 +192,7 @@ public void notifySuccess(ResourceOperationDescription successDescription, try { listener.notifySuccess(successDescription, task, parentResult); } catch (RuntimeException e) { - LOGGER.error("Exception {} thrown by operation success listener {}: {}-{}", new Object[]{ - e.getClass(), listener.getName(), e.getMessage(), e }); + LOGGER.error("Exception {} thrown by operation success listener {}: {}-{}", e.getClass(), listener.getName(), e.getMessage(), e); parentResult.createSubresult(CLASS_NAME_WITH_DOT + "notifySuccess") .recordWarning("Operation success listener has thrown unexpected exception", e); } @@ -227,17 +202,12 @@ public void notifySuccess(ResourceOperationDescription successDescription, } } - /* (non-Javadoc) - * @see com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener#notifyFailure(com.evolveum.midpoint.provisioning.api.ResourceObjectShadowFailureDescription, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) - */ @Override public void notifyInProgress(ResourceOperationDescription inProgressDescription, Task task, OperationResult parentResult) { Validate.notNull(inProgressDescription, "Operation description of resource object shadow must not be null."); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Resource operation in-progress notification\n{} ", inProgressDescription.debugDump()); - } + LOGGER.trace("Resource operation in-progress notification\n{} ", inProgressDescription.debugDumpLazily()); inProgressDescription.checkConsistence(); @@ -247,8 +217,7 @@ public void notifyInProgress(ResourceOperationDescription inProgressDescription, try { listener.notifyInProgress(inProgressDescription, task, parentResult); } catch (RuntimeException e) { - LOGGER.error("Exception {} thrown by operation in-progress listener {}: {}-{}", new Object[]{ - e.getClass(), listener.getName(), e.getMessage(), e }); + LOGGER.error("Exception {} thrown by operation in-progress listener {}: {}-{}", e.getClass(), listener.getName(), e.getMessage(), e); parentResult.createSubresult(CLASS_NAME_WITH_DOT + "notifyInProgress") .recordWarning("Operation in-progress listener has thrown unexpected exception", e); } @@ -258,9 +227,6 @@ public void notifyInProgress(ResourceOperationDescription inProgressDescription, } } - /* (non-Javadoc) - * @see com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener#getName() - */ @Override public String getName() { return "object change notification dispatcher"; @@ -268,10 +234,9 @@ public String getName() { @Override public void notifyEvent(ResourceEventDescription eventDescription, - Task task, OperationResult parentResult) throws SchemaException, - CommunicationException, ConfigurationException, - SecurityViolationException, ObjectNotFoundException, - GenericConnectorException, ObjectAlreadyExistsException, ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException { + Task task, OperationResult parentResult) throws SchemaException, CommunicationException, ConfigurationException, + SecurityViolationException, ObjectNotFoundException, GenericConnectorException, ObjectAlreadyExistsException, + ExpressionEvaluationException, PolicyViolationException { Validate.notNull(eventDescription, "Event description must not be null."); if (LOGGER.isTraceEnabled()) { @@ -287,12 +252,10 @@ public void notifyEvent(ResourceEventDescription eventDescription, if ((null != eventListeners) && (!eventListeners.isEmpty())) { for (ResourceEventListener listener : new ArrayList<>(eventListeners)) { // sometimes there is registration/deregistration from within - //LOGGER.trace("Listener: {}", listener.getClass().getSimpleName()); try { listener.notifyEvent(eventDescription, task, parentResult); } catch (RuntimeException e) { - LOGGER.error("Exception {} thrown by event listener {}: {}-{}", new Object[]{ - e.getClass(), listener.getName(), e.getMessage(), e }); + LOGGER.error("Exception {} thrown by event listener {}: {}-{}", e.getClass(), listener.getName(), e.getMessage(), e); parentResult.createSubresult(CLASS_NAME_WITH_DOT + "notifyEvent") .recordWarning("Event listener has thrown unexpected exception", e); throw e; @@ -301,8 +264,5 @@ public void notifyEvent(ResourceEventDescription eventDescription, } else { LOGGER.warn("Event notification received but listener list is empty, there is nobody to get the message"); } - } - - } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/EntitlementConverter.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/EntitlementConverter.java index ffc192205a9..9fe9e924a26 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/EntitlementConverter.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/EntitlementConverter.java @@ -95,45 +95,43 @@ public void postProcessEntitlementsRead(ProvisioningContext subjectCtx, LOGGER.trace("Starting postProcessEntitlementRead"); RefinedObjectClassDefinition objectClassDefinition = subjectCtx.getObjectClassDefinition(); Collection entitlementAssociationDefs = objectClassDefinition.getAssociationDefinitions(); - if (entitlementAssociationDefs != null) { - ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(resourceObject); + ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(resourceObject); - PrismContainerDefinition associationDef = resourceObject.getDefinition().findContainerDefinition(ShadowType.F_ASSOCIATION); - PrismContainer associationContainer = associationDef.instantiate(); + PrismContainerDefinition associationDef = resourceObject.getDefinition().findContainerDefinition(ShadowType.F_ASSOCIATION); + PrismContainer associationContainer = associationDef.instantiate(); - for (RefinedAssociationDefinition assocDefType: entitlementAssociationDefs) { - ShadowKindType entitlementKind = assocDefType.getKind(); - if (entitlementKind == null) { - entitlementKind = ShadowKindType.ENTITLEMENT; + for (RefinedAssociationDefinition assocDefType: entitlementAssociationDefs) { + ShadowKindType entitlementKind = assocDefType.getKind(); + if (entitlementKind == null) { + entitlementKind = ShadowKindType.ENTITLEMENT; + } + for (String entitlementIntent: assocDefType.getIntents()) { + LOGGER.trace("Resolving association {} for kind {} and intent {}", assocDefType.getName(), entitlementKind, entitlementIntent); + ProvisioningContext entitlementCtx = subjectCtx.spawn(entitlementKind, entitlementIntent); + RefinedObjectClassDefinition entitlementDef = entitlementCtx.getObjectClassDefinition(); + if (entitlementDef == null) { + throw new SchemaException("No definition for entitlement intent(s) '"+assocDefType.getIntents()+"' in "+resourceType); } - for (String entitlementIntent: assocDefType.getIntents()) { - LOGGER.trace("Resolving association {} for kind {} and intent {}", assocDefType.getName(), entitlementKind, entitlementIntent); - ProvisioningContext entitlementCtx = subjectCtx.spawn(entitlementKind, entitlementIntent); - RefinedObjectClassDefinition entitlementDef = entitlementCtx.getObjectClassDefinition(); - if (entitlementDef == null) { - throw new SchemaException("No definition for entitlement intent(s) '"+assocDefType.getIntents()+"' in "+resourceType); - } - ResourceObjectAssociationDirectionType direction = assocDefType.getResourceObjectAssociationType().getDirection(); - if (direction == ResourceObjectAssociationDirectionType.SUBJECT_TO_OBJECT) { - postProcessEntitlementSubjectToEntitlement(resourceType, resourceObject, objectClassDefinition, assocDefType, entitlementDef, attributesContainer, associationContainer, parentResult); - } else if (direction == ResourceObjectAssociationDirectionType.OBJECT_TO_SUBJECT) { - if (assocDefType.getResourceObjectAssociationType().getShortcutAssociationAttribute() != null) { - postProcessEntitlementSubjectToEntitlement(resourceType, resourceObject, objectClassDefinition, - assocDefType, entitlementDef, attributesContainer, associationContainer, - assocDefType.getResourceObjectAssociationType().getShortcutAssociationAttribute(), - assocDefType.getResourceObjectAssociationType().getShortcutValueAttribute(), parentResult); - } else { - postProcessEntitlementEntitlementToSubject(subjectCtx, resourceObject, assocDefType, entitlementCtx, attributesContainer, associationContainer, parentResult); - } + ResourceObjectAssociationDirectionType direction = assocDefType.getResourceObjectAssociationType().getDirection(); + if (direction == ResourceObjectAssociationDirectionType.SUBJECT_TO_OBJECT) { + postProcessEntitlementSubjectToEntitlement(resourceType, resourceObject, objectClassDefinition, assocDefType, entitlementDef, attributesContainer, associationContainer, parentResult); + } else if (direction == ResourceObjectAssociationDirectionType.OBJECT_TO_SUBJECT) { + if (assocDefType.getResourceObjectAssociationType().getShortcutAssociationAttribute() != null) { + postProcessEntitlementSubjectToEntitlement(resourceType, resourceObject, objectClassDefinition, + assocDefType, entitlementDef, attributesContainer, associationContainer, + assocDefType.getResourceObjectAssociationType().getShortcutAssociationAttribute(), + assocDefType.getResourceObjectAssociationType().getShortcutValueAttribute(), parentResult); } else { - throw new IllegalArgumentException("Unknown entitlement direction "+direction+" in association "+assocDefType+" in "+resourceType); + postProcessEntitlementEntitlementToSubject(subjectCtx, resourceObject, assocDefType, entitlementCtx, attributesContainer, associationContainer, parentResult); } + } else { + throw new IllegalArgumentException("Unknown entitlement direction "+direction+" in association "+assocDefType+" in "+resourceType); } } + } - if (!associationContainer.isEmpty()) { - resourceObject.add(associationContainer); - } + if (!associationContainer.isEmpty()) { + resourceObject.add(associationContainer); } } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java index cf461f49fc9..57c69618dfd 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java @@ -31,10 +31,7 @@ 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.CapabilitiesType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CapabilityType; import org.jetbrains.annotations.NotNull; @@ -338,4 +335,10 @@ public PrismContext getPrismContext() { public ItemPath path(Object... components) { return ItemPath.create(components); } + + public CachingStategyType getCachingStrategy() + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + return ProvisioningUtil.getCachingStrategy(this); + } } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java index 04b3ae051f8..57c23dde22a 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java @@ -26,6 +26,7 @@ import com.evolveum.midpoint.prism.delta.ItemDeltaCollectionsUtil; import com.evolveum.midpoint.prism.path.ItemPath; import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Primary; @@ -385,6 +386,51 @@ public int synchronize(ResourceShadowDiscriminator shadowCoordinates, Task task, } + @Override + public String startListeningForAsyncUpdates(@NotNull ResourceShadowDiscriminator shadowCoordinates, @NotNull Task task, + @NotNull OperationResult parentResult) + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + String resourceOid = shadowCoordinates.getResourceOid(); + Validate.notNull(resourceOid, "Resource oid must not be null."); + + OperationResult result = parentResult.createSubresult(ProvisioningService.class.getName() + ".startListeningForAsyncUpdates"); + result.addParam(OperationResult.PARAM_OID, resourceOid); + result.addParam(OperationResult.PARAM_TASK, task.toString()); + + String listeningActivityHandle; + try { + LOGGER.trace("Starting listening for async updates for {}", shadowCoordinates); + listeningActivityHandle = shadowCache.startListeningForAsyncUpdates(shadowCoordinates, task, result); + } catch (ObjectNotFoundException | CommunicationException | SchemaException | ConfigurationException | ExpressionEvaluationException | RuntimeException | Error e) { + ProvisioningUtil.recordFatalError(LOGGER, result, null, e); + result.summarize(true); + throw e; + } + result.addReturn("listeningActivityHandle", listeningActivityHandle); + result.recordSuccess(); + result.cleanupResult(); + return listeningActivityHandle; + } + + @Override + public void stopListeningForAsyncUpdates(@NotNull String listeningActivityHandle, Task task, OperationResult parentResult) { + OperationResult result = parentResult.createSubresult(ProvisioningService.class.getName() + ".stopListeningForAsyncUpdates"); + result.addParam("listeningActivityHandle", listeningActivityHandle); + result.addParam(OperationResult.PARAM_TASK, task.toString()); + + try { + LOGGER.trace("Stopping listening for async updates for {}", listeningActivityHandle); + shadowCache.stopListeningForAsyncUpdates(listeningActivityHandle, task, result); + } catch (RuntimeException | Error e) { + ProvisioningUtil.recordFatalError(LOGGER, result, null, e); + result.summarize(true); + throw e; + } + result.recordSuccess(); + result.cleanupResult(); + } + @SuppressWarnings("rawtypes") private PrismProperty getTokenProperty(ResourceShadowDiscriminator shadowCoordinates, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, ExpressionEvaluationException { @@ -793,7 +839,7 @@ public OperationResult testResource(String resourceOid, Task task) throws Object testResult.addParam("resourceOid", resourceOid); testResult.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, ProvisioningServiceImpl.class); - PrismObject resource = null; + PrismObject resource; try { resource = getRepoObject(ResourceType.class, resourceOid, null, testResult); diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java index f6152560d1a..90982e9d6b5 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java @@ -21,6 +21,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CachingStategyType; import org.apache.commons.lang.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -79,46 +80,48 @@ public String getName() { } @Override - public void notifyEvent(ResourceEventDescription eventDescription, Task task, OperationResult parentResult) throws SchemaException, CommunicationException, ConfigurationException, - SecurityViolationException, ObjectNotFoundException, GenericConnectorException, ObjectAlreadyExistsException, - ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException { + public void notifyEvent(ResourceEventDescription eventDescription, Task task, OperationResult parentResult) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ObjectNotFoundException, GenericConnectorException, ObjectAlreadyExistsException, + ExpressionEvaluationException, PolicyViolationException { Validate.notNull(eventDescription, "Event description must not be null."); Validate.notNull(task, "Task must not be null."); Validate.notNull(parentResult, "Operation result must not be null"); - LOGGER.trace("Received event notification with the description: {}", eventDescription.debugDump()); + LOGGER.trace("Received event notification with the description: {}", eventDescription.debugDumpLazily()); - if (eventDescription.getCurrentShadow() == null && eventDescription.getDelta() == null){ + if (eventDescription.getCurrentShadow() == null && eventDescription.getDelta() == null) { throw new IllegalStateException("Neither current shadow, nor delta specified. It is required to have at least one of them specified."); } applyDefinitions(eventDescription, parentResult); - PrismObject shadow = null; - - shadow = eventDescription.getShadow(); - + PrismObject shadow = eventDescription.getShadow(); ProvisioningContext ctx = provisioningContextFactory.create(shadow, task, parentResult); ctx.assertDefinition(); Collection> identifiers = ShadowUtil.getPrimaryIdentifiers(shadow); + // TODO reconsider this + if (ctx.getCachingStrategy() == CachingStategyType.PASSIVE) { + if (eventDescription.getCurrentShadow() == null && eventDescription.getOldShadow() != null && eventDescription.getDelta() != null) { + PrismObject newShadow = eventDescription.getOldShadow().clone(); + eventDescription.getDelta().applyTo(newShadow); + eventDescription.setCurrentShadow(newShadow); + } + } + Change change = new Change(identifiers, eventDescription.getCurrentShadow(), eventDescription.getOldShadow(), eventDescription.getDelta()); - ObjectClassComplexTypeDefinition objectClassDefinition = ShadowUtil.getObjectClassDefinition(shadow); - change.setObjectClassDefinition(objectClassDefinition); + change.setObjectClassDefinition(ShadowUtil.getObjectClassDefinition(shadow)); - LOGGER.trace("Start to precess change: {}", change.toString()); + LOGGER.trace("Starting to synchronize change: {}", change); try { - shadowCache.processChange(ctx, change, null, parentResult); + shadowCache.processSynchronization(ctx, false, change, task, null, parentResult); } catch (EncryptionException e) { // TODO: better handling throw new SystemException(e.getMessage(), e); } - - LOGGER.trace("Change after processing {} . Start synchronizing.", change.toString()); - shadowCache.processSynchronization(ctx, false, change, task, null, parentResult); - } private void applyDefinitions(ResourceEventDescription eventDescription, @@ -131,7 +134,7 @@ private void applyDefinitions(ResourceEventDescription eventDescription, shadowCache.applyDefinition(eventDescription.getOldShadow(), parentResult); } - if (eventDescription.getDelta() != null){ + if (eventDescription.getDelta() != null) { shadowCache.applyDefinition(eventDescription.getDelta(), null, parentResult); } } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java index c01b0669181..ada1cd56d81 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java @@ -32,36 +32,25 @@ import com.evolveum.midpoint.provisioning.util.ProvisioningUtil; import com.evolveum.midpoint.repo.cache.RepositoryCache; import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.processor.*; -import com.evolveum.midpoint.schema.result.AsynchronousOperationQueryable; -import com.evolveum.midpoint.schema.result.AsynchronousOperationResult; -import com.evolveum.midpoint.schema.result.AsynchronousOperationReturnValue; -import com.evolveum.midpoint.schema.result.OperationConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.schema.result.*; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.schema.util.SchemaDebugUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.util.*; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.Holder; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.PrettyPrinter; 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.*; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationLockoutStatusCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationStatusCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.AddRemoveAttributeValuesCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CreateCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.DeleteCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.LiveSyncCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ReadCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.UpdateCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.*; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; - import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; @@ -108,6 +97,7 @@ public class ResourceObjectConverter { @Autowired private Clock clock; @Autowired private PrismContext prismContext; @Autowired private RelationRegistry relationRegistry; + @Autowired private AsyncUpdateListeningRegistry listeningRegistry; private static final Trace LOGGER = TraceManager.getTrace(ResourceObjectConverter.class); @@ -1798,7 +1788,7 @@ public List fetchChanges(ProvisioningContext ctx, PrismProperty lastT Iterator iterator = changes.iterator(); while (iterator.hasNext()) { Change change = iterator.next(); - LOGGER.trace("Original change:\n{}", change.debugDump()); + LOGGER.trace("Original change:\n{}", change.debugDumpLazily()); if (change.isTokenOnly()) { continue; } @@ -1814,7 +1804,7 @@ public List fetchChanges(ProvisioningContext ctx, PrismProperty lastT if (ctx.isWildcard() && changeObjectClassDefinition != null) { shadowCtx = ctx.spawn(changeObjectClassDefinition.getTypeName()); if (shadowCtx.isWildcard()) { - String message = "Unkown object class "+changeObjectClassDefinition.getTypeName()+" found in synchronization delta"; + String message = "Unknown object class "+changeObjectClassDefinition.getTypeName()+" found in synchronization delta"; parentResult.recordFatalError(message); throw new SchemaException(message); } @@ -1827,12 +1817,11 @@ public List fetchChanges(ProvisioningContext ctx, PrismProperty lastT if (currentShadow == null) { // There is no current shadow in a change. Add it by fetching it explicitly. try { - + // TODO maybe we can postpone this fetch to ShadowCache.preProcessChange where it is implemented [pmed] LOGGER.trace("Re-fetching object {} because it is not in the change", change.getIdentifiers()); currentShadow = fetchResourceObject(shadowCtx, change.getIdentifiers(), shadowAttrsToReturn, true, parentResult); // todo consider whether it is always necessary to fetch the entitlements change.setCurrentShadow(currentShadow); - } catch (ObjectNotFoundException ex) { parentResult.recordHandledError( "Object detected in change log no longer exist on the resource. Skipping processing this object.", ex); @@ -1852,7 +1841,6 @@ public List fetchChanges(ProvisioningContext ctx, PrismProperty lastT LOGGER.trace("Re-fetching object {} because of attrsToReturn", identification); currentShadow = connector.fetchObject(identification, shadowAttrsToReturn, ctx, parentResult); } - } PrismObject processedCurrentShadow = postProcessResourceObjectRead(shadowCtx, @@ -1868,7 +1856,77 @@ public List fetchChanges(ProvisioningContext ctx, PrismProperty lastT LOGGER.trace("END fetch changes ({} changes)", changes == null ? "null" : changes.size()); return changes; } - + + String startListeningForAsyncUpdates(@NotNull ProvisioningContext ctx, + @NotNull ChangeListener outerListener, @NotNull OperationResult parentResult) throws SchemaException, + CommunicationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException { + + LOGGER.trace("START start listening for async updates, objectClass: {}", ctx.getObjectClassDefinition()); + ConnectorInstance connector = ctx.getConnector(AsyncUpdateCapabilityType.class, parentResult); + + ChangeListener innerListener = change -> { + try { + LOGGER.trace("Start processing change:\n{}", change.debugDumpLazily()); + setResourceOidIfMissing(change, ctx.getResourceOid()); + ProvisioningContext shadowCtx = ctx; + ObjectClassComplexTypeDefinition changeObjectClassDefinition = change.getObjectClassDefinition(); + if (changeObjectClassDefinition == null) { + if (!ctx.isWildcard() || change.getObjectDelta() == null || !change.getObjectDelta().isDelete()) { + throw new SchemaException("No object class definition in change "+change); + } + } + if (ctx.isWildcard() && changeObjectClassDefinition != null) { + shadowCtx = ctx.spawn(changeObjectClassDefinition.getTypeName()); + if (shadowCtx.isWildcard()) { + String message = "Unknown object class " + changeObjectClassDefinition.getTypeName() + + " found in synchronization delta"; + throw new SchemaException(message); + } + change.setObjectClassDefinition(shadowCtx.getObjectClassDefinition()); + } + + if (change.getCurrentShadow() != null) { + shadowCaretaker.applyAttributesDefinition(ctx, change.getCurrentShadow()); + PrismObject processedCurrentShadow = postProcessResourceObjectRead(shadowCtx, + change.getCurrentShadow(), true, parentResult); + change.setCurrentShadow(processedCurrentShadow); + } else { + // we will fetch current shadow later + } + return outerListener.onChange(change); + } catch (Throwable t) { + throw new SystemException("Couldn't process async update: " + t.getMessage(), t); + } + }; + ListeningActivity listeningActivity = connector.startListeningForChanges(innerListener, parentResult); + String handle = listeningRegistry.registerListeningActivity(listeningActivity); + computeResultStatus(parentResult); + + LOGGER.trace("END start listening for async updates; listening handle = {}", handle); + return handle; + } + + private void setResourceOidIfMissing(Change change, String resourceOid) { + setResourceOidIfMissing(change.getOldShadow(), resourceOid); + setResourceOidIfMissing(change.getCurrentShadow(), resourceOid); + if (change.getObjectDelta() != null) { + setResourceOidIfMissing(change.getObjectDelta().getObjectToAdd(), resourceOid); + } + } + + private void setResourceOidIfMissing(PrismObject object, String resourceOid) { + if (object != null && object.asObjectable().getResourceRef() == null && resourceOid != null) { + object.asObjectable().setResourceRef(ObjectTypeUtil.createObjectRef(resourceOid, ObjectTypes.RESOURCE)); + } + } + + void stopListeningForAsyncUpdates(@NotNull String listeningActivityHandle, @NotNull OperationResult parentResult) { + LOGGER.trace("START stop listening for async updates, handle: {}", listeningActivityHandle); + listeningRegistry.getListeningActivity(listeningActivityHandle).stop(); + computeResultStatus(parentResult); + LOGGER.trace("END stop listening for async updates"); + } + /** * Process simulated activation, credentials and other properties that are added to the object by midPoint. */ diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java index c81c1ed8f2d..27dfce0a293 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java @@ -31,10 +31,7 @@ import com.evolveum.midpoint.provisioning.api.*; import com.evolveum.midpoint.provisioning.impl.errorhandling.ErrorHandler; import com.evolveum.midpoint.provisioning.impl.errorhandling.ErrorHandlerLocator; -import com.evolveum.midpoint.provisioning.ucf.api.Change; -import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; -import com.evolveum.midpoint.provisioning.ucf.api.ConnectorOperationOptions; -import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; +import com.evolveum.midpoint.provisioning.ucf.api.*; import com.evolveum.midpoint.provisioning.util.ProvisioningUtil; import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepositoryService; @@ -77,6 +74,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -99,8 +97,8 @@ * process. * * The two principal classes that do the operations are: - * ResourceObjectConvertor: executes operations on resource ShadowManager: - * executes operations in the repository + * ResourceObjectConvertor: executes operations on resource + * ShadowManager: executes operations in the repository * * @author Radovan Semancik * @author Katarina Valalikova @@ -153,7 +151,7 @@ public PrismContext getPrismContext() { return prismContext; } - public PrismObject getShadow(String oid, PrismObject repositoryShadow, + PrismObject getShadow(String oid, PrismObject repositoryShadow, Collection> options, Task task, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, EncryptionException { @@ -289,7 +287,7 @@ public PrismObject getShadow(String oid, PrismObject rep return resultShadow; } else { - LOGGER.trace("{} was not found, following normal error processing beacuse shadow is in {} state", repositoryShadow, shadowState); + LOGGER.trace("{} was not found, following normal error processing because shadow is in {} state", repositoryShadow, shadowState); // This is live shadow that was not found on resource. Just re-throw the exception. It will // be caught later and the usual error handlers will bury the shadow. throw e; @@ -317,8 +315,8 @@ public PrismObject getShadow(String oid, PrismObject rep LOGGER.trace("Resource object fetched from resource:\n{}", resourceShadow.debugDump(1)); } - repositoryShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, repositoryShadow, shadowState, - parentResult); + repositoryShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, repositoryShadow, + shadowState, parentResult); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Repository shadow after update:\n{}", repositoryShadow.debugDump(1)); } @@ -383,7 +381,7 @@ private PrismObject processNoFetchGet(ProvisioningContext ctx, if (!GetOperationOptions.isRaw(rootOptions)) { // Even with noFetch we still want to delete expired pending operations. And even delete // the shadow if needed. - repositoryShadow = refreshShadowQick(ctx, repositoryShadow, now, task, parentResult); + repositoryShadow = refreshShadowQuick(ctx, repositoryShadow, now, task, parentResult); } if (repositoryShadow == null) { @@ -1309,7 +1307,7 @@ public PrismObject refreshShadow(PrismObject repoShadow, /** * Used to quickly and efficiently refresh shadow before GET operations. */ - private PrismObject refreshShadowQick(ProvisioningContext ctx, + private PrismObject refreshShadowQuick(ProvisioningContext ctx, PrismObject repoShadow, XMLGregorianCalendar now, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, EncryptionException { @@ -1889,7 +1887,7 @@ public SearchResultMetadata searchObjectsIterative(final ProvisioningContext ctx objResult.recordFatalError("Configuration error: " + e.getMessage(), e); LOGGER.error("Configuration error: {}", e.getMessage(), e); return false; - } catch (ObjectNotFoundException | ObjectAlreadyExistsException | CommunicationException + } catch (ObjectNotFoundException | CommunicationException | SecurityViolationException | GenericConnectorException | ExpressionEvaluationException | EncryptionException e) { objResult.recordFatalError(e.getMessage(), e); LOGGER.error("{}", e.getMessage(), e); @@ -1951,7 +1949,7 @@ public SearchResultMetadata searchObjectsIterative(final ProvisioningContext ctx } - ObjectQuery createAttributeQuery(ObjectQuery query) throws SchemaException { + private ObjectQuery createAttributeQuery(ObjectQuery query) throws SchemaException { QueryFactory queryFactory = prismContext.queryFactory(); ObjectFilter filter = null; @@ -2334,15 +2332,13 @@ public boolean handle(PrismObject shadow, OperationResult objResult) public int synchronize(ResourceShadowDiscriminator shadowCoordinates, PrismProperty lastToken, Task task, TaskPartitionDefinitionType partition, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, GenericFrameworkException, SchemaException, ConfigurationException, - SecurityViolationException, ObjectAlreadyExistsException, ExpressionEvaluationException, EncryptionException, PolicyViolationException, PreconditionViolationException { + SecurityViolationException, ObjectAlreadyExistsException, ExpressionEvaluationException, EncryptionException, PolicyViolationException { InternalMonitor.recordCount(InternalCounters.PROVISIONING_ALL_EXT_OPERATION_COUNT); final ProvisioningContext ctx = ctxFactory.create(shadowCoordinates, task, parentResult); - boolean isSimulate = false; - if (partition != null && ExecutionModeType.SIMULATE == partition.getStage()) { - isSimulate = true; - } + boolean isSimulate = partition != null && partition.getStage() == ExecutionModeType.SIMULATE; + boolean retryUnhandledError = !Boolean.FALSE.equals(task.getExtensionPropertyRealValue(SchemaConstants.SYNC_TOKEN_RETRY_UNHANDLED)); List changes; try { @@ -2363,71 +2359,18 @@ public int synchronize(ResourceShadowDiscriminator shadowCoordinates, PrismPrope continue; } - ObjectClassComplexTypeDefinition changeObjectClassDefinition = change.getObjectClassDefinition(); - - ProvisioningContext shadowCtx; - PrismObject oldShadow = null; - if (changeObjectClassDefinition == null) { - if (change.getObjectDelta() != null && change.getObjectDelta().isDelete()) { - oldShadow = change.getOldShadow(); - if (oldShadow == null) { - oldShadow = shadowManager.findOrAddShadowFromChangeGlobalContext(ctx, change, - parentResult); - } - if (oldShadow == null) { - LOGGER.debug( - "No old shadow for delete synchronization event {}, we probably did not know about that object anyway, so well be ignoring this event", - change); - continue; - } - shadowCtx = ctx.spawn(oldShadow); - } else { - throw new SchemaException("No object class definition in change " + change); - } - } else { - shadowCtx = ctx.spawn(changeObjectClassDefinition.getTypeName()); - } + boolean isSuccess = processSynchronization(ctx, isSimulate, change, task, partition, parentResult); - processChange(shadowCtx, change, oldShadow, parentResult); - - // this is the case,when we want to skip processing of change, - // because the shadow was not created or found to the resource - // object - // it may be caused with the fact, that the object which was - // created in the resource was deleted before the sync run - // such a change should be skipped to process consistent changes - if (change.getOldShadow() == null) { - PrismProperty newToken = change.getToken(); - task.setExtensionProperty(newToken); - processedChanges++; - if (task instanceof RunningTask) { - ((RunningTask) task).incrementProgressAndStoreStatsIfNeeded(); - } - LOGGER.debug( - "Skipping processing change. Can't find appropriate shadow (e.g. the object was deleted on the resource meantime)."); - continue; - } - boolean isSuccess = processSynchronization(shadowCtx, isSimulate, change, task, partition, parentResult); - - boolean retryUnhandledError = true; - if (task.getExtension() != null) { - PrismProperty tokenRetryUnhandledErrProperty = task.getExtensionProperty(SchemaConstants.SYNC_TOKEN_RETRY_UNHANDLED); - - if (tokenRetryUnhandledErrProperty != null) { - retryUnhandledError = (boolean) tokenRetryUnhandledErrProperty.getRealValue(); - } - } - - if (!retryUnhandledError || isSuccess) { + if (isSuccess || !retryUnhandledError) { if (!isSimulate) { - // get updated token from change, create property modification from new token and replace old token with the new one task.setExtensionProperty(change.getToken()); } - processedChanges++; if (task instanceof RunningTask) { ((RunningTask) task).incrementProgressAndStoreStatsIfNeeded(); } + } else { + // we need to retry the failed change -- so we must not update the token } } @@ -2446,33 +2389,83 @@ public int synchronize(ResourceShadowDiscriminator shadowCoordinates, PrismPrope } } - @SuppressWarnings("rawtypes") - boolean processSynchronization(ProvisioningContext ctx, boolean isSimulate, Change change, Task task, TaskPartitionDefinitionType partition, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, - CommunicationException, ConfigurationException, ExpressionEvaluationException, SecurityViolationException, PolicyViolationException, PreconditionViolationException { + String startListeningForAsyncUpdates(ResourceShadowDiscriminator shadowCoordinates, Task task, OperationResult parentResult) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, ExpressionEvaluationException { + InternalMonitor.recordCount(InternalCounters.PROVISIONING_ALL_EXT_OPERATION_COUNT); + + ProvisioningContext ctx = ctxFactory.create(shadowCoordinates, task, parentResult); + ChangeListener listener = change -> { + try { + boolean success = processSynchronization(ctx, false, change, task, null, parentResult); + if (task instanceof RunningTask) { + ((RunningTask) task).incrementProgressAndStoreStatsIfNeeded(); + } + return success; + } catch (Throwable t) { + throw new SystemException("Couldn't process async update: " + t.getMessage(), t); + } + }; + return resouceObjectConverter.startListeningForAsyncUpdates(ctx, listener, parentResult); + } + + void stopListeningForAsyncUpdates(String listeningActivityHandle, Task task, OperationResult parentResult) { + InternalMonitor.recordCount(InternalCounters.PROVISIONING_ALL_EXT_OPERATION_COUNT); + resouceObjectConverter.stopListeningForAsyncUpdates(listeningActivityHandle, parentResult); + } + + /** + * This is the common code for processing changes coming from various sources e.g. live sync, async updates + * or model.notifyChange. Therefore all the auxiliary pre-processing (finding old shadow, determining object class, + * etc) is also placed here. + */ + boolean processSynchronization(ProvisioningContext globalCtx, boolean isSimulate, Change change, Task task, + TaskPartitionDefinitionType partition, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, + ObjectAlreadyExistsException, CommunicationException, ConfigurationException, ExpressionEvaluationException, + SecurityViolationException, PolicyViolationException, EncryptionException { + + ProvisioningContext ctx = determineProvisioningContext(globalCtx, change, parentResult); + if (ctx == null) { + return true; + } + + if (change.getObjectDelta() != null) { + shadowCaretaker.applyAttributesDefinition(ctx, change.getObjectDelta()); + } + if (change.getOldShadow() != null) { + shadowCaretaker.applyAttributesDefinition(ctx, change.getOldShadow()); + } + if (change.getCurrentShadow() != null) { + shadowCaretaker.applyAttributesDefinition(ctx, change.getCurrentShadow()); // maybe redundant + } + + preProcessChange(ctx, change, parentResult); + + // This is the case when we want to skip processing of change because the shadow was not found nor created. + // The usual reason is that this is the delete delta and the object was already deleted on both resource and in repo. + if (change.getOldShadow() == null) { + assert change.isDelete(); + LOGGER.debug("Skipping processing change. Can't find appropriate shadow (e.g. the object was " + + "deleted on the resource meantime)."); + return true; + } OperationResult result = parentResult.createSubresult(OP_PROCESS_SYNCHRONIZATION); - boolean successfull = false; + boolean successful; try { ResourceObjectShadowChangeDescription shadowChangeDescription = createResourceShadowChangeDescription( change, ctx.getResource(), ctx.getChannel()); shadowChangeDescription.setSimulate(isSimulate); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Created resource object shadow change description {}", - SchemaDebugUtil.prettyPrint(shadowChangeDescription)); - } - OperationResult notifyChangeResult = result.createMinorSubresult( - ShadowCache.class.getName() + ".notifyChange"); + + LOGGER.trace("Created resource object shadow change description {}", + SchemaDebugUtil.prettyPrintLazily(shadowChangeDescription)); + OperationResult notifyChangeResult = result.createMinorSubresult(ShadowCache.class.getName() + ".notifyChange"); notifyChangeResult.addParam("resourceObjectShadowChangeDescription", shadowChangeDescription.toString()); try { notifyResourceObjectChangeListeners(shadowChangeDescription, ctx.getTask(), notifyChangeResult); notifyChangeResult.computeStatusIfUnknown(); } catch (RuntimeException ex) { - // recordFatalError(LOGGER, notifyChangeResult, "Synchronization - // error: " + ex.getMessage(), ex); saveAccountResult(shadowChangeDescription, change, notifyChangeResult, result); throw new SystemException("Synchronization error: " + ex.getMessage(), ex); } @@ -2488,46 +2481,84 @@ boolean processSynchronization(ProvisioningContext ctx, boolean isSimulate, Chan // And we need to modify ResourceObjectChangeListener for that. Keeping all dead // shadows is much easier. // deleteShadowFromRepoIfNeeded(change, result); - successfull = true; - + successful = true; } else { - successfull = false; + successful = false; saveAccountResult(shadowChangeDescription, change, notifyChangeResult, result); } if (result.isUnknown()) { result.computeStatus(); } - - validateResult(notifyChangeResult, task, partition); - + + try { + validateResult(notifyChangeResult, task, partition); + } catch (PreconditionViolationException e) { + throw new SystemException(e.getMessage(), e); + } + } catch (SchemaException | ObjectNotFoundException | ObjectAlreadyExistsException | CommunicationException | ConfigurationException | ExpressionEvaluationException | RuntimeException | Error e) { result.recordFatalError(e); throw e; } - - - return successfull; + + + return successful; } - private void validateResult(OperationResult result, Task task, TaskPartitionDefinitionType partition) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { - + /** + * Returns null if the context cannot be determined because the change is DELETE and there's no shadow in repo. + * + * As a side effect it may fill-in change.oldShadow. + */ + @Nullable + private ProvisioningContext determineProvisioningContext(ProvisioningContext globalCtx, Change change, + OperationResult parentResult) + throws SchemaException, CommunicationException, ConfigurationException, ObjectNotFoundException, + ExpressionEvaluationException, EncryptionException { + if (change.getObjectClassDefinition() == null) { + if (!change.isDelete()) { + throw new SchemaException("No object class definition in change " + change); + } else { + // A specialty for DELETE changes: we try to determine the object class if not specified in the change + // It is needed e.g. for some LDAP servers doing live synchronization. + if (change.getOldShadow() == null) { + PrismObject existing = shadowManager.findOrAddShadowFromChange(globalCtx, change, parentResult); + if (existing == null) { + LOGGER.debug("No old shadow for delete synchronization event {}, we probably did not know about " + + "that object anyway, so well be ignoring this event", change); + return null; + } else { + change.setOldShadow(existing); + } + } + return globalCtx.spawn(change.getOldShadow()); + } + } else { + return globalCtx.spawn(change.getObjectClassDefinition().getTypeName()); + } + } + + private void validateResult(OperationResult result, Task task, TaskPartitionDefinitionType partition) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, + PreconditionViolationException { + if (result.isSuccess() || result.isHandledError()) { return; } - + Throwable ex = RepoCommonUtils.getResultException(result); CriticalityType criticality = ExceptionUtil.getCriticality(partition.getErrorCriticality(), ex, CriticalityType.PARTIAL); RepoCommonUtils.processErrorCriticality(task.getTaskType(), criticality, ex, result); } - + private void notifyResourceObjectChangeListeners(ResourceObjectShadowChangeDescription change, Task task, OperationResult parentResult) { changeNotificationDispatcher.notifyChange(change, task, parentResult); } - @SuppressWarnings("unchecked") private ResourceObjectShadowChangeDescription createResourceShadowChangeDescription( Change change, ResourceType resourceType, String channel) { ResourceObjectShadowChangeDescription shadowChangeDescription = new ResourceObjectShadowChangeDescription(); @@ -2535,12 +2566,7 @@ private ResourceObjectShadowChangeDescription createResourceShadowChangeDescript shadowChangeDescription.setResource(resourceType.asPrismObject()); shadowChangeDescription.setOldShadow(change.getOldShadow()); shadowChangeDescription.setCurrentShadow(change.getCurrentShadow()); - if (null == channel) { - shadowChangeDescription - .setSourceChannel(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC)); - } else { - shadowChangeDescription.setSourceChannel(channel); - } + shadowChangeDescription.setSourceChannel(channel != null ? channel : SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); return shadowChangeDescription; } @@ -2596,7 +2622,7 @@ private Collection createShadowResultModification(Change ch } private String getOidFromChange(Change change) { - String shadowOid = null; + String shadowOid; if (change.getObjectDelta() != null && change.getObjectDelta().getOid() != null) { shadowOid = change.getObjectDelta().getOid(); } else { @@ -2633,62 +2659,88 @@ private void deleteShadowFromRepoIfNeeded(Change change, OperationResult parentR } } - void processChange(ProvisioningContext ctx, Change change, PrismObject oldShadow, - OperationResult parentResult) throws SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ObjectNotFoundException, - GenericConnectorException, ObjectAlreadyExistsException, ExpressionEvaluationException, EncryptionException { - - if (oldShadow == null) { - oldShadow = shadowManager.findOrAddShadowFromChange(ctx, change, parentResult); - } - - if (oldShadow != null) { - shadowCaretaker.applyAttributesDefinition(ctx, oldShadow); - - LOGGER.trace("Processing change, old shadow: {}", ShadowUtil.shortDumpShadow(oldShadow)); - - // skip setting other attribute when shadow is null - if (oldShadow == null) { - change.setOldShadow(null); + /** + * Manages repository representation of the shadow affected by the change. + * Polishes the change itself. + * + * Does no synchronization. + */ + private void preProcessChange(ProvisioningContext ctx, Change change, OperationResult parentResult) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ObjectNotFoundException, GenericConnectorException, ExpressionEvaluationException, EncryptionException { + + if (change.getOldShadow() == null) { + PrismObject repoShadow = shadowManager.findOrAddShadowFromChange(ctx, change, parentResult); + if (repoShadow != null) { + shadowCaretaker.applyAttributesDefinition(ctx, repoShadow); + change.setOldShadow(repoShadow); + } else { + assert change.isDelete(); + LOGGER.debug("No old shadow for synchronization event {}, the shadow must be gone in the meantime " + + "(this is probably harmless)", change); return; } + } - ProvisioningUtil.setProtectedFlag(ctx, oldShadow, matchingRuleRegistry, relationRegistry); - change.setOldShadow(oldShadow); + @NotNull PrismObject oldShadow = change.getOldShadow(); + LOGGER.trace("Processing change, old shadow: {}", ShadowUtil.shortDumpShadow(oldShadow)); - if (change.getCurrentShadow() != null) { - PrismObject currentShadow = completeShadow(ctx, change.getCurrentShadow(), - oldShadow, false, parentResult); - change.setCurrentShadow(currentShadow); - // TODO: shadowState - shadowManager.updateShadow(ctx, currentShadow, oldShadow, null, parentResult); - } + ProvisioningUtil.setProtectedFlag(ctx, oldShadow, matchingRuleRegistry, relationRegistry); - if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) { - change.getObjectDelta().setOid(oldShadow.getOid()); - } - - if (change.getObjectDelta() != null && change.getObjectDelta().isDelete()) { - PrismObject currentShadow = change.getCurrentShadow(); - if (currentShadow == null) { - currentShadow = oldShadow.clone(); + if (change.getCurrentShadow() == null && !change.isDelete()) { + // Temporary measure: let us determine the current shadow; either by fetching it from the resource + // (if possible) or by taking cached values and applying the delta. In the future we might implement + // pure delta changes that do not need to know the current state. + if (change.isAdd()) { + change.setCurrentShadow(change.getObjectDelta().getObjectToAdd().clone()); + } else { + if (ctx.getCachingStrategy() == CachingStategyType.PASSIVE) { + PrismObject currentShadow = oldShadow.clone(); + if (change.getObjectDelta() != null) { + change.getObjectDelta().applyTo(currentShadow); + } + change.setCurrentShadow(currentShadow); + } else { + // no caching; let us retrieve the object from the resource + Collection> options = schemaHelper.getOperationOptionsBuilder() + .doNotDiscovery().build(); + PrismObject currentShadow; + try { + currentShadow = getShadow(oldShadow.getOid(), oldShadow, options, ctx.getTask(), parentResult); + } catch (ObjectNotFoundException e) { + // The object on the resource does not exist (any more?). + LOGGER.warn("Object {} does not exist on the resource any more", oldShadow); + throw e; // TODO + } change.setCurrentShadow(currentShadow); - } - ShadowType currentShadowType = currentShadow.asObjectable(); - if (!ShadowUtil.isDead(currentShadowType) || ShadowUtil.isExists(currentShadowType)) { - shadowManager.markShadowTombstone(currentShadow, parentResult); } } - - } else { - LOGGER.debug( - "No old shadow for synchronization event {}, the shadow must be gone in the meantime (this is probably harmless)", - change); + } + assert change.getCurrentShadow() != null || change.isDelete(); + if (change.getCurrentShadow() != null) { + PrismObject currentShadow = completeShadow(ctx, change.getCurrentShadow(), oldShadow, false, parentResult); + change.setCurrentShadow(currentShadow); + // TODO: shadowState + shadowManager.updateShadow(ctx, currentShadow, oldShadow, null, parentResult); + } + + if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) { + change.getObjectDelta().setOid(oldShadow.getOid()); } + if (change.isDelete()) { + PrismObject currentShadow = change.getCurrentShadow(); + if (currentShadow == null) { + currentShadow = oldShadow.clone(); + change.setCurrentShadow(currentShadow); // todo why is this? [pmed] + } + if (!ShadowUtil.isDead(currentShadow) || ShadowUtil.isExists(currentShadow)) { + shadowManager.markShadowTombstone(currentShadow, parentResult); + } + } } - public PrismProperty fetchCurrentToken(ResourceShadowDiscriminator shadowCoordinates, + PrismProperty fetchCurrentToken(ResourceShadowDiscriminator shadowCoordinates, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, ExpressionEvaluationException { Validate.notNull(parentResult, "Operation result must not be null."); @@ -2763,9 +2815,9 @@ private PrismObject completeShadow(ProvisioningContext ctx, // Attributes resultShadow.removeContainer(ShadowType.F_ATTRIBUTES); - ResourceAttributeContainer resultAttibutes = resourceAttributesContainer.clone(); - accessChecker.filterGetAttributes(resultAttibutes, ctx.computeCompositeObjectClassDefinition(auxObjectClassQNames), parentResult); - resultShadow.add(resultAttibutes); + ResourceAttributeContainer resultAttributes = resourceAttributesContainer.clone(); + accessChecker.filterGetAttributes(resultAttributes, ctx.computeCompositeObjectClassDefinition(auxObjectClassQNames), parentResult); + resultShadow.add(resultAttributes); resultShadowType.setIgnored(resourceShadowType.isIgnored()); @@ -2827,8 +2879,8 @@ private PrismObject completeShadow(ProvisioningContext ctx, if (rEntitlementAssociation == null) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Entitlement association with name {} couldn't be found in {} {}\nresource shadow:\n{}\nrepo shadow:\n{}", - new Object[]{ associationName, ctx.getObjectClassDefinition(), ctx.getDesc(), - resourceShadow.debugDump(1), repoShadow==null?null:repoShadow.debugDump(1)}); + associationName, ctx.getObjectClassDefinition(), ctx.getDesc(), + resourceShadow.debugDump(1), repoShadow==null?null:repoShadow.debugDump(1)); LOGGER.trace("Full refined definition: {}", ctx.getObjectClassDefinition().debugDump()); } throw new SchemaException("Entitlement association with name " + associationName @@ -2882,7 +2934,7 @@ private PrismObject completeShadow(ProvisioningContext ctx, parentResult.muteLastSubresultError(); LOGGER.warn( "The entitlement identified by {} referenced from {} violates the schema. Skipping. Original error: {}-{}", - new Object[] { associationCVal, resourceShadow, e.getMessage(), e }); + associationCVal, resourceShadow, e.getMessage(), e); continue; } } else { diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowManager.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowManager.java index 9d0476e0b12..7d49ef61414 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowManager.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowManager.java @@ -29,6 +29,7 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemName; import org.apache.commons.lang.BooleanUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -70,7 +71,6 @@ import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.schema.util.SchemaDebugUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; @@ -165,11 +165,8 @@ public PrismObject lookupLiveShadowInRepository(ProvisioningContext OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { - ObjectQuery query = createSearchShadowQueryByPrimaryIdentifier(ctx, resourceShadow, prismContext, - parentResult); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Searching for shadow using filter:\n{}", query.debugDump()); - } + ObjectQuery query = createSearchShadowQueryByPrimaryIdentifier(ctx, resourceShadow, prismContext, parentResult); + LOGGER.trace("Searching for shadow using filter:\n{}", DebugUtil.debugDumpLazily(query)); List> foundShadows = repositoryService.searchObjects(ShadowType.class, query, null, parentResult); MiscSchemaUtil.reduceSearchResult(foundShadows); @@ -206,8 +203,7 @@ public PrismObject eliminateDeadShadows(List PrismObject liveShadow = null; for (PrismObject shadow: shadows) { - ShadowType shadowType = shadow.asObjectable(); - if (!ShadowUtil.isDead(shadowType)) { + if (!ShadowUtil.isDead(shadow)) { if (liveShadow == null) { liveShadow = shadow; } else { @@ -423,24 +419,26 @@ private List> getNormalizedValue(PrismProperty attr } - // beware, may return null if an shadow that was to be marked as DEAD, was deleted in the meantime - public PrismObject findOrAddShadowFromChange(ProvisioningContext ctx, Change change, - OperationResult parentResult) throws SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, EncryptionException { + /** + * Returns null if there's no existing (relevant) shadow and the change is DELETE. + */ + PrismObject findOrAddShadowFromChange(ProvisioningContext ctx, Change change, + OperationResult parentResult) throws SchemaException, CommunicationException, + ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, EncryptionException { // Try to locate existing shadow in the repository - List> accountList = searchShadowByIdenifiers(ctx, change, parentResult); + List> accountList = searchShadowByIdentifiers(ctx, change, parentResult); // We normally do not want dead shadows here. Normally we should not receive any change notifications about dead // shadows anyway. And dead shadows may get into the way. E.g. account is deleted and then it is quickly re-created. // In that case we will get ADD change notification and there is a dead shadow in repo. But we do not want to use that // dead shadow. The notification is about a new (re-create) account. We want to create new shadow. - PrismObject foundShadow = eliminateDeadShadows(accountList, parentResult); + PrismObject foundLiveShadow = eliminateDeadShadows(accountList, parentResult); - if (foundShadow == null) { + if (foundLiveShadow == null) { // account was not found in the repository, create it now - if ((change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE)) { + if (change.isDelete()) { if (accountList.isEmpty()) { // Delete. No shadow is OK here. return null; @@ -451,135 +449,59 @@ public PrismObject findOrAddShadowFromChange(ProvisioningContext ctx } else { - foundShadow = createNewShadowFromChange(ctx, change, parentResult); + foundLiveShadow = createNewShadowFromChange(ctx, change, parentResult); try { - ConstraintsChecker.onShadowAddOperation(foundShadow.asObjectable()); - String oid = repositoryService.addObject(foundShadow, null, parentResult); - foundShadow.setOid(oid); + ConstraintsChecker.onShadowAddOperation(foundLiveShadow.asObjectable()); + String oid = repositoryService.addObject(foundLiveShadow, null, parentResult); + foundLiveShadow.setOid(oid); if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) { change.getObjectDelta().setOid(oid); } } catch (ObjectAlreadyExistsException e) { - parentResult.recordFatalError("Can't add " + foundShadow + " to the repository. Reason: " + e.getMessage(), e); + parentResult.recordFatalError("Can't add " + foundLiveShadow + " to the repository. Reason: " + e.getMessage(), e); throw new IllegalStateException(e.getMessage(), e); } - LOGGER.debug("Added new shadow (from change): {}", foundShadow); + LOGGER.debug("Added new shadow (from change): {}", foundLiveShadow); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Added new shadow (from change):\n{}", foundShadow.debugDump(1)); + LOGGER.trace("Added new shadow (from change):\n{}", foundLiveShadow.debugDump(1)); } } - } - return foundShadow; - } - - // This is really invoked only for delete case. It is mostly copy&paste with findOrAddShadowFromChange. - // TODO: not very elegant, cleanup - public PrismObject findOrAddShadowFromChangeGlobalContext(ProvisioningContext globalCtx, Change change, - OperationResult parentResult) throws SchemaException, CommunicationException, - ConfigurationException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, EncryptionException { - - // Try to locate existing shadow in the repository - List> accountList = searchShadowByIdenifiers(globalCtx, change, parentResult); - - // We normally do not want dead shadows here. Normally we should not receive any change notifications about dead - // shadows anyway. And dead shadows may get into the way. E.g. account is deleted and then it is quickly re-created. - // In that case we will get ADD change notification and there is a dead shadow in repo. But we do not want to use that - // dead shadow. The notification is about a new (re-create) account. We want to create new shadow. - PrismObject newShadow = eliminateDeadShadows(accountList, parentResult); - - if (newShadow == null) { - - if ((change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE)) { - if (accountList.isEmpty()) { - // Delete. No shadow is OK here. - return null; - } else { - // Delete of shadow that is already dead. - return accountList.get(0); - } - - } else { - // All situations except delete: create new shadow - newShadow = createNewShadowFromChange(globalCtx, change, parentResult); - - try { - ConstraintsChecker.onShadowAddOperation(newShadow.asObjectable()); - String oid = repositoryService.addObject(newShadow, null, parentResult); - newShadow.setOid(oid); - if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) { - change.getObjectDelta().setOid(oid); - } - } catch (ObjectAlreadyExistsException e) { - parentResult.recordFatalError("Can't add " + SchemaDebugUtil.prettyPrint(newShadow) - + " to the repository. Reason: " + e.getMessage(), e); - throw new IllegalStateException(e.getMessage(), e); - } - LOGGER.debug("Added new shadow (from global change): {}", newShadow); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Added new shadow (from global change):\n{}", newShadow.debugDump()); - } - } - - } else { - // Live shadow was found in repository - - if (change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE) { - List> deadDeltas = prismContext.deltaFor(ShadowType.class) - .item(ShadowType.F_DEAD).replace(true) - .item(ShadowType.F_EXISTS).replace(false) - .asItemDeltas(); - try { - ConstraintsChecker.onShadowModifyOperation(deadDeltas); - repositoryService.modifyObject(ShadowType.class, newShadow.getOid(), deadDeltas, parentResult); - } catch (ObjectAlreadyExistsException e) { - parentResult.recordFatalError( - "Can't add " + newShadow + " to the repository. Reason: " + e.getMessage(), e); - throw new IllegalStateException(e.getMessage(), e); - } catch (ObjectNotFoundException e) { - parentResult.recordWarning("Shadow " + newShadow + " was probably deleted from the repository in the meantime. Exception: " - + e.getMessage(), e); - return null; - } - - ObjectDeltaUtil.applyTo(newShadow, deadDeltas); - } - } - - return newShadow; + return foundLiveShadow; } private PrismObject createNewShadowFromChange(ProvisioningContext ctx, Change change, - OperationResult parentResult) throws SchemaException, - CommunicationException, ConfigurationException, - SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, EncryptionException { + OperationResult parentResult) throws SchemaException, CommunicationException, ConfigurationException, + ObjectNotFoundException, ExpressionEvaluationException, EncryptionException { + + assert !change.isDelete(); PrismObject shadow = change.getCurrentShadow(); - if (shadow == null){ - //try to look in the delta, if there exists some account to be added - if (change.getObjectDelta() != null) { - if (change.getObjectDelta().isAdd()) { - shadow = (PrismObject) change.getObjectDelta().getObjectToAdd(); - } else if (change.getObjectDelta().isDelete()) { - // Sanity checks. We can remove them later when entire sync code is cleaned up. - ShadowType shadowType = shadow.asObjectable(); - if (!ShadowUtil.isDead(shadowType)) { - throw new IllegalStateException("Deleted "+shadow+" not dead"); - } - if (ShadowUtil.isExists(shadowType)) { - throw new IllegalStateException("Deleted "+shadow+" exists"); - } + if (shadow == null) { + if (change.isAdd()) { + shadow = change.getObjectDelta().getObjectToAdd(); + assert shadow != null; + } else if (change.getIdentifiers() != null && !change.getIdentifiers().isEmpty()) { + if (change.getObjectClassDefinition() == null) { + throw new IllegalStateException("Could not create shadow from change description. Object class is not specified."); } + ShadowType newShadow = new ShadowType(prismContext); + newShadow.setObjectClass(change.getObjectClassDefinition().getTypeName()); + ResourceAttributeContainer attributeContainer = change.getObjectClassDefinition() + .toResourceAttributeContainerDefinition().instantiate(); + newShadow.asPrismObject().add(attributeContainer); + for (ResourceAttribute identifier : change.getIdentifiers()) { + attributeContainer.add(identifier.clone()); + } + shadow = newShadow.asPrismObject(); + } else { + throw new IllegalStateException("Could not create shadow from change description. Neither current shadow, nor delta containing shadow, nor identifiers exists."); } } - if (shadow == null){ - throw new IllegalStateException("Could not create shadow from change description. Neither current shadow, nor delta containing shadow exits."); - } - try { shadow = createRepositoryShadow(ctx, shadow); } catch (SchemaException ex) { @@ -593,13 +515,13 @@ private PrismObject createNewShadowFromChange(ProvisioningContext ct return shadow; } - private List> searchShadowByIdenifiers(ProvisioningContext ctx, Change change, OperationResult parentResult) + private List> searchShadowByIdentifiers(ProvisioningContext ctx, Change change, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { Collection> identifiers = change.getIdentifiers(); ObjectQuery query = createSearchShadowQuery(ctx, identifiers, true, prismContext, parentResult); - List> accountList = null; + List> accountList; try { accountList = repositoryService.searchObjects(ShadowType.class, query, null, parentResult); } catch (SchemaException ex) { @@ -1755,20 +1677,48 @@ private void computeUpdateShadowAttributeChanges(ProvisioningContext ctx, Collec } /** - * Updates repository shadow based on shadow from resource. Handles rename cases, - * change of auxiliary object classes, etc. - * @returns repository shadow as it should look like after the update + * Updates repository shadow based on shadow or delta from resource. + * Handles rename cases, change of auxiliary object classes, etc. + * + * @return repository shadow as it should look like after the update */ - @SuppressWarnings("unchecked") - public PrismObject updateShadow(ProvisioningContext ctx, PrismObject currentResourceShadow, - PrismObject oldRepoShadow, ShadowState shadowState, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException, ConfigurationException, CommunicationException, ExpressionEvaluationException { - - RefinedObjectClassDefinition ocDef = ctx.computeCompositeObjectClassDefinition(currentResourceShadow); - ObjectDelta shadowDelta = oldRepoShadow.createModifyDelta(); - PrismContainer currentResourceAttributesContainer = currentResourceShadow.findContainer(ShadowType.F_ATTRIBUTES); - PrismContainer oldRepoAttributesContainer = oldRepoShadow.findContainer(ShadowType.F_ATTRIBUTES); - ShadowType oldRepoShadowType = oldRepoShadow.asObjectable(); - ShadowType currentResourceShadowType = currentResourceShadow.asObjectable(); + @SuppressWarnings({ "unchecked", "WeakerAccess" }) + public PrismObject updateShadow(@NotNull ProvisioningContext ctx, + @NotNull PrismObject resourceShadowNew, + @NotNull PrismObject repoShadowOld, ShadowState shadowState, OperationResult parentResult) throws SchemaException, + ObjectNotFoundException, ConfigurationException, CommunicationException, ExpressionEvaluationException { + + ObjectDelta shadowDelta = computeShadowDelta(ctx, repoShadowOld, resourceShadowNew, shadowState); + + if (!shadowDelta.isEmpty()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Updating repo shadow {} with delta:\n{}", repoShadowOld, shadowDelta.debugDump(1)); + } + ConstraintsChecker.onShadowModifyOperation(shadowDelta.getModifications()); + try { + repositoryService.modifyObject(ShadowType.class, repoShadowOld.getOid(), shadowDelta.getModifications(), parentResult); + } catch (ObjectAlreadyExistsException e) { + throw new SystemException(e.getMessage(), e); // This should not happen for shadows + } + PrismObject repoShadowNew = repoShadowOld.clone(); + shadowDelta.applyTo(repoShadowNew); + return repoShadowNew; + } else { + LOGGER.trace("No need to update repo shadow {} (empty delta)", repoShadowOld); + return repoShadowOld; + } + } + + private ObjectDelta computeShadowDelta(@NotNull ProvisioningContext ctx, + @NotNull PrismObject repoShadowOld, + PrismObject resourceShadowNew, ShadowState shadowState) + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + RefinedObjectClassDefinition ocDef = ctx.computeCompositeObjectClassDefinition(resourceShadowNew); + ObjectDelta shadowDelta = repoShadowOld.createModifyDelta(); + PrismContainer currentResourceAttributesContainer = resourceShadowNew.findContainer(ShadowType.F_ATTRIBUTES); + PrismContainer oldRepoAttributesContainer = repoShadowOld.findContainer(ShadowType.F_ATTRIBUTES); + ShadowType oldRepoShadowType = repoShadowOld.asObjectable(); CachingStategyType cachingStrategy = ProvisioningUtil.getCachingStrategy(ctx); @@ -1818,13 +1768,13 @@ public PrismObject updateShadow(ProvisioningContext ctx, PrismObject } shadowDelta.addModification(attrDiff); } - + } } } } } - + for (Item oldRepoItem: oldRepoAttributesContainer.getValue().getItems()) { if (oldRepoItem instanceof PrismProperty) { PrismProperty oldRepoAttrProperty = (PrismProperty)oldRepoItem; @@ -1842,22 +1792,22 @@ public PrismObject updateShadow(ProvisioningContext ctx, PrismObject } } } - - PolyString currentShadowName = ShadowUtil.determineShadowName(currentResourceShadow); - PolyString oldRepoShadowName = oldRepoShadow.getName(); - if (!currentShadowName.equalsOriginalValue(oldRepoShadowName)) { + + PolyString currentShadowName = ShadowUtil.determineShadowName(resourceShadowNew); + PolyString oldRepoShadowName = repoShadowOld.getName(); + if (!currentShadowName.equalsOriginalValue(oldRepoShadowName)) { PropertyDelta shadowNameDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_NAME, - oldRepoShadow.getDefinition(),currentShadowName); + repoShadowOld.getDefinition(),currentShadowName); shadowDelta.addModification(shadowNameDelta); } - - PropertyDelta auxOcDelta = (PropertyDelta) ItemUtil.diff( - oldRepoShadow.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS), - currentResourceShadow.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS)); + + PropertyDelta auxOcDelta = ItemUtil.diff( + repoShadowOld.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS), + resourceShadowNew.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS)); if (auxOcDelta != null) { shadowDelta.addModification(auxOcDelta); } - + // Resource object obviously exists in this case. However, we do not want to mess with isExists flag in some // situations (e.g. in CORPSE state) as this existence may be just a quantum illusion. if (shadowState == ShadowState.CONCEPTION || shadowState == ShadowState.GESTATION) { @@ -1865,7 +1815,7 @@ public PrismObject updateShadow(ProvisioningContext ctx, PrismObject existsDelta.setRealValuesToReplace(true); shadowDelta.addModification(existsDelta); } - + if (cachingStrategy == CachingStategyType.NONE) { if (oldRepoShadowType.getCachingMetadata() != null) { shadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA); @@ -1873,10 +1823,10 @@ public PrismObject updateShadow(ProvisioningContext ctx, PrismObject } else if (cachingStrategy == CachingStategyType.PASSIVE) { - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, currentResourceShadow, oldRepoShadow); - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_FROM, currentResourceShadow, oldRepoShadow); - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_TO, currentResourceShadow, oldRepoShadow); - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, currentResourceShadow, oldRepoShadow); + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, resourceShadowNew, repoShadowOld); + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_FROM, resourceShadowNew, repoShadowOld); + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_TO, resourceShadowNew, repoShadowOld); + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, resourceShadowNew, repoShadowOld); CachingMetadataType cachingMetadata = new CachingMetadataType(); cachingMetadata.setRetrievalTimestamp(clock.currentTimeXMLGregorianCalendar()); @@ -1885,31 +1835,8 @@ public PrismObject updateShadow(ProvisioningContext ctx, PrismObject } else { throw new ConfigurationException("Unknown caching strategy "+cachingStrategy); } - - if (!shadowDelta.isEmpty()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Updating repo shadow {} with delta:\n{}", oldRepoShadow, shadowDelta.debugDump(1)); - } - ConstraintsChecker.onShadowModifyOperation(shadowDelta.getModifications()); - try { - - repositoryService.modifyObject(ShadowType.class, oldRepoShadow.getOid(), shadowDelta.getModifications(), parentResult); - - } catch (ObjectAlreadyExistsException e) { - // This should not happen for shadows - throw new SystemException(e.getMessage(), e); - } - - PrismObject newRepoShadow = oldRepoShadow.clone(); - shadowDelta.applyTo(newRepoShadow); - return newRepoShadow; - - } else { - LOGGER.trace("No need to update repo shadow {} (empty delta)", oldRepoShadow); - return oldRepoShadow; - } + return shadowDelta; } - private void compareUpdateProperty(ObjectDelta shadowDelta, ItemPath itemPath, PrismObject currentResourceShadow, PrismObject oldRepoShadow) { @@ -1998,7 +1925,7 @@ public PrismObject markShadowTombstone(PrismObject repoS repositoryService.modifyObject(ShadowType.class, repoShadow.getOid(), shadowChanges, parentResult); } catch (ObjectAlreadyExistsException e) { // Should not happen, this is not a rename - new SystemException(e.getMessage(), e); + throw new SystemException(e.getMessage(), e); } catch (ObjectNotFoundException e) { // Cannot be more dead LOGGER.trace("Attempt to mark shadow {} as tombstone found that no such shadow exists", repoShadow); diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java index 172ced5b698..1cb14a9b3d3 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java @@ -159,7 +159,7 @@ private void discoverConflictingShadow(ProvisioningContext ctx, PrismObject List evaluate(ExpressionType expressionBean, Map variables, QName outputPropertyName, + String ctxDesc) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + // TODO consider getting the task instance from the caller + Task task = taskManager.createTaskInstance(UcfExpressionEvaluatorImpl.class.getName() + ".evaluate"); + OperationResult result = task.getResult(); + + Expression, PrismPropertyDefinition> expression = + expressionFactory.makePropertyExpression(expressionBean, outputPropertyName, ctxDesc, task, result); + ExpressionVariables exprVariables = new ExpressionVariables(); + exprVariables.addVariableDefinitions(variables); + ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, exprVariables, ctxDesc, task, result); + PrismValueDeltaSetTriple> exprResultTriple = expression.evaluate(context); + List list = new ArrayList<>(); + for (PrismPropertyValue pv : exprResultTriple.getZeroSet()) { + list.add(pv.getRealValue()); + } + return list; + } +} diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/TestConnectorDiscovery.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/TestConnectorDiscovery.java index 95c0fe51556..ff0752dcf40 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/TestConnectorDiscovery.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/TestConnectorDiscovery.java @@ -88,7 +88,7 @@ public void test001Connectors() throws Exception { IntegrationTestTools.assertConnectorSchemaSanity(conn, prismContext); } - assertEquals("Unexpected number of connectors found", 8, connectors.size()); + assertEquals("Unexpected number of connectors found", 9, connectors.size()); } @Test @@ -108,7 +108,7 @@ public void testListConnectors() throws Exception{ System.out.println("-----\n"); } - assertEquals("Unexpected number of connectors found", 8, connectors.size()); + assertEquals("Unexpected number of connectors found", 9, connectors.size()); } @Test diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/EmbeddedBroker.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/EmbeddedBroker.java new file mode 100644 index 00000000000..ba6d12ac5f8 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/EmbeddedBroker.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.impl.async; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import org.apache.qpid.server.SystemLauncher; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +public class EmbeddedBroker { + + private final SystemLauncher broker = new SystemLauncher(); + + void start() throws Exception { + System.out.println("Starting the broker"); + Map attributes = new HashMap<>(); + attributes.put("type", "Memory"); + attributes.put("initialConfigurationLocation", findResourcePath("async/qpid-config.json")); + broker.startup(attributes); + } + + private String findResourcePath(String fileName) { + return EmbeddedBroker.class.getClassLoader().getResource(fileName).toExternalForm(); + } + + void stop() { + System.out.println("Stopping the broker"); + broker.shutdown(); + } + + void send(String queueName, String message) throws IOException, TimeoutException { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { + channel.basicPublish("", queueName, null, message.getBytes(StandardCharsets.UTF_8)); + System.out.println("Sent '" + message + "'"); + } + } + + public void createQueue(String queueName) throws IOException, TimeoutException { + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost("localhost"); + try (Connection connection = factory.newConnection(); + Channel channel = connection.createChannel()) { + channel.queueDeclare(queueName, true, false, false, new HashMap<>()); + } + } +} \ No newline at end of file diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/MockAsyncUpdateSource.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/MockAsyncUpdateSource.java new file mode 100644 index 00000000000..0094019e611 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/MockAsyncUpdateSource.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.impl.async; + +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateMessageListener; +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateSource; +import com.evolveum.midpoint.provisioning.ucf.api.ListeningActivity; +import com.evolveum.midpoint.provisioning.ucf.impl.builtin.async.AsyncUpdateConnectorInstance; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * See also MockAsyncUpdateSource in model-intest. + */ +@SuppressWarnings("unused") +public class MockAsyncUpdateSource implements AsyncUpdateSource { + + private static final Trace LOGGER = TraceManager.getTrace(MockAsyncUpdateSource.class); + + private final Queue messages = new LinkedList<>(); + + public static final MockAsyncUpdateSource INSTANCE = new MockAsyncUpdateSource(); + + private static class DummyListeningActivityImpl implements ListeningActivity { + @Override + public void stop() { + // no-op + } + } + + public static MockAsyncUpdateSource create(AsyncUpdateSourceType configuration, AsyncUpdateConnectorInstance connectorInstance) { + LOGGER.info("create() method called"); + return INSTANCE; + } + + @Override + public ListeningActivity startListening(AsyncUpdateMessageListener listener) throws SchemaException { + LOGGER.info("startListening() method called"); + for (AsyncUpdateMessageType message : messages) { + listener.onMessage(message); + } + return new DummyListeningActivityImpl(); + } + + @Override + public void test(OperationResult parentResult) { + LOGGER.info("test() method called"); + } + + public void prepareMessage(UcfChangeType changeDescription) { + AnyDataAsyncUpdateMessageType message = new AnyDataAsyncUpdateMessageType(); + message.setData(changeDescription); + messages.offer(message); + } + + public void reset() { + messages.clear(); + } + + @Override + public String toString() { + return "MockAsyncUpdateSource{" + + "messages:" + messages.size() + + '}'; + } +} diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdate.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdate.java new file mode 100644 index 00000000000..e9187457d18 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdate.java @@ -0,0 +1,442 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.impl.async; + +import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContainerDefinition; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; +import com.evolveum.midpoint.provisioning.impl.AbstractProvisioningIntegrationTest; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.MidPointConstants; +import com.evolveum.midpoint.schema.internals.InternalsConfig; +import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; +import com.evolveum.midpoint.schema.processor.ResourceSchema; +import com.evolveum.midpoint.schema.processor.ResourceSchemaImpl; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testng.AssertJUnit; +import org.testng.annotations.Test; +import org.w3c.dom.Element; + +import javax.xml.namespace.QName; +import java.io.File; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import static com.evolveum.midpoint.provisioning.impl.ProvisioningTestUtil.checkRepoAccountShadow; +import static com.evolveum.midpoint.provisioning.impl.ProvisioningTestUtil.checkRepoShadow; +import static org.testng.AssertJUnit.*; + +/** + * @author semancik + * @author mederly + */ +@ContextConfiguration(locations = "classpath:ctx-provisioning-test-main.xml") +@DirtiesContext +public abstract class TestAsyncUpdate extends AbstractProvisioningIntegrationTest { + + protected static final File TEST_DIR = new File("src/test/resources/async/"); + + static final File RESOURCE_ASYNC_CACHING_FILE = new File(TEST_DIR, "resource-async-caching.xml"); + static final File RESOURCE_ASYNC_NO_CACHING_FILE = new File(TEST_DIR, "resource-async-no-caching.xml"); + static final File RESOURCE_ASYNC_CACHING_AMQP_FILE = new File(TEST_DIR, "resource-async-caching-amqp.xml"); + private static final String RESOURCE_ASYNC_OID = "fb04d113-ebf8-41b4-b13b-990a597d110b"; + + private static final File CHANGE_100 = new File(TEST_DIR, "change-100-banderson-first-occurrence.xml"); + private static final File CHANGE_110 = new File(TEST_DIR, "change-110-banderson-delta.xml"); + private static final File CHANGE_120 = new File(TEST_DIR, "change-120-banderson-new-state.xml"); + private static final File CHANGE_125 = new File(TEST_DIR, "change-125-banderson-notification-only.xml"); + private static final File CHANGE_130 = new File(TEST_DIR, "change-130-banderson-delete.xml"); + + private static final QName RESOURCE_ACCOUNT_OBJECTCLASS = new QName(MidPointConstants.NS_RI, "AccountObjectClass"); + + static final String ASYNC_CONNECTOR_TYPE = "AsyncUpdateConnector"; + + @SuppressWarnings("unused") + private static final Trace LOGGER = TraceManager.getTrace(TestAsyncUpdate.class); + + private static final long TIMEOUT = 5000L; + + protected PrismObject resource; + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + // We need to switch off the encryption checks. Some values cannot be encrypted as we do + // not have a definition here + InternalsConfig.encryptionChecks = false; + + super.initSystem(initTask, initResult); + + syncServiceMock.setSupportActivation(false); + resource = addResourceFromFile(getResourceFile(), getConnectorTypes(), false, initResult); + + InternalsConfig.setSanityChecks(true); + } + + @NotNull + public abstract List getConnectorTypes(); + + protected abstract File getResourceFile(); + + @Test + public void test000Sanity() throws Exception { + final String TEST_NAME = "test000Sanity"; + TestUtil.displayTestTitle(TEST_NAME); + + assertNotNull("Resource is null", resource); + + OperationResult result = new OperationResult(TestAsyncUpdate.class.getName() + "." + TEST_NAME); + + ResourceType repoResource = repositoryService.getObject(ResourceType.class, RESOURCE_ASYNC_OID, null, result).asObjectable(); + assertNotNull("No connector ref", repoResource.getConnectorRef()); + String connectorOid = repoResource.getConnectorRef().getOid(); + assertNotNull("No connector ref OID", connectorOid); + ConnectorType repoConnector = repositoryService + .getObject(ConnectorType.class, connectorOid, null, result).asObjectable(); + assertNotNull(repoConnector); + display("Async Connector", repoConnector); + + // Check connector schema + IntegrationTestTools.assertConnectorSchemaSanity(repoConnector, prismContext); + } + + @Test + public void test003Connection() throws Exception { + final String TEST_NAME = "test003Connection"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + // Check that there is a schema, but no capabilities before test (pre-condition) + ResourceType resourceBefore = repositoryService.getObject(ResourceType.class, RESOURCE_ASYNC_OID, + null, result).asObjectable(); + + ResourceTypeUtil.getResourceXsdSchema(resourceBefore); + + CapabilitiesType capabilities = resourceBefore.getCapabilities(); + if (capabilities != null) { + AssertJUnit.assertNull("Native capabilities present before test connection. Bad test setup?", capabilities.getNative()); + } + + // WHEN + OperationResult testResult = provisioningService.testResource(RESOURCE_ASYNC_OID, task); + + // THEN + display("Test result", testResult); + TestUtil.assertSuccess("Test resource failed (result)", testResult); + + PrismObject resourceRepoAfter = repositoryService.getObject(ResourceType.class, RESOURCE_ASYNC_OID, null, result); + ResourceType resourceTypeRepoAfter = resourceRepoAfter.asObjectable(); + display("Resource after test", resourceTypeRepoAfter); + + XmlSchemaType xmlSchemaTypeAfter = resourceTypeRepoAfter.getSchema(); + assertNotNull("No schema after test connection", xmlSchemaTypeAfter); + Element resourceXsdSchemaElementAfter = ResourceTypeUtil.getResourceXsdSchema(resourceTypeRepoAfter); + assertNotNull("No schema after test connection", resourceXsdSchemaElementAfter); + + String resourceXml = prismContext.xmlSerializer().serialize(resourceRepoAfter); + display("Resource XML", resourceXml); + + CachingMetadataType cachingMetadata = xmlSchemaTypeAfter.getCachingMetadata(); + assertNotNull("No caching metadata", cachingMetadata); + assertNotNull("No retrievalTimestamp", cachingMetadata.getRetrievalTimestamp()); + assertNotNull("No serialNumber", cachingMetadata.getSerialNumber()); + + Element xsdElement = ObjectTypeUtil.findXsdElement(xmlSchemaTypeAfter); + ResourceSchema parsedSchema = ResourceSchemaImpl.parse(xsdElement, resourceBefore.toString(), prismContext); + assertNotNull("No schema after parsing", parsedSchema); + + // schema will be checked in next test + } + + @Test + public void test004Configuration() throws Exception { + final String TEST_NAME = "test004Configuration"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + OperationResult result = new OperationResult(TestAsyncUpdate.class.getName() + "." + TEST_NAME); + + // WHEN + resource = provisioningService.getObject(ResourceType.class, RESOURCE_ASYNC_OID, null, null, result); + + PrismContainer configurationContainer = resource.findContainer(ResourceType.F_CONNECTOR_CONFIGURATION); + assertNotNull("No configuration container", configurationContainer); + PrismContainerDefinition confContDef = configurationContainer.getDefinition(); + assertNotNull("No configuration container definition", confContDef); +// PrismProperty propDefaultAssignee = configurationContainer.findProperty(CONF_PROPERTY_DEFAULT_ASSIGNEE_QNAME); +// assertNotNull("No defaultAssignee conf prop", propDefaultAssignee); + +// assertNotNull("No configuration properties container", confingurationPropertiesContainer); +// PrismContainerDefinition confPropDef = confingurationPropertiesContainer.getDefinition(); +// assertNotNull("No configuration properties container definition", confPropDef); + + } + + @Test + public void test005ParsedSchema() throws Exception { + final String TEST_NAME = "test005ParsedSchema"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + + // THEN + // The returned type should have the schema pre-parsed + assertTrue(RefinedResourceSchemaImpl.hasParsedSchema(resource.asObjectable())); + + // Also test if the utility method returns the same thing + ResourceSchema resourceSchema = RefinedResourceSchemaImpl.getResourceSchema(resource.asObjectable(), prismContext); + + display("Parsed resource schema", resourceSchema); + + // Check whether it is reusing the existing schema and not parsing it all over again + // Not equals() but == ... we want to really know if exactly the same + // object instance is returned + assertSame("Broken caching", resourceSchema, + RefinedResourceSchemaImpl.getResourceSchema(resource.asObjectable(), prismContext)); + + ObjectClassComplexTypeDefinition accountDef = resourceSchema.findObjectClassDefinition(RESOURCE_ACCOUNT_OBJECTCLASS); + assertNotNull("Account definition is missing", accountDef); + assertNotNull("Null identifiers in account", accountDef.getPrimaryIdentifiers()); + assertFalse("Empty identifiers in account", accountDef.getPrimaryIdentifiers().isEmpty()); + assertNotNull("No naming attribute in account", accountDef.getNamingAttribute()); + + assertEquals("Unexpected number of definitions", 3, accountDef.getDefinitions().size()); + } + + @Test + public void test100ListeningForShadowAdd() throws Exception { + final String TEST_NAME = "test100ListeningForShadowAdd"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + Task task = taskManager.createTaskInstance(TestAsyncUpdate.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + prepareMessage(CHANGE_100); + + syncServiceMock.reset(); + + addDummyAccount("banderson"); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNotNull("Delta is missing", lastChange.getObjectDelta()); + assertNotNull("Current shadow is not present", lastChange.getCurrentShadow()); + + PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); + assertNotNull("Shadow was not created in the repository", accountRepo); + display("Repository shadow", accountRepo); + checkRepoAccountShadow(accountRepo); + } + + @Test + public void test110ListeningForValueAdd() throws Exception { + final String TEST_NAME = "test110ListeningForValueAdd"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + Task task = taskManager.createTaskInstance(TestAsyncUpdate.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + prepareMessage(CHANGE_110); + + syncServiceMock.reset(); + + setDummyAccountTestAttribute("banderson", "value1", "value2", "value3"); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNotNull("Delta is missing", lastChange.getObjectDelta()); + assertTrue("Delta is not a MODIFY one", lastChange.getObjectDelta().isModify()); + Collection> modifications = lastChange.getObjectDelta().getModifications(); + assertEquals("Wrong # of modifications", 1, modifications.size()); + assertEquals("Wrong # of values added", 3, modifications.iterator().next().getValuesToAdd().size()); + assertNotNull("Current shadow is not present", lastChange.getCurrentShadow()); + + PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); + assertNotNull("Shadow is not present in the repository", accountRepo); + display("Repository shadow", accountRepo); + checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + } + + @Test + public void test120ListeningForShadowReplace() throws Exception { + final String TEST_NAME = "test120ListeningForShadowReplace"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + Task task = taskManager.createTaskInstance(TestAsyncUpdate.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + prepareMessage(CHANGE_120); + + syncServiceMock.reset(); + + setDummyAccountTestAttribute("banderson", "value4"); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNull("Delta is present although it should not be", lastChange.getObjectDelta()); + assertNotNull("Current shadow is missing", lastChange.getCurrentShadow()); + + PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); + assertNotNull("Shadow is not present in the repository", accountRepo); + display("Repository shadow", accountRepo); + checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + } + + + @Test + public void test125ListeningForNotificationOnly() throws Exception { + + if (!hasReadCapability()) { + System.out.println("Skipping this test because there's no real read capability"); + return; + } + + final String TEST_NAME = "test125ListeningForNotificationOnly"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + Task task = taskManager.createTaskInstance(TestAsyncUpdate.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + prepareMessage(CHANGE_125); + + syncServiceMock.reset(); + + setDummyAccountTestAttribute("banderson", "value125"); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNull("Delta is present although it should not be", lastChange.getObjectDelta()); + assertNotNull("Current shadow is missing", lastChange.getCurrentShadow()); + + display("change current shadow", lastChange.getCurrentShadow()); + + PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); + assertNotNull("Shadow is not present in the repository", accountRepo); + display("Repository shadow", accountRepo); + checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + } + + @Test + public void test130ListeningForShadowDelete() throws Exception { + final String TEST_NAME = "test130ListeningForShadowDelete"; + TestUtil.displayTestTitle(TEST_NAME); + // GIVEN + Task task = taskManager.createTaskInstance(TestAsyncUpdate.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + prepareMessage(CHANGE_130); + + syncServiceMock.reset(); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNotNull("Delta is missing", lastChange.getObjectDelta()); + assertTrue("Delta is not a DELETE one", lastChange.getObjectDelta().isDelete()); + //assertNull("Current shadow is present while not expecting it", lastChange.getCurrentShadow()); + //current shadow was added during the processing + + PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); + assertNotNull("Shadow is not present in the repository", accountRepo); + display("Repository shadow", accountRepo); + checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + } + + @SuppressWarnings("SameParameterValue") + void addDummyAccount(String name) { + } + + @SuppressWarnings("SameParameterValue") + void setDummyAccountTestAttribute(String name, String... values) { + } + + abstract int getNumberOfAccountAttributes(); + + boolean hasReadCapability() { + return false; + } + + void prepareMessage(File messageFile) + throws java.io.IOException, com.evolveum.midpoint.util.exception.SchemaException, TimeoutException { + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(messageFile).parseRealValue()); + } +} diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCaching.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCaching.java new file mode 100644 index 00000000000..c4fa02091ef --- /dev/null +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCaching.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.impl.async; + +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; + +import static java.util.Collections.singletonList; + +/** + * + */ +public class TestAsyncUpdateCaching extends TestAsyncUpdate { + + @Override + protected File getResourceFile() { + return RESOURCE_ASYNC_CACHING_FILE; + } + + @NotNull + @Override + public List getConnectorTypes() { + return singletonList(ASYNC_CONNECTOR_TYPE); + } + + @Override + protected int getNumberOfAccountAttributes() { + return 3; + } +} diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCachingAmqp.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCachingAmqp.java new file mode 100644 index 00000000000..6757553025b --- /dev/null +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCachingAmqp.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.impl.async; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import org.apache.commons.io.IOUtils; +import org.testng.annotations.AfterClass; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +/** + * + */ +public class TestAsyncUpdateCachingAmqp extends TestAsyncUpdateCaching { + + private final EmbeddedBroker embeddedBroker = new EmbeddedBroker(); + + private static final String QUEUE_NAME = "testQueue"; + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + embeddedBroker.start(); + embeddedBroker.createQueue(QUEUE_NAME); + } + + @Override + protected File getResourceFile() { + return RESOURCE_ASYNC_CACHING_AMQP_FILE; + } + + @AfterClass + public void stop() { + embeddedBroker.stop(); + } + + @Override + void prepareMessage(File messageFile) throws IOException, TimeoutException { + String message = String.join("\n", IOUtils.readLines(new FileReader(messageFile))); + embeddedBroker.send(QUEUE_NAME, message); + } +} diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateNoCaching.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateNoCaching.java new file mode 100644 index 00000000000..6b44b607887 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateNoCaching.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.impl.async; + +import com.evolveum.icf.dummy.resource.*; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.DummyResourceContoller; +import com.evolveum.midpoint.test.IntegrationTestTools; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.net.ConnectException; +import java.util.Arrays; +import java.util.List; + +/** + * + */ +public class TestAsyncUpdateNoCaching extends TestAsyncUpdate { + + protected static DummyResource dummyResource; + protected static DummyResourceContoller dummyResourceCtl; + + @Override + protected File getResourceFile() { + return RESOURCE_ASYNC_NO_CACHING_FILE; + } + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + dummyResourceCtl = DummyResourceContoller.create("async"); + dummyResourceCtl.setResource(resource); + dummyResource = dummyResourceCtl.getDummyResource(); + dummyResourceCtl.addAttrDef(dummyResource.getAccountObjectClass(), "test", String.class, false, true); + } + + @NotNull + @Override + public List getConnectorTypes() { + return Arrays.asList(ASYNC_CONNECTOR_TYPE, IntegrationTestTools.DUMMY_CONNECTOR_TYPE); + } + + @Override + protected int getNumberOfAccountAttributes() { + return 2; + } + + @Override + protected void addDummyAccount(String name) { + try { + dummyResourceCtl.addAccount(name); + } catch (ObjectAlreadyExistsException | SchemaViolationException | ConnectException | FileNotFoundException | ConflictException | InterruptedException e) { + throw new AssertionError(e); + } + } + + @Override + protected void setDummyAccountTestAttribute(String name, String... values) { + try { + DummyAccount account = dummyResource.getAccountByUsername(name); + account.replaceAttributeValues("test", Arrays.asList(values)); + } catch (SchemaViolationException | ConnectException | FileNotFoundException | ConflictException | InterruptedException e) { + throw new AssertionError(e); + } + } + + @Override + protected boolean hasReadCapability() { + return true; + } +} diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/mock/SynchronizationServiceMock.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/mock/SynchronizationServiceMock.java index 9ac4315bbe2..1161281d46b 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/mock/SynchronizationServiceMock.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/mock/SynchronizationServiceMock.java @@ -402,4 +402,14 @@ public String getName() { return "synchronization service mock"; } + public void waitForNotifyChange(long timeout) throws InterruptedException { + long stop = System.currentTimeMillis() + timeout; + while (System.currentTimeMillis() < stop) { + if (wasCalledNotifyChange()) { + return; + } + Thread.sleep(100); + } + throw new AssertionError("notifyChange has not arrived within " + timeout + " ms"); + } } diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-100-banderson-first-occurrence.xml b/provisioning/provisioning-impl/src/test/resources/async/change-100-banderson-first-occurrence.xml new file mode 100644 index 00000000000..32fdee0952d --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-100-banderson-first-occurrence.xml @@ -0,0 +1,35 @@ + + + + ri:AccountObjectClass + + add + c:ShadowType + + + banderson + banderson + + + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-110-banderson-delta.xml b/provisioning/provisioning-impl/src/test/resources/async/change-110-banderson-delta.xml new file mode 100644 index 00000000000..40fb4a98188 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-110-banderson-delta.xml @@ -0,0 +1,38 @@ + + + + ri:AccountObjectClass + + banderson + banderson + + + modify + ShadowType + + add + attributes/ri:test + value1 + value2 + value3 + + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-120-banderson-new-state.xml b/provisioning/provisioning-impl/src/test/resources/async/change-120-banderson-new-state.xml new file mode 100644 index 00000000000..22db7e99d6f --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-120-banderson-new-state.xml @@ -0,0 +1,29 @@ + + + + ri:AccountObjectClass + + + banderson + banderson + value4 + + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-125-banderson-notification-only.xml b/provisioning/provisioning-impl/src/test/resources/async/change-125-banderson-notification-only.xml new file mode 100644 index 00000000000..e082e072fec --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-125-banderson-notification-only.xml @@ -0,0 +1,26 @@ + + + + ri:AccountObjectClass + + banderson + banderson + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-130-banderson-delete.xml b/provisioning/provisioning-impl/src/test/resources/async/change-130-banderson-delete.xml new file mode 100644 index 00000000000..aa51cedd7c9 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-130-banderson-delete.xml @@ -0,0 +1,32 @@ + + + + ri:AccountObjectClass + + banderson + banderson + + + delete + ShadowType + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/qpid-config.json b/provisioning/provisioning-impl/src/test/resources/async/qpid-config.json new file mode 100644 index 00000000000..19cc7a1b942 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/qpid-config.json @@ -0,0 +1,33 @@ +{ + "name": "EmbeddedBroker", + "modelVersion": "7.0", + "authenticationproviders": [ + { + "name": "password", + "type": "Plain", + "secureOnlyMechanisms": [], + "users": [{"name": "guest", "password": "guest", "type": "managed"}] + } + ], + "ports": [ + { + "name": "AMQP", + "port": "5672", + "authenticationProvider": "password", + "virtualhostaliases": [ + { + "name": "defaultAlias", + "type": "defaultAlias" + } + ] + } + ], + "virtualhostnodes": [ + { + "name": "default", + "defaultVirtualHostNode": "true", + "type": "Memory", + "virtualHostInitialConfiguration": "{\"type\": \"Memory\" }" + } + ] +} \ No newline at end of file diff --git a/provisioning/provisioning-impl/src/test/resources/async/resource-async-caching-amqp.xml b/provisioning/provisioning-impl/src/test/resources/async/resource-async-caching-amqp.xml new file mode 100644 index 00000000000..1e4d32b5b2c --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/resource-async-caching-amqp.xml @@ -0,0 +1,71 @@ + + + + + + Async Update Resource + + + + + amqp://localhost:5672/ + guest + guest + testQueue + + + + + + + + + + + + + + icfs:uid + icfs:name + icfs:name + icfs:name + + + + + + + + + + + + + + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/resource-async-caching.xml b/provisioning/provisioning-impl/src/test/resources/async/resource-async-caching.xml new file mode 100644 index 00000000000..149f44f84be --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/resource-async-caching.xml @@ -0,0 +1,68 @@ + + + + + + Async Update Resource + + + + + com.evolveum.midpoint.provisioning.impl.async.MockAsyncUpdateSource + + + + + + + + + + + + + + icfs:uid + icfs:name + icfs:name + icfs:name + + + + + + + + + + + + + + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/resource-async-no-caching.xml b/provisioning/provisioning-impl/src/test/resources/async/resource-async-no-caching.xml new file mode 100644 index 00000000000..af699133e0a --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/resource-async-no-caching.xml @@ -0,0 +1,97 @@ + + + + + + Async Update Resource + + + + + com.evolveum.midpoint.provisioning.impl.async.MockAsyncUpdateSource + + + + + + + + + + + + + + icfs:uid + icfs:name + icfs:name + icfs:name + + + + + + + + + + + + + + + + Dummy + + + + async + false + + + + + + + false + + + + + + + + false + + + + diff --git a/provisioning/provisioning-impl/testng-integration.xml b/provisioning/provisioning-impl/testng-integration.xml index 18ed76e715c..cd6c0eeda6b 100644 --- a/provisioning/provisioning-impl/testng-integration.xml +++ b/provisioning/provisioning-impl/testng-integration.xml @@ -73,4 +73,10 @@ + + + + + + diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/AsyncUpdateMessageListener.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/AsyncUpdateMessageListener.java new file mode 100644 index 00000000000..8baa27e0ebd --- /dev/null +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/AsyncUpdateMessageListener.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.api; + +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateMessageType; + +/** + * Listener that receives "raw" async update messages from asynchronous update source (e.g. AMQP, JMS, REST, ...). + */ +public interface AsyncUpdateMessageListener { + + /** + * Processes a message; typically by transforming it into UcfChangeType and invoking a synchronization procedure. + * + * @return true if the message was successfully processed and can be acknowledged; + * false (or by throwing an exception) otherwise + */ + boolean onMessage(AsyncUpdateMessageType message) throws SchemaException; +} diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/AsyncUpdateSource.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/AsyncUpdateSource.java new file mode 100644 index 00000000000..9fe9e23a77b --- /dev/null +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/AsyncUpdateSource.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.api; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.SchemaException; + +/** + * An instance of this interface is created by calling static method create(AsyncUpdateSourceType configuration) + * on the implementing class. + */ +public interface AsyncUpdateSource { + + /** + * Starts listening on this async update source. + * Returns a ListeningActivity that is to be used to stop the listening. + */ + ListeningActivity startListening(AsyncUpdateMessageListener listener) throws SchemaException; + + /** + * Tests this async update source. + */ + void test(OperationResult parentResult); + + // TODO consider adding lifecycle methods like connect(), disconnect(), dispose() here + // However, they are not really needed now, as the existing sources are stateless. + +} diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java index cd84f9681fd..7ab6ff6d09f 100644 --- a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java @@ -40,6 +40,14 @@ public final class Change implements DebugDumpable { private PrismObject oldShadow; private PrismObject currentShadow; + /** + * This means that the change is just a notification that a resource object has changed. To know about its state + * it has to be fetched. For notification-only changes the objectDelta and currentShadow has to be null. + * (And this flag is introduced to distinguish intentional notification-only changes from malformed ones that have + * both currentShadow and objectDelta missing.) + */ + private boolean notificationOnly; + public Change(Collection> identifiers, ObjectDelta change, PrismProperty token) { this.identifiers = identifiers; this.objectDelta = change; @@ -54,11 +62,11 @@ public Change(Collection> identifiers, PrismObject> identifiers, PrismObject currentShadow, PrismObject oldStadow, ObjectDelta objectDetla){ + public Change(Collection> identifiers, PrismObject currentShadow, PrismObject oldShadow, ObjectDelta objectDelta) { this.identifiers = identifiers; this.currentShadow = currentShadow; - this.oldShadow = oldStadow; - this.objectDelta = objectDetla; + this.oldShadow = oldShadow; + this.objectDelta = objectDelta; } public Change(ObjectDelta change, PrismProperty token) { @@ -66,6 +74,16 @@ public Change(ObjectDelta change, PrismProperty token) { this.token = token; } + private Change() { + } + + public static Change createNotificationOnly(Collection> identifiers) { + Change rv = new Change(); + rv.identifiers = identifiers; + rv.notificationOnly = true; + return rv; + } + public ObjectDelta getObjectDelta() { return objectDelta; } @@ -118,6 +136,22 @@ public boolean isTokenOnly() { return identifiers == null && objectDelta == null && currentShadow == null && token != null; } + public void setNotificationOnly(boolean notificationOnly) { + this.notificationOnly = notificationOnly; + } + + public boolean isNotificationOnly() { + return notificationOnly; + } + + public boolean isDelete() { + return objectDelta != null && objectDelta.isDelete(); + } + + // todo what if delta is null, oldShadow is null, current is not null? + public boolean isAdd() { + return objectDelta != null && objectDelta.isAdd(); + } @Override public String toString() { @@ -134,7 +168,11 @@ public String debugDump() { public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, 0); - sb.append("Change\n"); + sb.append("Change"); + if (notificationOnly) { + sb.append(" (notification only)"); + } + sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "identifiers", identifiers, indent + 1); sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "objectDelta", objectDelta, indent + 1); diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ChangeListener.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ChangeListener.java new file mode 100644 index 00000000000..30e0fda717d --- /dev/null +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ChangeListener.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.api; + +/** + * Processes changes encountered on a resource + */ +public interface ChangeListener { + + /** + * Called when the connector learns about a resource change. + * @param change The change. + * @return true if the change was successfully processed and can be acknowledged on the resource; + * false (or a runtime exception) should be returned otherwise + * + * TODO add operation result here? Beware of simultaneous firing of changes. OperationResult is not thread-safe yet. + */ + boolean onChange(Change change); +} diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ConnectorInstance.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ConnectorInstance.java index 990af42a654..b1adc4cc199 100644 --- a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ConnectorInstance.java +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ConnectorInstance.java @@ -65,15 +65,15 @@ */ public interface ConnectorInstance { - public static final String OPERATION_CONFIGURE = ConnectorInstance.class.getName() + ".configure"; - public static final String OPERATION_INITIALIZE = ConnectorInstance.class.getName() + ".initialize"; - public static final String OPERATION_DISPOSE = ConnectorInstance.class.getName() + ".dispose"; + String OPERATION_CONFIGURE = ConnectorInstance.class.getName() + ".configure"; + String OPERATION_INITIALIZE = ConnectorInstance.class.getName() + ".initialize"; + String OPERATION_DISPOSE = ConnectorInstance.class.getName() + ".dispose"; /** * The connector instance will be configured to the state that it can * immediately access the resource. The resource configuration is provided as * a parameter to this method. - * + * * This method may be invoked on connector instance that is already configured. * In that case re-configuration of the connector instance is requested. * The connector instance must be operational at all times, even during re-configuration. @@ -83,7 +83,7 @@ public interface ConnectorInstance { * @throws ConfigurationException */ void configure(PrismContainerValue configuration, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, SchemaException, ConfigurationException; - + ConnectorOperationalStatus getOperationalStatus() throws ObjectNotFoundException; /** @@ -336,4 +336,7 @@ List fetchChanges(ObjectClassComplexTypeDefinition objectClass, PrismPro */ void dispose(); + default ListeningActivity startListeningForChanges(ChangeListener changeListener, OperationResult parentResult) throws SchemaException { + throw new UnsupportedOperationException(); + } } diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ListeningActivity.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ListeningActivity.java new file mode 100644 index 00000000000..eaa600f9afc --- /dev/null +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/ListeningActivity.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.api; + +/** + * + */ +public interface ListeningActivity { + void stop(); +} diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/UcfExpressionEvaluator.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/UcfExpressionEvaluator.java new file mode 100644 index 00000000000..8d5e9b86b3f --- /dev/null +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/UcfExpressionEvaluator.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.api; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; + +import javax.xml.namespace.QName; +import java.util.List; +import java.util.Map; + +/** + * + */ +@Experimental +public interface UcfExpressionEvaluator { + + /** + * Evaluates given expression. + */ + List evaluate(ExpressionType expressionBean, Map variables, QName outputPropertyName, + String contextDescription) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, + ConfigurationException, ExpressionEvaluationException; +} diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/UcfExpressionEvaluatorAware.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/UcfExpressionEvaluatorAware.java new file mode 100644 index 00000000000..25ced9c9c56 --- /dev/null +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/UcfExpressionEvaluatorAware.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.api; + +/** + * + */ +public interface UcfExpressionEvaluatorAware { + + @SuppressWarnings("unused") + UcfExpressionEvaluator getUcfExpressionEvaluator(); + + void setUcfExpressionEvaluator(UcfExpressionEvaluator evaluator); +} diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManagedConnectorInstance.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManagedConnectorInstance.java index 48bafd83188..352336c1e52 100644 --- a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManagedConnectorInstance.java +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManagedConnectorInstance.java @@ -20,6 +20,7 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.provisioning.ucf.api.*; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; @@ -29,10 +30,6 @@ import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.schema.PrismSchema; -import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; -import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; -import com.evolveum.midpoint.provisioning.ucf.api.ManagedConnectorConfiguration; -import com.evolveum.midpoint.provisioning.ucf.api.UcfUtil; import com.evolveum.midpoint.schema.processor.ResourceSchema; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.annotation.Experimental; @@ -99,7 +96,7 @@ public void setPrismContext(PrismContext prismContext) { this.prismContext = prismContext; } - protected ResourceSchema getResourceSchema() { + public ResourceSchema getResourceSchema() { return resourceSchema; } diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManualConnectorInstance.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManualConnectorInstance.java index fef125bf446..e848b69f4e6 100644 --- a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManualConnectorInstance.java +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/connectors/AbstractManualConnectorInstance.java @@ -298,7 +298,7 @@ public ConnectorOperationalStatus getOperationalStatus() throws ObjectNotFoundEx public ResourceSchema fetchResourceSchema(List generateObjectClasses, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, ConfigurationException { // Schema discovery is not supported. Schema must be defined manually. Or other connector has to provide it. - InternalMonitor.recordConnectorOperation("schea"); + InternalMonitor.recordConnectorOperation("schema"); return null; } diff --git a/provisioning/ucf-impl-builtin/pom.xml b/provisioning/ucf-impl-builtin/pom.xml index 4ab4c97ec28..841ddf397ec 100644 --- a/provisioning/ucf-impl-builtin/pom.xml +++ b/provisioning/ucf-impl-builtin/pom.xml @@ -90,8 +90,17 @@ org.springframework spring-core + + org.jetbrains + annotations-java5 + + + com.rabbitmq + amqp-client + ${rabbit-amqp-client.version} + - + javax.xml.bind jaxb-api diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ConnectorFactoryBuiltinImpl.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ConnectorFactoryBuiltinImpl.java index f3502ab415f..595310bddee 100644 --- a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ConnectorFactoryBuiltinImpl.java +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ConnectorFactoryBuiltinImpl.java @@ -26,7 +26,9 @@ import com.evolveum.midpoint.casemgmt.api.CaseManager; import com.evolveum.midpoint.casemgmt.api.CaseManagerAware; import com.evolveum.midpoint.prism.MutablePrismContainerDefinition; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; import com.evolveum.midpoint.prism.schema.MutablePrismSchema; +import com.evolveum.midpoint.provisioning.ucf.api.*; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.task.api.TaskManagerAware; import org.springframework.beans.BeanWrapper; @@ -42,12 +44,6 @@ import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.schema.PrismSchema; import com.evolveum.midpoint.prism.xml.XsdTypeMapper; -import com.evolveum.midpoint.provisioning.ucf.api.ConfigurationProperty; -import com.evolveum.midpoint.provisioning.ucf.api.ConnectorFactory; -import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance; -import com.evolveum.midpoint.provisioning.ucf.api.ManagedConnector; -import com.evolveum.midpoint.provisioning.ucf.api.ManagedConnectorConfiguration; -import com.evolveum.midpoint.provisioning.ucf.api.UcfUtil; import com.evolveum.midpoint.provisioning.ucf.api.connectors.AbstractManagedConnectorInstance; import com.evolveum.midpoint.repo.api.RepositoryAware; import com.evolveum.midpoint.repo.api.RepositoryService; @@ -82,6 +78,7 @@ public class ConnectorFactoryBuiltinImpl implements ConnectorFactory { @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @Autowired private CaseManager caseManager; @Autowired private TaskManager taskManager; + @Autowired private UcfExpressionEvaluator ucfExpressionEvaluator; private Map connectorMap; @@ -218,12 +215,20 @@ private ItemDefinition createPropertyDefinition(MutablePrismContainerDefiniti } // TODO: minOccurs: define which properties are optional/mandatory // TODO: display names, ordering, help texts - QName propType = XsdTypeMapper.toXsdType(baseType); - return configurationContainerDef.createPropertyDefinition(new QName(configurationContainerDef.getName().getNamespaceURI(), propName), - propType, minOccurs, maxOccurs); + QName propType = XsdTypeMapper.getJavaToXsdMapping(baseType); + if (propType == null) { + PrismPropertyDefinition propDef = prismContext.getSchemaRegistry() + .findItemDefinitionByCompileTimeClass(baseType, PrismPropertyDefinition.class); + if (propDef != null) { + propType = propDef.getTypeName(); + } else { + throw new IllegalStateException("Property " + propName + " of " + baseType + " cannot be resolved to a XSD type or a prism property"); + } + } + return configurationContainerDef.createPropertyDefinition( + new QName(configurationContainerDef.getName().getNamespaceURI(), propName), propType, minOccurs, maxOccurs); } - @Override public ConnectorInstance createConnectorInstance(ConnectorType connectorType, String namespace, String instanceName, String desc) throws ObjectNotFoundException, SchemaException { @@ -249,6 +254,9 @@ public ConnectorInstance createConnectorInstance(ConnectorType connectorType, St if (connectorInstance instanceof TaskManagerAware) { ((TaskManagerAware)connectorInstance).setTaskManager(taskManager); } + if (connectorInstance instanceof UcfExpressionEvaluatorAware) { + ((UcfExpressionEvaluatorAware) connectorInstance).setUcfExpressionEvaluator(ucfExpressionEvaluator); + } return connectorInstance; } diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java index 988d1d597ad..434aa6eaec3 100644 --- a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/ManualConnectorInstance.java @@ -134,8 +134,8 @@ public TaskManager getTaskManager() { @Override protected String createTicketAdd(PrismObject object, - Collection additionalOperations, OperationResult result) throws CommunicationException, - GenericFrameworkException, SchemaException, ObjectAlreadyExistsException, ConfigurationException { + Collection additionalOperations, OperationResult result) throws SchemaException, + ObjectAlreadyExistsException { LOGGER.debug("Creating case to add account\n{}", object.debugDump(1)); ObjectDelta objectDelta = DeltaFactory.Object.createAddDelta(object); ObjectDeltaType objectDeltaType = DeltaConvertor.toObjectDeltaType(objectDelta); @@ -153,8 +153,7 @@ protected String createTicketAdd(PrismObject object, @Override protected String createTicketModify(ObjectClassComplexTypeDefinition objectClass, PrismObject shadow, Collection> identifiers, String resourceOid, Collection changes, - OperationResult result) throws ObjectNotFoundException, CommunicationException, - GenericFrameworkException, SchemaException, ObjectAlreadyExistsException, ConfigurationException { + OperationResult result) throws SchemaException, ObjectAlreadyExistsException { LOGGER.debug("Creating case to modify account {}:\n{}", identifiers, DebugUtil.debugDump(changes, 1)); if (InternalsConfig.isSanityChecks()) { if (MiscUtil.hasDuplicates(changes)) { @@ -178,8 +177,7 @@ protected String createTicketModify(ObjectClassComplexTypeDefinition objectClass @Override protected String createTicketDelete(ObjectClassComplexTypeDefinition objectClass, PrismObject shadow, Collection> identifiers, String resourceOid, OperationResult result) - throws ObjectNotFoundException, CommunicationException, GenericFrameworkException, - SchemaException, ConfigurationException { + throws SchemaException { LOGGER.debug("Creating case to delete account {}", identifiers); String shadowName = shadow.getName().toString(); String description = "Please delete resource account: "+shadowName; diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/Amqp091AsyncUpdateSource.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/Amqp091AsyncUpdateSource.java new file mode 100644 index 00000000000..a547ff66124 --- /dev/null +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/Amqp091AsyncUpdateSource.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.impl.builtin.async; + +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateMessageListener; +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateSource; +import com.evolveum.midpoint.provisioning.ucf.api.ListeningActivity; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SystemException; +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.Amqp091MessageType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.Amqp091SourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateSourceType; +import com.rabbitmq.client.*; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Async Update source for AMQP 0.9.1 brokers. + */ +public class Amqp091AsyncUpdateSource implements AsyncUpdateSource { + + private static final Trace LOGGER = TraceManager.getTrace(Amqp091AsyncUpdateSource.class); + + @NotNull private final Amqp091SourceType sourceConfiguration; + @NotNull private final PrismContext prismContext; + @NotNull private final ConnectionFactory connectionFactory; + + private static final long CONNECTION_CLOSE_TIMEOUT = 5000L; + + private Amqp091AsyncUpdateSource(@NotNull Amqp091SourceType sourceConfiguration, @NotNull AsyncUpdateConnectorInstance connectorInstance) { + this.sourceConfiguration = sourceConfiguration; + this.prismContext = connectorInstance.getPrismContext(); + this.connectionFactory = createConnectionFactory(); + } + + private class ListeningActivityImpl implements ListeningActivity { + + private Connection activeConnection; + private Channel activeChannel; + private String activeConsumerTag; + + private final AtomicInteger messagesBeingProcessed = new AtomicInteger(0); + + private ListeningActivityImpl(AsyncUpdateMessageListener listener) { + try { + activeConnection = connectionFactory.newConnection(); + activeChannel = activeConnection.createChannel(); + DeliverCallback deliverCallback = (consumerTag, message) -> { + try { + messagesBeingProcessed.incrementAndGet(); + byte[] body = message.getBody(); + System.out.println("Body: " + new String(body, StandardCharsets.UTF_8)); + boolean successful = listener.onMessage(createAsyncUpdateMessage(message)); + if (successful) { + activeChannel.basicAck(message.getEnvelope().getDeliveryTag(), false); + } else { + rejectMessage(message); + } + } catch (RuntimeException | SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Got exception while processing message", e); + rejectMessage(message); + } finally { + messagesBeingProcessed.decrementAndGet(); + } + }; + activeConsumerTag = activeChannel + .basicConsume(sourceConfiguration.getQueue(), false, deliverCallback, consumerTag -> {}); + System.out.println("Opened consumer " + activeConsumerTag); + } catch (RuntimeException | IOException | TimeoutException e) { + if (activeConnection != null) { + silentlyCloseActiveConnection(); + } + throw new SystemException("Couldn't start listening on " + listener + ": " + e.getMessage(), e); + } + } + + @Override + public void stop() { + if (activeChannel != null && activeConsumerTag != null) { + LOGGER.info("Cancelling consumer {} on {}", activeConsumerTag, activeChannel); + try { + activeChannel.basicCancel(activeConsumerTag); + } catch (IOException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't cancel consumer {} on channel {}", e, activeConsumerTag, activeChannel); + } + } + + // wait until remaining messages are processed + long start = System.currentTimeMillis(); + while (messagesBeingProcessed.get() > 0 && System.currentTimeMillis() - start < CONNECTION_CLOSE_TIMEOUT) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + LOGGER.warn("Waiting for connection to be closed was interrupted"); + break; + } + } + if (messagesBeingProcessed.get() > 0) { + LOGGER.warn("Closing the connection even if {} messages are being processed; they will be unacknowledged", messagesBeingProcessed.get()); + } + if (activeConnection != null) { + silentlyCloseActiveConnection(); + } + } + + @Override + public String toString() { + return "AMQP091-ListeningActivityImpl{" + + "connection=" + activeConnection + + ", consumerTag='" + activeConsumerTag + '\'' + + '}'; + } + + private void rejectMessage(Delivery message) throws IOException { + // TODO implement a policy to selectively requeue or discard messages + activeChannel.basicReject(message.getEnvelope().getDeliveryTag(), true); + } + + private void silentlyCloseActiveConnection() { + try { + System.out.println("Closing " + activeConnection); + activeConnection.close(); + } catch (Throwable t) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't close active connection {}", t, activeConnection); + } + activeConnection = null; + } + } + + private Amqp091MessageType createAsyncUpdateMessage(Delivery message) { + return new Amqp091MessageType() + .sourceName(sourceConfiguration.getName()) + .body(message.getBody()); + // todo other attributes here + } + + public static Amqp091AsyncUpdateSource create(AsyncUpdateSourceType configuration, AsyncUpdateConnectorInstance connectorInstance) { + if (!(configuration instanceof Amqp091SourceType)) { + throw new IllegalArgumentException("AMQP source requires " + Amqp091SourceType.class.getName() + " but got " + + configuration.getClass().getName()); + } + return new Amqp091AsyncUpdateSource((Amqp091SourceType) configuration, connectorInstance); + } + + @Override + public ListeningActivity startListening(AsyncUpdateMessageListener listener) { + return new ListeningActivityImpl(listener); + } + + @NotNull + private ConnectionFactory createConnectionFactory() { + try { + ConnectionFactory connectionFactory = new ConnectionFactory(); + connectionFactory.setUri(sourceConfiguration.getUri()); + connectionFactory.setUsername(sourceConfiguration.getUsername()); + if (sourceConfiguration.getPassword() != null) { + connectionFactory.setPassword(prismContext.getDefaultProtector().decryptString(sourceConfiguration.getPassword())); + } + if (sourceConfiguration.getVirtualHost() != null) { + connectionFactory.setVirtualHost(sourceConfiguration.getVirtualHost()); + } + return connectionFactory; + } catch (URISyntaxException | NoSuchAlgorithmException | KeyManagementException | EncryptionException e) { + throw new SystemException("Couldn't create connection factory: " + e.getMessage(), e); + } + } + + @Override + public void test(OperationResult parentResult) { + OperationResult result = parentResult.createSubresult(getClass().getName() + ".test"); + result.addParam("sourceName", sourceConfiguration.getName()); + try (Connection connection = connectionFactory.newConnection(); + Channel channel = connection.createChannel()) { + LOGGER.info("Connection and channel created OK: {}", channel); + int messageCount = channel.queueDeclarePassive(sourceConfiguration.getQueue()).getMessageCount(); + LOGGER.info("# of messages in queue {}: {}", sourceConfiguration.getQueue(), messageCount); + result.recordSuccess(); + } catch (TimeoutException | IOException e) { + result.recordFatalError("Couldn't connect to AMQP queue: " + e.getMessage(), e); + throw new SystemException("Couldn't connect to AMQP queue: " + e.getMessage(), e); + } + } +} diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/AsyncUpdateConnectorConfiguration.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/AsyncUpdateConnectorConfiguration.java new file mode 100644 index 00000000000..db8fb42504d --- /dev/null +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/AsyncUpdateConnectorConfiguration.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.provisioning.ucf.impl.builtin.async; + +import com.evolveum.midpoint.provisioning.ucf.api.ConfigurationProperty; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateSourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateSourcesType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * + */ +public class AsyncUpdateConnectorConfiguration { + + private AsyncUpdateSourcesType sources; + private ExpressionType transformExpression; + + @ConfigurationProperty + public AsyncUpdateSourcesType getSources() { + return sources; + } + + public void setSources(AsyncUpdateSourcesType sources) { + this.sources = sources; + } + + @ConfigurationProperty + public ExpressionType getTransformExpression() { + return transformExpression; + } + + @SuppressWarnings("unused") + public void setTransformExpression(ExpressionType transformExpression) { + this.transformExpression = transformExpression; + } + + public void validate() { + if (getAllSources().isEmpty()) { + throw new IllegalStateException("No asynchronous update sources were configured"); + } + } + + @NotNull + List getAllSources() { + List allSources = new ArrayList<>(); + if (sources != null) { + allSources.addAll(sources.getAmqp091()); + allSources.addAll(sources.getOther()); + } + return allSources; + } + + boolean hasSourcesChanged(AsyncUpdateConnectorConfiguration other) { + // we can consider weaker comparison here in the future + return other == null || !Objects.equals(other.sources, sources); + } +} diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/AsyncUpdateConnectorInstance.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/AsyncUpdateConnectorInstance.java new file mode 100644 index 00000000000..e5821b87a61 --- /dev/null +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/AsyncUpdateConnectorInstance.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.provisioning.ucf.impl.builtin.async; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.provisioning.ucf.api.*; +import com.evolveum.midpoint.provisioning.ucf.api.connectors.AbstractManagedConnectorInstance; +import com.evolveum.midpoint.schema.SearchResultMetadata; +import com.evolveum.midpoint.schema.constants.ConnectorTestOperation; +import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.schema.processor.*; +import com.evolveum.midpoint.schema.result.AsynchronousOperationResult; +import com.evolveum.midpoint.schema.result.AsynchronousOperationReturnValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.statistics.ConnectorOperationalStatus; +import com.evolveum.midpoint.task.api.StateReporter; +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.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.AsyncUpdateCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PagedSearchCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ReadCapabilityType; + +import javax.xml.namespace.QName; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Connector that is able to obtain and process asynchronous updates. + * It can be used to receive messages from JMS or AMQP messaging systems; or maybe from REST calls in the future. + * + * Currently we keep no state besides the configuration and open listening activities. It is because calls to this + * connector should be really infrequent. Sources are therefore instantiated on demand, e.g. on test() or startListening() calls. + * + */ +@SuppressWarnings("DefaultAnnotationParam") +@ManagedConnector(type="AsyncUpdateConnector", version="1.0.0") +public class AsyncUpdateConnectorInstance extends AbstractManagedConnectorInstance implements UcfExpressionEvaluatorAware { + + @SuppressWarnings("unused") + private static final Trace LOGGER = TraceManager.getTrace(AsyncUpdateConnectorInstance.class); + + private static final com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ObjectFactory CAPABILITY_OBJECT_FACTORY + = new com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ObjectFactory(); + + private AsyncUpdateConnectorConfiguration configuration; + + private final SourceManager sourceManager = new SourceManager(this); + + /** + * The expression evaluator has to come from the higher layers because it needs features not present in UCF impl module. + */ + private UcfExpressionEvaluator ucfExpressionEvaluator; + + /** + * Open listening activities. Needed mainly to be able to restart them on configuration change. + */ + private final Collection openListeningActivities = ConcurrentHashMap.newKeySet(); + + @ManagedConnectorConfiguration + public AsyncUpdateConnectorConfiguration getConfiguration() { + return configuration; + } + + public void setConfiguration(AsyncUpdateConnectorConfiguration configuration) { + configuration.validate(); + boolean sourcesChanged = configuration.hasSourcesChanged(this.configuration); + this.configuration = configuration; + if (sourcesChanged) { + HashSet openActivitiesClone = new HashSet<>(openListeningActivities); + if (!openActivitiesClone.isEmpty()) { + restartListeningActivities(openActivitiesClone); + } + } + } + + @Override + protected void connect(OperationResult result) { + // no-op + } + + @Override + protected void disconnect(OperationResult result) { + // no-op - we act on configuration change in setConfiguration method because + // we need the original configuration to know the difference + } + + @Override + public void test(OperationResult parentResult) { + OperationResult result = parentResult.createSubresult(ConnectorTestOperation.CONNECTOR_CONNECTION.getOperation()); + result.addContext(OperationResult.CONTEXT_IMPLEMENTATION_CLASS, AsyncUpdateConnectorInstance.class); + result.addContext("connector", getConnectorObject().toString()); + Collection sources = sourceManager.createSources(configuration.getAllSources()); + try { + sources.forEach(s -> s.test(result)); + result.computeStatus(); + } catch (RuntimeException e) { + result.recordFatalError("Couldn't test async update sources: " + e.getMessage(), e); + } + } + + @Override + public void dispose() { + // This operation is invoked on system shutdown; for simplicity let's not try to cancel open listening activities + // as they were probably cancelled on respective Async Update tasks going down; and will be cancelled on system + // shutdown anyway. + // + // This will change if the use of dispose() will change. + } + + @Override + public ListeningActivity startListeningForChanges(ChangeListener changeListener, OperationResult parentResult) throws SchemaException { + ConnectorInstanceListeningActivity listeningActivity = new ConnectorInstanceListeningActivity(changeListener); + try { + openListeningActivities.add(listeningActivity); + startListeningInternal(listeningActivity); + } catch (Throwable t) { + openListeningActivities.remove(listeningActivity); + throw t; + } + return listeningActivity; + } + + private void startListeningInternal(ConnectorInstanceListeningActivity listeningActivity) + throws SchemaException { + TransformationalAsyncUpdateMessageListener messageListener = new TransformationalAsyncUpdateMessageListener( + listeningActivity.changeListener, this); + Collection sources = sourceManager.createSources(configuration.getAllSources()); + try { + for (AsyncUpdateSource source : sources) { + listeningActivity.addActivity(source.startListening(messageListener)); + } + } catch (Throwable t) { + listeningActivity.stopInnerActivities(); + throw t; + } + } + + private void restartListeningActivities(Set activities) { + LOGGER.info("Restarting {} open listening activities", activities.size()); + + for (ConnectorInstanceListeningActivity activity : activities) { + try { + LOGGER.debug("Stopping listening activity {}", activity); + activity.stopInnerActivities(); + LOGGER.debug("Starting listening activity {} again", activity); + startListeningInternal(activity); + } catch (RuntimeException | SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't restart listening activity {} on {}", e, activity, this); + } + } + } + + @Override + public ConnectorOperationalStatus getOperationalStatus() { + ConnectorOperationalStatus status = new ConnectorOperationalStatus(); + status.setConnectorClassName(this.getClass().getName()); + return status; + } + + @Override + public Collection fetchCapabilities(OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("capabilities"); + + Collection capabilities = new ArrayList<>(); + capabilities.add(CAPABILITY_OBJECT_FACTORY.createAsyncUpdate(new AsyncUpdateCapabilityType())); + capabilities.add(CAPABILITY_OBJECT_FACTORY.createRead(new ReadCapabilityType().cachingOnly(true))); + return capabilities; + + // TODO activation, credentials? + } + + @Override + public UcfExpressionEvaluator getUcfExpressionEvaluator() { + return ucfExpressionEvaluator; + } + + @Override + public void setUcfExpressionEvaluator(UcfExpressionEvaluator evaluator) { + this.ucfExpressionEvaluator = evaluator; + } + + ExpressionType getTransformExpression() { + return configuration.getTransformExpression(); + } + + //region Unsupported operations + @Override + public ResourceSchema fetchResourceSchema(List generateObjectClasses, OperationResult parentResult) { + // Schema discovery is not supported. Schema must be defined manually. Or other connector has to provide it. + InternalMonitor.recordConnectorOperation("schema"); + return null; + } + + @Override + public PrismObject fetchObject(ResourceObjectIdentification resourceObjectIdentification, + AttributesToReturn attributesToReturn, StateReporter reporter, OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("fetchObject"); + return null; + } + + @Override + public SearchResultMetadata search(ObjectClassComplexTypeDefinition objectClassDefinition, ObjectQuery query, + ShadowResultHandler handler, AttributesToReturn attributesToReturn, + PagedSearchCapabilityType pagedSearchConfigurationType, SearchHierarchyConstraints searchHierarchyConstraints, + StateReporter reporter, OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("search"); + return null; + } + + @Override + public int count(ObjectClassComplexTypeDefinition objectClassDefinition, ObjectQuery query, + PagedSearchCapabilityType pagedSearchConfigurationType, StateReporter reporter, OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("count"); + return 0; + } + + @Override + public AsynchronousOperationReturnValue>> addObject(PrismObject object, + Collection additionalOperations, StateReporter reporter, OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("addObject"); + return null; + } + + @Override + public AsynchronousOperationReturnValue> modifyObject( + ResourceObjectIdentification identification, PrismObject shadow, Collection changes, + ConnectorOperationOptions options, StateReporter reporter, OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("modifyObject"); + return null; + } + + @Override + public AsynchronousOperationResult deleteObject(ObjectClassComplexTypeDefinition objectClass, + Collection additionalOperations, PrismObject shadow, + Collection> identifiers, StateReporter reporter, OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("deleteObject"); + return null; + } + + @Override + public Object executeScript(ExecuteProvisioningScriptOperation scriptOperation, StateReporter reporter, + OperationResult parentResult) { + InternalMonitor.recordConnectorOperation("executeScript"); + return null; + } + + @Override + public PrismProperty deserializeToken(Object serializedToken) { + return null; + } + + @Override + public PrismProperty fetchCurrentToken(ObjectClassComplexTypeDefinition objectClass, StateReporter reporter, + OperationResult parentResult) { + return null; + } + + @Override + public List fetchChanges(ObjectClassComplexTypeDefinition objectClass, PrismProperty lastToken, + AttributesToReturn attrsToReturn, StateReporter reporter, OperationResult parentResult) { + return null; + } + + //endregion + + //region Listening activity + private class ConnectorInstanceListeningActivity implements ListeningActivity { + + private final List activities = new ArrayList<>(); + private final ChangeListener changeListener; + + ConnectorInstanceListeningActivity(ChangeListener changeListener) { + this.changeListener = changeListener; + } + + @Override + public void stop() { + openListeningActivities.remove(this); + stopInnerActivities(); + } + + private void stopInnerActivities() { + for (ListeningActivity activity : activities) { + try { + activity.stop(); + } catch (RuntimeException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't stop listening on {}", e, activity); + } + } + activities.clear(); + } + + void addActivity(ListeningActivity activity) { + activities.add(activity); + } + + @Override + public String toString() { + return "ConnectorInstanceListeningActivity{" + activities + "}"; + } + } + + //endregion +} diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/SourceManager.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/SourceManager.java new file mode 100644 index 00000000000..2d9cc4b40fe --- /dev/null +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/SourceManager.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.impl.builtin.async; + +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateSource; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.Amqp091SourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AsyncUpdateSourceType; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * Creates AsyncUpdateSource objects based on their configurations (AsyncUpdateSourceType objects). + */ +class SourceManager { + + private static final Trace LOGGER = TraceManager.getTrace(SourceManager.class); + + @NotNull private final AsyncUpdateConnectorInstance connectorInstance; + + SourceManager(@NotNull AsyncUpdateConnectorInstance connectorInstance) { + this.connectorInstance = connectorInstance; + } + + @NotNull + Collection createSources(Collection sourceConfigurations) { + if (sourceConfigurations.isEmpty()) { + throw new IllegalStateException("No asynchronous update sources are configured"); + } + return sourceConfigurations.stream() + .map(this::createSource) + .collect(Collectors.toList()); + } + + @NotNull + private AsyncUpdateSource createSource(AsyncUpdateSourceType sourceConfiguration) { + LOGGER.trace("Creating source from configuration: {}", sourceConfiguration); + Class sourceClass = determineSourceClass(sourceConfiguration); + try { + Method createMethod = sourceClass.getMethod("create", AsyncUpdateSourceType.class, AsyncUpdateConnectorInstance.class); + AsyncUpdateSource source = (AsyncUpdateSource) createMethod.invoke(null, sourceConfiguration, connectorInstance); + if (source == null) { + throw new SystemException("Asynchronous update source was not created for " + sourceClass); + } + return source; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassCastException e) { + throw new SystemException("Couldn't instantiate asynchronous update source class " + sourceClass + ": " + e.getMessage(), e); + } + } + + private Class determineSourceClass(AsyncUpdateSourceType cfg) { + if (cfg.getClassName() != null) { + try { + //noinspection unchecked + return ((Class) Class.forName(cfg.getClassName())); + } catch (ClassNotFoundException e) { + throw new SystemException("Couldn't find async source implementation class: " + cfg.getClassName()); + } + } else if (cfg instanceof Amqp091SourceType) { + return Amqp091AsyncUpdateSource.class; + } else { + throw new SystemException("Couldn't find async update source class for configuration: " + cfg.getClass()); + } + } +} diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/TransformationalAsyncUpdateMessageListener.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/TransformationalAsyncUpdateMessageListener.java new file mode 100644 index 00000000000..8adfd4992d4 --- /dev/null +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/TransformationalAsyncUpdateMessageListener.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2010-2019 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.evolveum.midpoint.provisioning.ucf.impl.builtin.async; + +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemName; +import com.evolveum.midpoint.provisioning.ucf.api.AsyncUpdateMessageListener; +import com.evolveum.midpoint.provisioning.ucf.api.Change; +import com.evolveum.midpoint.provisioning.ucf.api.ChangeListener; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.SchemaConstantsGenerated; +import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; +import com.evolveum.midpoint.schema.processor.ResourceAttribute; +import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition; +import com.evolveum.midpoint.schema.processor.ResourceSchema; +import com.evolveum.midpoint.util.QNameUtil; +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.*; +import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; +import org.jetbrains.annotations.NotNull; + +import javax.xml.namespace.QName; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.toPrismObject; +import static com.evolveum.midpoint.util.MiscUtil.emptyIfNull; + +/** + * Transforms AsyncUpdateMessageType objects to Change ones (via UcfChangeType intermediary). + */ +public class TransformationalAsyncUpdateMessageListener implements AsyncUpdateMessageListener { + + private static final Trace LOGGER = TraceManager.getTrace(TransformationalAsyncUpdateMessageListener.class); + + private static final QName VAR_MESSAGE = new QName("message"); + + @NotNull private final ChangeListener changeListener; + @NotNull private final AsyncUpdateConnectorInstance connectorInstance; + + TransformationalAsyncUpdateMessageListener(@NotNull ChangeListener changeListener, + @NotNull AsyncUpdateConnectorInstance connectorInstance) { + this.changeListener = changeListener; + this.connectorInstance = connectorInstance; + } + + @Override + public boolean onMessage(AsyncUpdateMessageType message) throws SchemaException { + LOGGER.trace("Got {}", message); + Map variables = new HashMap<>(); + variables.put(VAR_MESSAGE, message); + List changeBeans; + try { + ExpressionType transformExpression = connectorInstance.getTransformExpression(); + if (transformExpression != null) { + changeBeans = connectorInstance.getUcfExpressionEvaluator().evaluate(transformExpression, variables, + SchemaConstantsGenerated.C_UCF_CHANGE, "computing UCF change from async update"); + } else { + changeBeans = unwrapMessage(message); + } + } catch (RuntimeException | SchemaException | ObjectNotFoundException | SecurityViolationException | CommunicationException | + ConfigurationException | ExpressionEvaluationException e) { + throw new SystemException("Couldn't evaluate message transformation expression: " + e.getMessage(), e); + } + boolean ok = true; + for (UcfChangeType changeBean : changeBeans) { + // intentionally in this order - to process changes even after failure + // (if listener wants to fail fast, it can throw an exception) + ok = changeListener.onChange(createChange(changeBean)) && ok; + } + return ok; + } + + /** + * Mainly for testing purposes we provide an option to simply unwrap UcfChangeType from "any data" message. + */ + private List unwrapMessage(AsyncUpdateMessageType message) throws SchemaException { + Object data; + if (message instanceof AnyDataAsyncUpdateMessageType) { + data = ((AnyDataAsyncUpdateMessageType) message).getData(); + } else if (message instanceof Amqp091MessageType) { + String text = new String(((Amqp091MessageType) message).getBody(), StandardCharsets.UTF_8); + data = getPrismContext().parserFor(text).xml().parseRealValue(); + } else { + throw new SchemaException( + "Cannot apply trivial message transformation: message is not 'any data' nor AMQP one. Please " + + "specify transformExpression parameter"); + } + if (data instanceof UcfChangeType) { + return Collections.singletonList((UcfChangeType) data); + } else { + throw new SchemaException("Cannot apply trivial message transformation: message does not contain " + + "UcfChangeType object (it is " + data.getClass().getName() + " instead). Please specify transformExpression parameter"); + } + } + + private Change createChange(UcfChangeType changeBean) throws SchemaException { + QName objectClassName = changeBean.getObjectClass(); + if (objectClassName == null) { + throw new SchemaException("Object class name is null in " + changeBean); + } + ObjectClassComplexTypeDefinition objectClassDef = getResourceSchema().findObjectClassDefinition(objectClassName); + if (objectClassDef == null) { + throw new SchemaException("Object class " + objectClassName + " not found in " + getResourceSchema()); + } + ObjectDelta delta; + ObjectDeltaType deltaBean = changeBean.getObjectDelta(); + if (deltaBean != null) { + setFromDefaults((ShadowType) deltaBean.getObjectToAdd(), objectClassName); + if (deltaBean.getObjectType() == null) { + deltaBean.setObjectType(ShadowType.COMPLEX_TYPE); + } + delta = DeltaConvertor.createObjectDelta(deltaBean, getPrismContext(), true); + } else { + delta = null; + } + setFromDefaults(changeBean.getObject(), objectClassName); + Collection> identifiers = getIdentifiers(changeBean, objectClassDef); + Change change = new Change(identifiers, toPrismObject(changeBean.getObject()), null, delta); + change.setObjectClassDefinition(objectClassDef); + if (change.getCurrentShadow() == null && change.getObjectDelta() == null) { + change.setNotificationOnly(true); + } + return change; + } + + private void setFromDefaults(ShadowType object, QName objectClassName) { + if (object != null) { + if (object.getObjectClass() == null) { + object.setObjectClass(objectClassName); + } + } + } + + private Collection> getIdentifiers(UcfChangeType changeBean, ObjectClassComplexTypeDefinition ocDef) + throws SchemaException { + Collection> rv = new ArrayList<>(); + PrismContainerValue attributesPcv; + boolean mayContainNonIdentifiers; + if (changeBean.getIdentifiers() != null) { + //noinspection unchecked + attributesPcv = changeBean.getIdentifiers().asPrismContainerValue(); + mayContainNonIdentifiers = false; + } else if (changeBean.getObject() != null) { + //noinspection unchecked + attributesPcv = changeBean.getObject().getAttributes().asPrismContainerValue(); + mayContainNonIdentifiers = true; + } else if (changeBean.getObjectDelta() != null && changeBean.getObjectDelta().getChangeType() == ChangeTypeType.ADD && + changeBean.getObjectDelta().getObjectToAdd() instanceof ShadowType) { + //noinspection unchecked + attributesPcv = ((ShadowType) changeBean.getObjectDelta().getObjectToAdd()).getAttributes().asPrismContainerValue(); + mayContainNonIdentifiers = true; + } else { + throw new SchemaException("Change does not contain identifiers"); + } + Set identifiers = ocDef.getAllIdentifiers().stream().map(ItemDefinition::getName).collect(Collectors.toSet()); + for (Item attribute : emptyIfNull(attributesPcv.getItems())) { + if (QNameUtil.matchAny(attribute.getElementName(), identifiers)) { + if (attribute instanceof ResourceAttribute) { + rv.add(((ResourceAttribute) attribute).clone()); + } else { + ResourceAttributeDefinition definition = ocDef + .findAttributeDefinition(attribute.getElementName()); + if (definition == null) { + throw new SchemaException("No definition of " + attribute.getElementName() + " in " + ocDef); + } + ResourceAttribute resourceAttribute = definition.instantiate(); + for (Object realValue : attribute.getRealValues()) { + resourceAttribute.addRealValue(realValue); + } + rv.add(resourceAttribute); + } + } else { + if (!mayContainNonIdentifiers) { + LOGGER.warn("Attribute {} is not an identifier in {} -- ignoring it", attribute, ocDef); + } + } + } + return rv; + } + + private PrismContext getPrismContext() { + return connectorInstance.getPrismContext(); + } + + private ResourceSchema getResourceSchema() { + return connectorInstance.getResourceSchema(); + } +} diff --git a/provisioning/ucf-impl-connid/pom.xml b/provisioning/ucf-impl-connid/pom.xml index fcf10cdc49a..eb6edfdc2db 100644 --- a/provisioning/ucf-impl-connid/pom.xml +++ b/provisioning/ucf-impl-connid/pom.xml @@ -102,6 +102,10 @@ net.tirasa.connid connector-framework-internal + + org.jetbrains + annotations-java5 + org.slf4j jul-to-slf4j diff --git a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java index 502b41cecc3..4cfb2538845 100644 --- a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java +++ b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java @@ -168,6 +168,7 @@ import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.UpdateCapabilityType; import com.evolveum.prism.xml.ns._public.query_3.OrderDirectionType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import org.jetbrains.annotations.NotNull; /** * Implementation of ConnectorInstance for ConnId connectors. @@ -1703,10 +1704,11 @@ public PrismProperty fetchCurrentToken(ObjectClassComplexTypeDefinition o return property; } + @NotNull @Override - public List fetchChanges(ObjectClassComplexTypeDefinition objectClass, PrismProperty lastToken, AttributesToReturn attrsToReturn, StateReporter reporter, - OperationResult parentResult) throws CommunicationException, GenericFrameworkException, - SchemaException, ConfigurationException { + public List fetchChanges(ObjectClassComplexTypeDefinition objectClass, PrismProperty lastToken, + AttributesToReturn attrsToReturn, StateReporter reporter, OperationResult parentResult) + throws CommunicationException, GenericFrameworkException, SchemaException { OperationResult result = parentResult.createSubresult(ConnectorInstance.class.getName() + ".fetchChanges"); @@ -2331,7 +2333,7 @@ private ResourceAttributeDefinition getUidDefinition(ResourceObjectIdentificatio return primaryIdentifier.getDefinition(); } - + @NotNull private List getChangesFromSyncDeltas(ObjectClass connIdObjClass, Collection connIdDeltas, PrismSchema schema, OperationResult parentResult) throws SchemaException, GenericFrameworkException { diff --git a/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CounterManager.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CounterManager.java new file mode 100644 index 00000000000..c30e0204788 --- /dev/null +++ b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CounterManager.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010-2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.repo.api; + +import java.util.Collection; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyRuleType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; + +/** + * @author katka + * + */ +public interface CounterManager { + + + CounterSepcification getCounterSpec(TaskType task, String policyRuleId, PolicyRuleType policyRule); + void cleanupCounters(String taskOid); + Collection listCounters(); + void removeCounter(CounterSepcification counterSpecification); +} diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/CounterSepcification.java b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CounterSepcification.java similarity index 63% rename from repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/CounterSepcification.java rename to repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CounterSepcification.java index f083703bf00..fd4cbc259d1 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/CounterSepcification.java +++ b/repo/repo-api/src/main/java/com/evolveum/midpoint/repo/api/CounterSepcification.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.evolveum.midpoint.repo.common; - -import java.time.Duration; +package com.evolveum.midpoint.repo.api; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyRuleType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyThresholdType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; /** * @author katka @@ -29,7 +29,16 @@ public class CounterSepcification implements DebugDumpable { private int count = 0; private long counterStart; - private PolicyThresholdType policyThreshold; + + private TaskType task; + private PolicyRuleType policyRule; + private String policyRuleId; + + public CounterSepcification(TaskType task, String policyRuleId, PolicyRuleType policyRule) { + this.task = task; + this.policyRuleId = policyRuleId; + this.policyRule = policyRule; + } public int getCount() { return count; @@ -43,21 +52,28 @@ public void setCount(int count) { public void setCounterStart(long counterStart) { this.counterStart = counterStart; } - - /** - * @return the policyThreshold - */ + public PolicyThresholdType getPolicyThreshold() { - return policyThreshold; + return policyRule.getPolicyThreshold(); } - /** - * @param policyThreshold the policyThreshold to set - */ - public void setPolicyThreshold(PolicyThresholdType policyThreshold) { - this.policyThreshold = policyThreshold; + public String getTaskName() { + return task.getName().getOrig(); } + public String getPolicyRuleName() { + return policyRule.getName(); + } + + public String getTaskOid() { + return task.getOid(); + } + + public String getPolicyRuleId() { + return policyRuleId; + } + + public void reset(long currentTimeMillis) { count = 0; counterStart = currentTimeMillis; @@ -66,9 +82,11 @@ public void reset(long currentTimeMillis) { @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); + sb.append("Counter for: ").append(task).append(", policy rule: ").append(policyRule).append("\n"); sb.append("Current count: ").append(count).append("\n"); sb.append("Counter start: ").append(XmlTypeConverter.createXMLGregorianCalendar(counterStart)).append("\n"); - sb.append("Thresholds: \n").append(policyThreshold.toString()); + + sb.append("Thresholds: \n").append(getPolicyThreshold().toString()); return sb.toString(); } diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/CounterManager.java b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheCounterManager.java similarity index 71% rename from repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/CounterManager.java rename to repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheCounterManager.java index 39728747aa4..d6e8cc82b3b 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/CounterManager.java +++ b/repo/repo-cache/src/main/java/com/evolveum/midpoint/repo/cache/CacheCounterManager.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.evolveum.midpoint.repo.common; +package com.evolveum.midpoint.repo.cache; +import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -26,12 +27,15 @@ import org.springframework.stereotype.Component; import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; -import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.repo.api.CounterManager; +import com.evolveum.midpoint.repo.api.CounterSepcification; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyRuleType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PolicyThresholdType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TimeIntervalType; /** @@ -39,15 +43,15 @@ * */ @Component -public class CounterManager { +public class CacheCounterManager implements CounterManager { @Autowired private Clock clock; - private static final Trace LOGGER = TraceManager.getTrace(CounterManager.class); + private static final Trace LOGGER = TraceManager.getTrace(CacheCounterManager.class); private Map countersMap = new ConcurrentHashMap<>(); - public synchronized CounterSepcification registerCounter(Task task, String policyRuleId, PolicyRuleType policyRule) { + public synchronized CounterSepcification registerCounter(TaskType task, String policyRuleId, PolicyRuleType policyRule) { if (task.getOid() == null) { LOGGER.trace("Not persistent task, skipping registering counter."); @@ -57,10 +61,10 @@ public synchronized CounterSepcification registerCounter(Task task, String polic CounterKey key = new CounterKey(task.getOid(), policyRuleId); CounterSepcification counterSpec = countersMap.get(key); if (counterSpec == null) { - return initCleanCounter(key, policyRule); + return initCleanCounter(key, task, policyRule); } - if (isResetCounter(counterSpec)) { + if (isResetCounter(counterSpec, false)) { return refreshCounter(key, counterSpec); } @@ -68,7 +72,7 @@ public synchronized CounterSepcification registerCounter(Task task, String polic } - private boolean isResetCounter(CounterSepcification counterSpec) { + private boolean isResetCounter(CounterSepcification counterSpec, boolean removeIfTimeIntervalNotSpecified) { PolicyThresholdType threshold = counterSpec.getPolicyThreshold(); if (threshold == null) { @@ -78,17 +82,18 @@ private boolean isResetCounter(CounterSepcification counterSpec) { TimeIntervalType timeInterval = threshold.getTimeInterval(); if (timeInterval == null) { - return false; + return removeIfTimeIntervalNotSpecified; } if (timeInterval.getInterval() == null) { - return false; + return removeIfTimeIntervalNotSpecified; } Duration interval = timeInterval.getInterval(); - return !XmlTypeConverter.isAfterInterval(XmlTypeConverter.createXMLGregorianCalendar(counterSpec.getCounterStart()), interval, clock.currentTimeXMLGregorianCalendar()); + return XmlTypeConverter.isAfterInterval(XmlTypeConverter.createXMLGregorianCalendar(counterSpec.getCounterStart()), interval, clock.currentTimeXMLGregorianCalendar()); } + @Override public void cleanupCounters(String taskOid) { Set keys = countersMap.keySet(); @@ -100,14 +105,16 @@ public void cleanupCounters(String taskOid) { } for (CounterKey counterToRemove : counersToRemove) { - countersMap.remove(counterToRemove); + CounterSepcification spec = countersMap.get(counterToRemove); + if (isResetCounter(spec, true)) { + countersMap.remove(counterToRemove); + } } } - private CounterSepcification initCleanCounter(CounterKey key, PolicyRuleType policyRule) { - CounterSepcification counterSpec = new CounterSepcification(); + private CounterSepcification initCleanCounter(CounterKey key, TaskType task, PolicyRuleType policyRule) { + CounterSepcification counterSpec = new CounterSepcification(task, key.policyRuleId, policyRule); counterSpec.setCounterStart(clock.currentTimeMillis()); - counterSpec.setPolicyThreshold(policyRule.getPolicyThreshold()); countersMap.put(key, counterSpec); return counterSpec; } @@ -118,7 +125,8 @@ private CounterSepcification refreshCounter(CounterKey key, CounterSepcification return counterSpec; } - public CounterSepcification getCounterSpec(Task task, String policyRuleId, PolicyRuleType policyRule) { + @Override + public CounterSepcification getCounterSpec(TaskType task, String policyRuleId, PolicyRuleType policyRule) { if (task.getOid() == null) { LOGGER.trace("Cannot get counter spec for task without oid"); return null; @@ -132,7 +140,7 @@ public CounterSepcification getCounterSpec(Task task, String policyRuleId, Polic return registerCounter(task, policyRuleId, policyRule); } - if (isResetCounter(counterSpec)) { + if (isResetCounter(counterSpec, false)) { counterSpec = refreshCounter(key, counterSpec); } @@ -140,6 +148,17 @@ public CounterSepcification getCounterSpec(Task task, String policyRuleId, Polic return counterSpec; } + @Override + public Collection listCounters() { + return countersMap.values(); + } + + @Override + public void removeCounter(CounterSepcification counterSpecification) { + CounterKey key = new CounterKey(counterSpecification.getTaskOid(), counterSpecification.getPolicyRuleId()); + countersMap.remove(key); + } + class CounterKey { private String oid; diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java index 72ef8ba2cfa..df9b37062e0 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/expression/ExpressionFactory.java @@ -22,9 +22,7 @@ import javax.xml.namespace.QName; import com.evolveum.midpoint.common.LocalizationService; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.repo.common.CacheRegistry; import com.evolveum.midpoint.repo.common.Cacheable; import com.evolveum.midpoint.repo.common.ObjectResolver; @@ -96,6 +94,14 @@ public Expression makeExpre return expression; } + public Expression,PrismPropertyDefinition> makePropertyExpression( + ExpressionType expressionType, QName outputPropertyName, String shortDesc, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException { + //noinspection unchecked + PrismPropertyDefinition outputDefinition = prismContext.getSchemaRegistry().findPropertyDefinitionByElementName(outputPropertyName); + return makeExpression(expressionType, outputDefinition, shortDesc, task, result); + } + private Expression createExpression(ExpressionType expressionType, D outputDefinition, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeTaskHandler.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeTaskHandler.java index d4fac57c7d5..1c89db2a870 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeTaskHandler.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeTaskHandler.java @@ -17,30 +17,27 @@ import static com.evolveum.midpoint.prism.PrismProperty.getRealValue; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.function.Function; import javax.xml.namespace.QName; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.FilterUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.TaskWorkStateTypeUtil; -import com.evolveum.midpoint.task.api.*; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.query.FilterUtil; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.CounterManager; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.SelectorOptions; @@ -48,10 +45,33 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.TaskWorkStateTypeUtil; +import com.evolveum.midpoint.task.api.ExitWorkBucketHandlerException; +import com.evolveum.midpoint.task.api.RunningTask; +import com.evolveum.midpoint.task.api.StatisticsCollectionStrategy; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.task.api.TaskRunResult; import com.evolveum.midpoint.task.api.TaskRunResult.TaskRunResultStatus; +import com.evolveum.midpoint.task.api.TaskWorkBucketProcessingResult; +import com.evolveum.midpoint.task.api.WorkBucketAwareTaskHandler; import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.IterationMethodType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SelectorQualifiedGetOptionsType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskPartitionDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkBucketType; import com.evolveum.prism.xml.ns._public.query_3.QueryType; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; @@ -92,8 +112,6 @@ public abstract class AbstractSearchIterativeTaskHandler + + diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index a1b9ff5d600..b6463a4c794 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -440,9 +440,29 @@ protected PrismObject addResourceFromFile(File file, String connec protected PrismObject addResourceFromFile(File file, String connectorType, boolean overwrite, OperationResult result) throws JAXBException, SchemaException, ObjectAlreadyExistsException, EncryptionException, IOException { - LOGGER.trace("addObjectFromFile: {}, connector type {}", file, connectorType); + return addResourceFromFile(file, Collections.singletonList(connectorType), overwrite, result); + } + + protected PrismObject addResourceFromFile(File file, List connectorTypes, boolean overwrite, OperationResult result) + throws JAXBException, SchemaException, ObjectAlreadyExistsException, EncryptionException, IOException { + LOGGER.trace("addObjectFromFile: {}, connector types {}", file, connectorTypes); PrismObject resource = prismContext.parseObject(file); - fillInConnectorRef(resource, connectorType, result); + return addResourceFromObject(resource, connectorTypes, overwrite, result); + } + + @NotNull + protected PrismObject addResourceFromObject(PrismObject resource, List connectorTypes, + boolean overwrite, OperationResult result) + throws SchemaException, EncryptionException, + ObjectAlreadyExistsException { + for (int i = 0; i < connectorTypes.size(); i++) { + String type = connectorTypes.get(i); + if (i == 0) { + fillInConnectorRef(resource, type, result); + } else { + fillInAdditionalConnectorRef(resource, i-1, type, result); + } + } CryptoUtil.encryptValues(protector, resource); display("Adding resource ", resource); RepoAddOptions options = null; @@ -504,6 +524,15 @@ protected void fillInAdditionalConnectorRef(PrismObject resource, } } + protected void fillInAdditionalConnectorRef(PrismObject resource, int connectorIndex, String connectorType, OperationResult result) + throws SchemaException { + ResourceType resourceType = resource.asObjectable(); + PrismObject connectorPrism = findConnectorByType(connectorType, result); + ConnectorInstanceSpecificationType additionalConnector = resourceType.getAdditionalConnector().get(connectorIndex); + ObjectReferenceType ref = new ObjectReferenceType().oid(connectorPrism.getOid()); + additionalConnector.setConnectorRef(ref); + } + protected SystemConfigurationType getSystemConfiguration() throws ObjectNotFoundException, SchemaException { OperationResult result = new OperationResult(AbstractIntegrationTest.class.getName()+".getSystemConfiguration"); try { @@ -2162,42 +2191,15 @@ protected void assertExceptionUserFriendly(CommonException e, String expectedMes } protected ParallelTestThread[] multithread(final String TEST_NAME, MultithreadRunner lambda, int numberOfThreads, Integer randomStartDelayRange) { - ParallelTestThread[] threads = new ParallelTestThread[numberOfThreads]; - for (int i = 0; i < numberOfThreads; i++) { - threads[i] = new ParallelTestThread(i, - (ii) -> { - randomDelay(randomStartDelayRange); - LOGGER.info("{} starting", Thread.currentThread().getName()); - lambda.run(ii); - }); - threads[i].setName("Thread " + (i+1) + " of " + numberOfThreads); - threads[i].start(); - } - return threads; + return TestUtil.multithread(TEST_NAME, lambda, numberOfThreads, randomStartDelayRange); } protected void randomDelay(Integer range) { - if (range == null) { - return; - } - try { - Thread.sleep(RND.nextInt(range)); - } catch (InterruptedException e) { - // Nothing to do, really - } + TestUtil.randomDelay(range); } protected void waitForThreads(ParallelTestThread[] threads, long timeout) throws InterruptedException { - for (int i = 0; i < threads.length; i++) { - if (threads[i].isAlive()) { - System.out.println("Waiting for " + threads[i]); - threads[i].join(timeout); - } - Throwable threadException = threads[i].getException(); - if (threadException != null) { - throw new AssertionError("Test thread "+i+" failed: "+threadException.getMessage(), threadException); - } - } + TestUtil.waitForThreads(threads, timeout); } protected ItemPath getMetadataPath(QName propName) { diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyResourceContoller.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyResourceContoller.java index 145ec444748..83c2db0cb92 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyResourceContoller.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyResourceContoller.java @@ -410,6 +410,12 @@ public DummyOrg addOrgTop() throws ConnectException, FileNotFoundException, Obje return org; } + public DummyAccount addAccount(String userId) throws ObjectAlreadyExistsException, SchemaViolationException, ConnectException, FileNotFoundException, ConflictException, InterruptedException { + DummyAccount account = new DummyAccount(userId); + dummyResource.addAccount(account); + return account; + } + public DummyAccount addAccount(String userId, String fullName) throws ObjectAlreadyExistsException, SchemaViolationException, ConnectException, FileNotFoundException, ConflictException, InterruptedException { DummyAccount account = new DummyAccount(userId); account.setEnabled(true); diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java index a24d1258075..dd16e84570e 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java @@ -175,8 +175,12 @@ protected void assertPolyStringProperty(QName propName, String expectedOrig) { protected void assertPolyStringPropertyMulti(QName propName, String... expectedOrigs) { PrismProperty prop = getObject().findProperty(ItemName.fromQName(propName)); - assertNotNull("No "+propName.getLocalPart()+" in "+desc(), prop); - PrismAsserts.assertEqualsPolyStringMulti("Wrong "+propName.getLocalPart()+" in "+desc(), prop.getRealValues(), expectedOrigs); + if (expectedOrigs.length > 0) { + assertNotNull("No " + propName.getLocalPart() + " in " + desc(), prop); + PrismAsserts.assertEqualsPolyStringMulti("Wrong "+propName.getLocalPart()+" in "+desc(), prop.getRealValues(), expectedOrigs); + } else { + assertTrue("Property is not empty even if it should be: " + prop, prop == null || prop.isEmpty()); + } } protected void assertPropertyEquals(QName propName, T expected) { diff --git a/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Task.java b/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Task.java index 78f0b0aaf3c..a5c23827fb9 100644 --- a/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Task.java +++ b/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Task.java @@ -538,7 +538,6 @@ void setBindingImmediate(TaskBinding value, OperationResult parentResult) /** * Returns specified single-valued property real value from the extension - * @param propertyName * @return null if extension or property does not exist. */ T getExtensionPropertyRealValue(ItemName propertyName); diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/LightweightPartitioningTaskHandler.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/LightweightPartitioningTaskHandler.java index c26395a9c3e..98da206d219 100644 --- a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/LightweightPartitioningTaskHandler.java +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/handlers/LightweightPartitioningTaskHandler.java @@ -20,17 +20,24 @@ import javax.annotation.PostConstruct; -import com.evolveum.midpoint.task.api.*; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.repo.api.CounterManager; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.RunningTask; +import com.evolveum.midpoint.task.api.StatisticsCollectionStrategy; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskCategory; +import com.evolveum.midpoint.task.api.TaskConstants; +import com.evolveum.midpoint.task.api.TaskHandler; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.task.api.TaskRunResult; import com.evolveum.midpoint.task.api.TaskRunResult.TaskRunResultStatus; import com.evolveum.midpoint.task.quartzimpl.RunningTaskQuartzImpl; -import com.evolveum.midpoint.task.quartzimpl.TaskManagerQuartzImpl; import com.evolveum.midpoint.task.quartzimpl.execution.HandlerExecutor; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -51,7 +58,7 @@ public class LightweightPartitioningTaskHandler implements TaskHandler { @Autowired private PrismContext prismContext; @Autowired private TaskManager taskManager; @Autowired private HandlerExecutor handlerExecutor; -// @Autowired private TaskManager taskManager; + @Autowired private CounterManager counterManager; @PostConstruct @@ -113,6 +120,7 @@ public TaskRunResult run(RunningTask task, TaskPartitionDefinitionType taskParti runResult.setProgress(runResult.getProgress() + 1); opResult.computeStatusIfUnknown(); + counterManager.cleanupCounters(task.getOid()); return runResult; } @@ -159,8 +167,7 @@ public StatisticsCollectionStrategy getStatisticsCollectionStrategy() { @Override public String getCategoryName(Task task) { - // TODO Auto-generated method stub - return null; + return TaskCategory.UTIL; } diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholds.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholds.java index d51d52b8997..03575e65b7a 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholds.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholds.java @@ -52,6 +52,7 @@ public abstract class TestThresholds extends AbstractStoryTest { private static final File LDIF_CREATE_BASE_USERS_FILE = new File(TEST_DIR, "users-base.ldif"); private static final File LDIF_CREATE_USERS_FILE = new File(TEST_DIR, "users.ldif"); + private static final File LDIF_CREATE_USERS_NEXT_FILE = new File(TEST_DIR, "users-next.ldif"); private static final File LDIF_CHANGE_ACTIVATION_FILE = new File(TEST_DIR, "users-activation.ldif"); @@ -85,15 +86,9 @@ public static void stopResources() throws Exception { protected abstract File getTaskFile(); protected abstract String getTaskOid(); protected abstract int getProcessedUsers(); - protected abstract void assertSynchronizationStatisticsAfterImport(Task syncInfo) throws Exception; - - - protected void assertSynchronizationStatisticsActivation(Task taskAfter) { - assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnmatched(), 5); - assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountDeleted(), 0); - assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountLinked(), getDefaultUsers()); - assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnlinked(), 0); - } + protected abstract void assertSynchronizationStatisticsAfterImport(Task taskAfter) throws Exception; + protected abstract void assertSynchronizationStatisticsAfterSecondImport(Task taskAfter) throws Exception; + protected abstract void assertSynchronizationStatisticsActivation(Task taskAfter); @Override @@ -186,7 +181,7 @@ public void test110importAccounts() throws Exception { assertUsers(getNumberOfUsers()); //WHEN displayWhen(TEST_NAME); - OperationResult reconResult = waitForTaskResume(getTaskOid(), false, 20000); + OperationResult reconResult = waitForTaskResume(getTaskOid(), false, 30000); assertFailure(reconResult); //THEN @@ -196,6 +191,32 @@ public void test110importAccounts() throws Exception { Task taskAfter = taskManager.getTaskWithResult(getTaskOid(), result); assertSynchronizationStatisticsAfterImport(taskAfter); + } + @Test + public void test111importAccountsAgain() throws Exception { + final String TEST_NAME = "test111importAccountsAgain"; + displayTestTitle(TEST_NAME); + + Task task = taskManager.createTaskInstance(TEST_NAME); + OperationResult result = task.getResult(); + + openDJController.addEntriesFromLdifFile(LDIF_CREATE_USERS_NEXT_FILE); + + + + assertUsers(getNumberOfUsers()+getProcessedUsers()); + //WHEN + displayWhen(TEST_NAME); + OperationResult reconResult = waitForTaskResume(getTaskOid(), false, 30000); + assertFailure(reconResult); + + //THEN + assertUsers(getProcessedUsers()*2 + getNumberOfUsers()); + assertTaskExecutionStatus(getTaskOid(), TaskExecutionStatus.SUSPENDED); + + Task taskAfter = taskManager.getTaskWithResult(getTaskOid(), result); + assertSynchronizationStatisticsAfterSecondImport(taskAfter); + } @Test @@ -237,7 +258,7 @@ public void test520changeActivationThreeAccounts() throws Exception { Task taskAfter = taskManager.getTaskWithResult(getTaskOid(), result); assertTaskExecutionStatus(getTaskOid(), TaskExecutionStatus.SUSPENDED); - assertUsers(getNumberOfUsers() + getProcessedUsers()); + assertUsers(getNumberOfUsers() + getProcessedUsers()*2); assertSynchronizationStatisticsActivation(taskAfter); diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncFull.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncFull.java index ce1d4d7013a..39a0c51471b 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncFull.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncFull.java @@ -78,4 +78,24 @@ protected void assertSynchronizationStatisticsActivation(Task taskAfter) { assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountLinked(), 0); assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnlinked(), 0); } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.testing.story.TestThresholds#assertSynchronizationStatisticsAfterSecondImport(com.evolveum.midpoint.task.api.Task) + */ + @Override + protected void assertSynchronizationStatisticsAfterSecondImport(Task taskAfter) throws Exception { + SynchronizationInformationType syncInfo = taskAfter.getStoredOperationStats().getSynchronizationInformation(); + + assertSyncToken(taskAfter, 12, taskAfter.getResult()); + + assertEquals(syncInfo.getCountUnmatched(), 5); + assertEquals(syncInfo.getCountDeleted(), 0); + assertEquals(syncInfo.getCountLinked(), 0); + assertEquals(syncInfo.getCountUnlinked(), 0); + + assertEquals(syncInfo.getCountUnmatchedAfter(), 0); + assertEquals(syncInfo.getCountDeletedAfter(), 0); + assertEquals(syncInfo.getCountLinkedAfter(), getProcessedUsers()); + assertEquals(syncInfo.getCountUnlinkedAfter(), 0); + } } diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncSimulate.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncSimulate.java index cff1e2deeab..7684b831bf5 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncSimulate.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsLiveSyncSimulate.java @@ -73,6 +73,21 @@ protected void assertSynchronizationStatisticsActivation(Task taskAfter) { assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountLinked(), 0); assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnlinked(), 0); } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.testing.story.TestThresholds#assertSynchronizationStatisticsAfterSecondImport(com.evolveum.midpoint.task.api.Task) + */ + @Override + protected void assertSynchronizationStatisticsAfterSecondImport(Task taskAfter) throws Exception { + SynchronizationInformationType syncInfo = taskAfter.getStoredOperationStats().getSynchronizationInformation(); + + assertSyncToken(taskAfter, 4, taskAfter.getResult()); + + assertEquals(syncInfo.getCountUnmatchedAfter(), 0); + assertEquals(syncInfo.getCountDeletedAfter(), 0); + assertEquals(syncInfo.getCountLinkedAfter(), 0); + assertEquals(syncInfo.getCountUnlinkedAfter(), 0); + } } diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconFull.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconFull.java index feda7236d0c..0b047353502 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconFull.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconFull.java @@ -71,4 +71,41 @@ protected void assertSynchronizationStatisticsAfterImport(Task taskAfter) throws assertEquals(syncInfo.getCountLinkedAfter(), getDefaultUsers() + getProcessedUsers()); assertEquals(syncInfo.getCountUnlinked(), 0); } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.testing.story.TestThresholds#assertSynchronizationStatisticsAfterSecondImport(com.evolveum.midpoint.task.api.Task) + */ + @Override + protected void assertSynchronizationStatisticsAfterSecondImport(Task taskAfter) throws Exception { + IterativeTaskInformationType infoType = taskAfter.getStoredOperationStats().getIterativeTaskInformation(); + assertEquals(infoType.getTotalFailureCount(), 1); + + SynchronizationInformationType syncInfo = taskAfter.getStoredOperationStats().getSynchronizationInformation(); + + assertEquals(syncInfo.getCountUnmatched(), 5); + assertEquals(syncInfo.getCountDeleted(), 0); + assertEquals(syncInfo.getCountLinked(), getDefaultUsers()+getProcessedUsers()); + assertEquals(syncInfo.getCountUnlinked(), 0); + + assertEquals(syncInfo.getCountUnmatchedAfter(), 0); + assertEquals(syncInfo.getCountDeleted(), 0); + assertEquals(syncInfo.getCountLinkedAfter(), getDefaultUsers() + getProcessedUsers()*2); + assertEquals(syncInfo.getCountUnlinked(), 0); + } + + protected void assertSynchronizationStatisticsActivation(Task taskAfter) { + IterativeTaskInformationType infoType = taskAfter.getStoredOperationStats().getIterativeTaskInformation(); + assertEquals(infoType.getTotalFailureCount(), 1); + + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnmatched(), 5); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountDeleted(), 0); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountLinked(), getDefaultUsers() + getProcessedUsers()); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnlinked(), 0); + + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnmatchedAfter(), 0); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountDeleted(), 0); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountLinked(), getDefaultUsers() + getProcessedUsers()); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnlinked(), 0); + } + } diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconSimulate.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconSimulate.java index 474c9b18c52..8c7c2bd7038 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconSimulate.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/TestThresholdsReconSimulate.java @@ -71,5 +71,42 @@ protected void assertSynchronizationStatisticsAfterImport(Task taskAfter) throws assertEquals(syncInfo.getCountLinkedAfter(), 0); assertEquals(syncInfo.getCountUnlinkedAfter(), 0); } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.testing.story.TestThresholds#assertSynchronizationStatisticsAfterSecondImport(com.evolveum.midpoint.task.api.Task) + */ + @Override + protected void assertSynchronizationStatisticsAfterSecondImport(Task taskAfter) throws Exception { + IterativeTaskInformationType infoType = taskAfter.getStoredOperationStats().getIterativeTaskInformation(); + assertEquals(infoType.getTotalFailureCount(), 1); + + SynchronizationInformationType syncInfo = taskAfter.getStoredOperationStats().getSynchronizationInformation(); + + assertEquals(syncInfo.getCountUnmatched(), 5); + assertEquals(syncInfo.getCountDeleted(), 0); + assertEquals(syncInfo.getCountLinked(), getDefaultUsers() + getProcessedUsers()); + assertEquals(syncInfo.getCountUnlinked(), 0); + + assertEquals(syncInfo.getCountUnmatchedAfter(), 0); + assertEquals(syncInfo.getCountDeletedAfter(), 0); + assertEquals(syncInfo.getCountLinkedAfter(), 0); + assertEquals(syncInfo.getCountUnlinkedAfter(), 0); + } + + @Override + protected void assertSynchronizationStatisticsActivation(Task taskAfter) { + IterativeTaskInformationType infoType = taskAfter.getStoredOperationStats().getIterativeTaskInformation(); + assertEquals(infoType.getTotalFailureCount(), 1); + + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnmatched(), 5); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountDeleted(), 0); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountLinked(), getDefaultUsers() + getProcessedUsers()); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnlinked(), 0); + + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnmatchedAfter(), 0); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountDeleted(), 0); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountLinked(), getDefaultUsers() + getProcessedUsers()); + assertEquals(taskAfter.getStoredOperationStats().getSynchronizationInformation().getCountUnlinked(), 0); + } } diff --git a/testing/story/src/test/resources/thresholds/users-next.ldif b/testing/story/src/test/resources/thresholds/users-next.ldif new file mode 100644 index 00000000000..6f7b70c4dc3 --- /dev/null +++ b/testing/story/src/test/resources/thresholds/users-next.ldif @@ -0,0 +1,72 @@ +dn: uid=user10,ou=People,dc=example,dc=com +uid: user10 +cn: User Tenth +sn: Tenth +givenname: User +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +l: Caribbean +mail: user.tenth@example.com + +dn: uid=user11,ou=People,dc=example,dc=com +uid: user11 +cn: User Eleventh +sn: Eleventh +givenname: User +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +l: Caribbean +mail: user.eleventh@example.com + +dn: uid=user12,ou=People,dc=example,dc=com +uid: user12 +cn: User Twelfth +sn: Twelfth +givenname: User +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +l: Caribbean +mail: user.twelfth@example.com + +dn: uid=user13,ou=People,dc=example,dc=com +uid: user13 +cn: User 13th +sn: 13th +givenname: User +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +l: Caribbean +mail: user.13th@example.com + +dn: uid=user14,ou=People,dc=example,dc=com +uid: user14 +cn: User 14th +sn: 14th +givenname: User +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +l: Caribbean +mail: user.14th@example.com + +dn: uid=user15,ou=People,dc=example,dc=com +uid: user15 +cn: User 15th +sn: 15th +givenname: User +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +l: Caribbean +mail: user.15th@example.com +