diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.html index d52358631ff..5d409d5e191 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.html @@ -19,9 +19,6 @@
- - - diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.java index 45ae01f80a5..5dca00f61ad 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/input/UploadDownloadPanel.java @@ -26,6 +26,7 @@ import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.form.upload.FileUpload; @@ -40,7 +41,6 @@ public class UploadDownloadPanel extends InputPanel { private static final Trace LOGGER = TraceManager.getTrace(UploadDownloadPanel.class); - private static final String ID_BUTTON_UPLOAD = "upload"; private static final String ID_BUTTON_DOWNLOAD = "download"; private static final String ID_BUTTON_DELETE = "remove"; private static final String ID_INPUT_FILE = "fileInput"; @@ -51,7 +51,27 @@ public UploadDownloadPanel(String id, boolean isReadOnly) { } private void initLayout(final boolean isReadOnly) { - FileUploadField fileUpload = new FileUploadField(ID_INPUT_FILE); + final FileUploadField fileUpload = new FileUploadField(ID_INPUT_FILE); + Form form = this.findParent(Form.class); + fileUpload.add(new AjaxFormSubmitBehavior(form, "change") + { + @Override + protected void onSubmit ( AjaxRequestTarget target ) + { + super.onSubmit(target); + Component input = getInputFile(); + try { + FileUpload uploadedFile = getFileUpload(); + updateValue(uploadedFile.getBytes()); + LOGGER.trace("Upload file success."); + input.success(getString("UploadPanel.message.uploadSuccess")); + } catch (Exception e) { + LOGGER.trace("Upload file error.", e); + input.error(getString("UploadPanel.message.uploadError") + " " + e.getMessage()); + } + } + } ); + fileUpload.setOutputMarkupId(true); add(fileUpload); final AjaxDownloadBehaviorFromStream downloadBehavior = new AjaxDownloadBehaviorFromStream() { @@ -63,27 +83,12 @@ protected InputStream initStream() { }; add(downloadBehavior); - add(new AjaxSubmitButton(ID_BUTTON_UPLOAD) { - - @Override - protected void onSubmit(AjaxRequestTarget target, Form form) { - uploadFilePerformed(target); - } - - @Override - protected void onError(AjaxRequestTarget target, Form form) { - uploadFileFailed(target); - } - }); - add(new AjaxSubmitButton(ID_BUTTON_DOWNLOAD) { @Override protected void onSubmit(AjaxRequestTarget target, Form form) { downloadPerformed(downloadBehavior, target); } - - }); add(new AjaxSubmitButton(ID_BUTTON_DELETE) { @@ -154,4 +159,8 @@ private void downloadPerformed(AjaxDownloadBehaviorFromStream downloadBehavior, AjaxRequestTarget target) { downloadBehavior.initiate(target); } + + private FileUploadField getInputFile(){ + return (FileUploadField)get(ID_INPUT_FILE); + } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/PrismValuePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/PrismValuePanel.java index 3dd2434a785..5628b9fa159 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/PrismValuePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/PrismValuePanel.java @@ -500,7 +500,8 @@ public Iterator getIterator(String input) { @Override public InputStream getStream() { - return new ByteArrayInputStream((byte[]) ((PrismPropertyValue) model.getObject().getValue()).getValue()); + Object object = ((PrismPropertyValue) model.getObject().getValue()).getValue(); + return object != null ? new ByteArrayInputStream((byte[]) object) : new ByteArrayInputStream(new byte[0]); // return super.getStream(); } diff --git a/icf-connectors/dummy-connector/src/main/java/com/evolveum/icf/dummy/connector/DummyConnector.java b/icf-connectors/dummy-connector/src/main/java/com/evolveum/icf/dummy/connector/DummyConnector.java index 710661a72d7..f07be18c76f 100644 --- a/icf-connectors/dummy-connector/src/main/java/com/evolveum/icf/dummy/connector/DummyConnector.java +++ b/icf-connectors/dummy-connector/src/main/java/com/evolveum/icf/dummy/connector/DummyConnector.java @@ -464,7 +464,6 @@ public Uid update(ObjectClass objectClass, Uid uid, Set replaceAttrib * {@inheritDoc} */ public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set valuesToAdd, OperationOptions options) { - log.info("addAttributeValues::begin"); validate(objectClass); try { @@ -501,6 +500,8 @@ public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set v String name = attr.getName(); try { account.addAttributeValues(name, attr.getValue()); + log.ok("Added attribute {0} values {1} from {2}, resulting values: {3}", + name, attr.getValue(), account, account.getAttributeValues(name, Object.class)); } catch (SchemaViolationException e) { // we cannot throw checked exceptions. But this one looks suitable. // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here @@ -544,6 +545,8 @@ public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set v } try { group.addAttributeValues(name, values); + log.ok("Added attribute {0} values {1} from {2}, resulting values: {3}", + name, attr.getValue(), group, group.getAttributeValues(name, Object.class)); } catch (SchemaViolationException e) { // we cannot throw checked exceptions. But this one looks suitable. // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here @@ -579,6 +582,8 @@ public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set v String name = attr.getName(); try { priv.addAttributeValues(name, attr.getValue()); + log.ok("Added attribute {0} values {1} from {2}, resulting values: {3}", + name, attr.getValue(), priv, priv.getAttributeValues(name, Object.class)); } catch (SchemaViolationException e) { // we cannot throw checked exceptions. But this one looks suitable. // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here @@ -600,7 +605,6 @@ public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set v throw new ConnectorIOException(e.getMessage(), e); } - log.info("addAttributeValues::end"); return uid; } @@ -608,7 +612,6 @@ public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set v * {@inheritDoc} */ public Uid removeAttributeValues(ObjectClass objectClass, Uid uid, Set valuesToRemove, OperationOptions options) { - log.info("removeAttributeValues::begin"); validate(objectClass); try { @@ -639,6 +642,8 @@ public Uid removeAttributeValues(ObjectClass objectClass, Uid uid, Set> getAttributeDefinitio Collection> defs = new ArrayList<>(); defs.addAll((Collection)structuralObjectClassDefinition.getAttributeDefinitions()); for(RefinedObjectClassDefinition auxiliaryObjectClassDefinition: auxiliaryObjectClassDefinitions) { - defs.addAll((Collection)auxiliaryObjectClassDefinition.getAttributeDefinitions()); + for (RefinedAttributeDefinition auxRAttrDef: auxiliaryObjectClassDefinition.getAttributeDefinitions()) { + boolean add = true; + for (RefinedAttributeDefinition def: defs) { + if (def.getName().equals(auxRAttrDef.getName())) { + add = false; + break; + } + } + if (add) { + ((Collection)defs).add(auxRAttrDef); + } + } } return defs; } diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinition.java b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinition.java index d4e40de36cd..180908da79c 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinition.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -399,6 +399,22 @@ public boolean isExlusiveStrong() { return refinedAttributeDefinition.isExlusiveStrong(); } + public boolean isSecondaryIdentifier() { + return refinedAttributeDefinition.isSecondaryIdentifier(); + } + + public boolean isInherited() { + return refinedAttributeDefinition.isInherited(); + } + + public boolean isVolatilityTrigger() { + return refinedAttributeDefinition.isVolatilityTrigger(); + } + + public boolean isDisplayNameAttribute() { + return refinedAttributeDefinition.isDisplayNameAttribute(); + } + public AttributeFetchStrategyType getFetchStrategy() { return refinedAttributeDefinition.getFetchStrategy(); } diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedAttributeDefinition.java b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedAttributeDefinition.java index a0f1843a741..2730681b488 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedAttributeDefinition.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/RefinedAttributeDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -64,6 +64,7 @@ public class RefinedAttributeDefinition extends ResourceAttributeDefinition attrDef, PrismContext prismContext) { super(attrDef.getName(), attrDef.getTypeName(), prismContext); @@ -320,17 +321,23 @@ public void setMatchingRuleQName(QName matchingRuleQName) { this.matchingRuleQName = matchingRuleQName; } - public List getTolerantValuePattern(){ return tolerantValuePattern; } public List getIntolerantValuePattern(){ return intolerantValuePattern; - } - // schemaHandlingAttrDefType may be null if we are parsing from schema only + public boolean isVolatilityTrigger() { + return isVolatilityTrigger; + } + + public void setVolatilityTrigger(boolean isVolatilityTrigger) { + this.isVolatilityTrigger = isVolatilityTrigger; + } + + // schemaHandlingAttrDefType may be null if we are parsing from schema only static RefinedAttributeDefinition parse(ResourceAttributeDefinition schemaAttrDef, ResourceAttributeDefinitionType schemaHandlingAttrDefType, ObjectClassComplexTypeDefinition objectClassDef, PrismContext prismContext, String contextDescription) throws SchemaException { @@ -388,6 +395,7 @@ static RefinedAttributeDefinition parse(ResourceAttributeDefinition sc rAttrDef.intolerantValuePattern = schemaHandlingAttrDefType.getIntolerantValuePattern(); rAttrDef.isExclusiveStrong = BooleanUtils.isTrue(schemaHandlingAttrDefType.isExclusiveStrong()); + rAttrDef.isVolatilityTrigger = BooleanUtils.isTrue(schemaHandlingAttrDefType.isVolatilityTrigger()); if (schemaHandlingAttrDefType.getOutbound() != null) { rAttrDef.setOutboundMappingType(schemaHandlingAttrDefType.getOutbound()); @@ -503,6 +511,7 @@ protected void copyDefinitionData(RefinedAttributeDefinition clone) { clone.inboundMappingTypes = this.inboundMappingTypes; clone.intolerantValuePattern = this.intolerantValuePattern; clone.isExclusiveStrong = this.isExclusiveStrong; + clone.isVolatilityTrigger = this.isVolatilityTrigger; clone.limitationsMap = this.limitationsMap; clone.matchingRuleQName = this.matchingRuleQName; clone.modificationPriority = this.modificationPriority; diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java index 931e9c3bea3..1d05d4e5687 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/delta/ItemDelta.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.PrettyPrinter; +import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; /** @@ -1481,6 +1482,40 @@ public int hashCode() { return result; } + /** + * Deltas are equivalent if they have the same result when + * applied to an object. I.e. meta-data and other "decorations" + * such as old values are not considered in this comparison. + */ + public boolean equivalent(ItemDelta other) { + if (elementName == null) { + if (other.elementName != null) + return false; + } else if (!QNameUtil.match(elementName, elementName)) + return false; + if (parentPath == null) { + if (other.parentPath != null) + return false; + } else if (!parentPath.equivalent(other.parentPath)) + return false; + if (!equivalentSetRealValue(this.valuesToAdd, other.valuesToAdd)) + return false; + if (!equivalentSetRealValue(this.valuesToDelete, other.valuesToDelete)) + return false; + if (!equivalentSetRealValue(this.valuesToReplace, other.valuesToReplace)) + return false; + return true; + } + + public static boolean hasEquivalent(Collection col, ItemDelta delta) { + for (ItemDelta colItem: col) { + if (colItem.equivalent(delta)) { + return true; + } + } + return false; + } + @Override public boolean equals(Object obj) { if (this == obj) @@ -1505,18 +1540,18 @@ public boolean equals(Object obj) { return false; } else if (!parentPath.equivalent(other.parentPath)) // or "equals" ? return false; - if (!equalsSetRealValue(this.valuesToAdd, other.valuesToAdd)) + if (!equivalentSetRealValue(this.valuesToAdd, other.valuesToAdd)) return false; - if (!equalsSetRealValue(this.valuesToDelete, other.valuesToDelete)) + if (!equivalentSetRealValue(this.valuesToDelete, other.valuesToDelete)) return false; - if (!equalsSetRealValue(this.valuesToReplace, other.valuesToReplace)) + if (!equivalentSetRealValue(this.valuesToReplace, other.valuesToReplace)) return false; - if (!equalsSetRealValue(this.estimatedOldValues, other.estimatedOldValues)) + if (!equivalentSetRealValue(this.estimatedOldValues, other.estimatedOldValues)) return false; return true; } - private boolean equalsSetRealValue(Collection thisValue, Collection otherValues) { + private boolean equivalentSetRealValue(Collection thisValue, Collection otherValues) { Comparator comparator = new Comparator() { @Override public int compare(Object o1, Object o2) { diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java index 55df343ded6..97b84ee789e 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/path/ItemPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.evolveum.midpoint.prism.path; import com.evolveum.midpoint.prism.PrismConstants; +import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; import org.apache.commons.lang.Validate; @@ -391,6 +392,14 @@ public boolean startsWith(ItemPath other) { } return other.isSubPathOrEquivalent(this); } + + public boolean startsWithName(QName name) { + if (!isEmpty() && startsWith(NameItemPathSegment.class)) { + return QNameUtil.match(name, ((NameItemPathSegment) first()).getName()); + } else { + return false; + } + } public QName asSingleName() { if (size() == 1 && startsWith(NameItemPathSegment.class)) { 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 f6827b3cc11..fe27afe4e0c 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 @@ -16,6 +16,7 @@ package com.evolveum.midpoint.schema.util; import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemPathSegment; import com.evolveum.midpoint.prism.path.NameItemPathSegment; @@ -594,4 +595,9 @@ public static ResourceObjectIdentification getResourceObjectIdentification( return new ResourceObjectIdentification(objectClassDefinition, ShadowUtil.getIdentifiers(shadow), ShadowUtil.getSecondaryIdentifiers(shadow)); } + + public static boolean matchesAttribute(ItemPath path, QName attributeName) { + return (ShadowType.F_ATTRIBUTES.equals(ItemPath.getFirstName(path)) && + QNameUtil.match(ItemPath.getFirstName(path.rest()), attributeName)); + } } 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 72fffe2c104..24b7e96e87a 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -3947,6 +3947,19 @@ + + + + If set to true it indicates that change of this attribute may cause + changes in other attributes. In that case midPoint re-reads the object + after the change of this attributes. + + + 3.4 + 3.3.1 + + + 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 84f83ee4b90..150db7e13e2 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 @@ -610,8 +610,8 @@ private void collectEntitlementToAttrDelta(ProvisioningContext ctx, Map PrismObject collectEntitlementsAsObjectOperation(ProvisioningContext ctx, Map roMap, - Collection> set, + private PrismObject collectEntitlementsAsObjectOperation(ProvisioningContext ctx, Map roMap, Collection> set, PrismObject subjectShadowBefore, PrismObject subjectShadowAfter, ModificationType modificationType, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ConfigurationException { @@ -625,8 +625,8 @@ private PrismObject collectEntitlementsAsObjectOperation(Provisi return subjectShadowAfter; } - private PrismObject collectEntitlementAsObjectOperation(ProvisioningContext subjectCtx, Map roMap, - PrismContainerValue associationCVal, + private PrismObject collectEntitlementAsObjectOperation(ProvisioningContext subjectCtx, Map roMap, PrismContainerValue associationCVal, PrismObject subjectShadowBefore, PrismObject subjectShadowAfter, ModificationType modificationType, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ConfigurationException { @@ -757,12 +757,18 @@ private PrismObject collectEntitlementAsObjectOperation(Prov } // TODO it seems that duplicate values are checked twice: once here and the second time in ResourceObjectConverter.executeModify // TODO check that and fix if necessary - attributeDelta = ProvisioningUtil.narrowPropertyDelta(attributeDelta, currentObjectShadow, assocDefType.getMatchingRule(), matchingRuleRegistry); + PropertyDelta attributeDeltaAfterNarrow = ProvisioningUtil.narrowPropertyDelta(attributeDelta, currentObjectShadow, assocDefType.getMatchingRule(), matchingRuleRegistry); + if (LOGGER.isTraceEnabled() && (attributeDeltaAfterNarrow == null || attributeDeltaAfterNarrow.isEmpty())) { + LOGGER.trace("Not collecting entitlement object operations ({}) association {}: attribute delta is empty after narrow, orig delta: {}", + modificationType, associationName.getLocalPart(), attributeDelta); + } + attributeDelta = attributeDeltaAfterNarrow; } if (attributeDelta != null && !attributeDelta.isEmpty()) { PropertyModificationOperation attributeModification = new PropertyModificationOperation(attributeDelta); attributeModification.setMatchingRuleQName(assocDefType.getMatchingRule()); + LOGGER.trace("Collecting entitlement object operations ({}) association {}: {}", modificationType, associationName.getLocalPart(), attributeModification); operations.getOperations().add(attributeModification); } 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 c1aff4eeb27..831a39d3635 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 @@ -736,17 +736,17 @@ public String modifyObject(Class type, String oid, } // getting object to modify - PrismObject object = getRepoObject(type, oid, null, result); + PrismObject repoShadow = getRepoObject(type, oid, null, result); if (LOGGER.isTraceEnabled()) { - LOGGER.trace("**PROVISIONING: modifyObject: object to modify:\n{}.", object.debugDump()); + LOGGER.trace("**PROVISIONING: modifyObject: object to modify (repository):\n{}.", repoShadow.debugDump()); } try { if (ShadowType.class.isAssignableFrom(type)) { // calling shadow cache to modify object - oid = getShadowCache(Mode.STANDARD).modifyShadow((PrismObject)object, oid, modifications, scripts, options, task, + oid = getShadowCache(Mode.STANDARD).modifyShadow((PrismObject)repoShadow, oid, modifications, scripts, options, task, result); } else { cacheRepositoryService.modifyObject(type, oid, modifications, result); 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 254853af7b3..f55f4753867 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 @@ -56,6 +56,7 @@ import com.evolveum.midpoint.prism.PrismPropertyValue; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.match.MatchingRule; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; @@ -63,6 +64,7 @@ import com.evolveum.midpoint.prism.query.EqualFilter; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.util.PrismUtil; import com.evolveum.midpoint.provisioning.api.GenericConnectorException; import com.evolveum.midpoint.provisioning.ucf.api.AttributesToReturn; import com.evolveum.midpoint.provisioning.ucf.api.Change; @@ -89,6 +91,8 @@ 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.DebugDumpable; +import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.Holder; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.PrettyPrinter; @@ -161,14 +165,18 @@ public class ResourceObjectConverter { public PrismObject getResourceObject(ProvisioningContext ctx, - Collection> identifiers, OperationResult parentResult) + Collection> identifiers, boolean fetchAssociations, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, GenericConnectorException { + LOGGER.trace("Getting resource object {}", identifiers); + AttributesToReturn attributesToReturn = ProvisioningUtil.createAttributesToReturn(ctx); PrismObject resourceShadow = fetchResourceObject(ctx, identifiers, - attributesToReturn, true, parentResult); // todo consider whether it is always necessary to fetch the entitlements + attributesToReturn, fetchAssociations, parentResult); // todo consider whether it is always necessary to fetch the entitlements + + LOGGER.trace("Got resource object {}", resourceShadow); return resourceShadow; @@ -180,7 +188,9 @@ public PrismObject getResourceObject(ProvisioningContext ctx, public PrismObject locateResourceObject(ProvisioningContext ctx, Collection> identifiers, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, GenericConnectorException { - ResourceType resource = ctx.getResource(); + + LOGGER.trace("Locating resource object {}", identifiers); + ConnectorInstance connector = ctx.getConnector(parentResult); AttributesToReturn attributesToReturn = ProvisioningUtil.createAttributesToReturn(ctx); @@ -239,7 +249,9 @@ public boolean handle(PrismObject shadow) { throw new ObjectNotFoundException("No object found for secondary identifier "+secondaryIdentifier); } PrismObject shadow = shadowHolder.getValue(); - return postProcessResourceObjectRead(ctx, shadow, true, parentResult); + PrismObject finalShadow = postProcessResourceObjectRead(ctx, shadow, true, parentResult); + LOGGER.trace("Located resource object {}", finalShadow); + return finalShadow; } catch (GenericFrameworkException e) { throw new GenericConnectorException(e.getMessage(), e); } @@ -272,6 +284,8 @@ public PrismObject addResourceObject(ProvisioningContext ctx, ObjectAlreadyExistsException, ConfigurationException, SecurityViolationException { ResourceType resource = ctx.getResource(); + LOGGER.trace("Adding resource object {}", shadow); + // We might be modifying the shadow (e.g. for simulated capabilities). But we do not want the changes // to propagate back to the calling code. Hence the clone. PrismObject shadowClone = shadow.clone(); @@ -339,6 +353,8 @@ public PrismObject addResourceObject(ProvisioningContext ctx, // Execute entitlement modification on other objects (if needed) executeEntitlementChangesAdd(ctx, shadowClone, scripts, parentResult); + + LOGGER.trace("Added resource object {}", shadow); parentResult.recordSuccess(); return shadow; @@ -348,8 +364,9 @@ public void deleteResourceObject(ProvisioningContext ctx, PrismObject> identifiers = ShadowUtil .getIdentifiers(shadow); @@ -407,19 +424,25 @@ public void deleteResourceObject(ProvisioningContext ctx, PrismObject modifyResourceObject( - ProvisioningContext ctx, PrismObject shadow, OperationProvisioningScriptsType scripts, + public Collection> modifyResourceObject( + ProvisioningContext ctx, PrismObject repoShadow, OperationProvisioningScriptsType scripts, Collection itemDeltas, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ObjectAlreadyExistsException { + + LOGGER.trace("Modifying resource object {}", repoShadow); + RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition(); Collection operations = new ArrayList(); - Collection> identifiers = ShadowUtil.getIdentifiers(shadow); + Collection> identifiers = ShadowUtil.getIdentifiers(repoShadow); + - if (isProtectedShadow(ctx.getObjectClassDefinition(), shadow)) { + if (isProtectedShadow(ctx.getObjectClassDefinition(), repoShadow)) { if (hasChangesOnResource(itemDeltas)) { LOGGER.error("Attempt to modify protected resource object " + objectClassDefinition + ": " + identifiers); @@ -448,41 +471,60 @@ public Collection modifyResourceObject( */ - collectAttributeAndEntitlementChanges(ctx, itemDeltas, operations, shadow, parentResult); + collectAttributeAndEntitlementChanges(ctx, itemDeltas, operations, repoShadow, parentResult); - Collection sideEffectChanges = null; - PrismObject shadowBefore = shadow.clone(); - if (operations.isEmpty()){ - // We have to check BEFORE we add script operations, otherwise the check would be pointless - LOGGER.trace("No modifications for connector object specified. Skipping processing of modifyShadow."); - } else { + PrismObject preReadShadow = null; + Collection sideEffectOperations = null; - // This must go after the skip check above. Otherwise the scripts would be executed even if there is no need to. - addExecuteScriptOperation(operations, ProvisioningOperationTypeType.MODIFY, scripts, ctx.getResource(), parentResult); - - //check identifier if it is not null - if (identifiers.isEmpty() && shadow.asObjectable().getFailedOperationType()!= null){ - throw new GenericConnectorException( - "Unable to modify object in the resource. Probably it has not been created yet because of previous unavailability of the resource."); + //check identifier if it is not null + if (identifiers.isEmpty() && repoShadow.asObjectable().getFailedOperationType()!= null){ + throw new GenericConnectorException( + "Unable to modify object in the resource. Probably it has not been created yet because of previous unavailability of the resource."); + } + + boolean hasVolatilityTriggerModification = false; + for (ItemDelta modification: itemDeltas) { + ItemPath path = modification.getPath(); + if (path.startsWithName(ShadowType.F_ATTRIBUTES)) { + QName attrName = ItemPath.getFirstName(path.rest()); + RefinedAttributeDefinition attrDef = ctx.getObjectClassDefinition().findAttributeDefinition(attrName); + if (attrDef.isVolatilityTrigger()) { + LOGGER.trace("Will pre-read and re-read object because volatility trigger attribute {} has changed", attrName); + hasVolatilityTriggerModification = true; + break; + } } - -// PrismObject currentShadow = null; - if (ResourceTypeUtil.isAvoidDuplicateValues(ctx.getResource()) || isRename(operations)) { - // We need to filter out the deltas that add duplicate values or remove values that are not there - LOGGER.trace("Fetching shadow for duplicate filtering and/or rename processing"); - shadow = getShadowToFilterDuplicates(ctx, identifiers, operations, true, parentResult); // yes, we need associations here - shadowBefore = shadow.clone(); + } + + if (hasVolatilityTriggerModification || ResourceTypeUtil.isAvoidDuplicateValues(ctx.getResource()) || isRename(operations)) { + // We need to filter out the deltas that add duplicate values or remove values that are not there + LOGGER.trace("Pre-reading resource shadow"); + preReadShadow = preReadShadow(ctx, identifiers, operations, true, parentResult); // yes, we need associations here + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Pre-read object:\n{}", preReadShadow.debugDump()); } + } + + if (!operations.isEmpty()) { + + // This must go after the skip check above. Otherwise the scripts would be executed even if there is no need to. + addExecuteScriptOperation(operations, ProvisioningOperationTypeType.MODIFY, scripts, ctx.getResource(), parentResult); // Execute primary ICF operation on this shadow - sideEffectChanges = executeModify(ctx, shadow, identifiers, operations, parentResult); + sideEffectOperations = executeModify(ctx, preReadShadow, identifiers, operations, parentResult); + + } else { + // We have to check BEFORE we add script operations, otherwise the check would be pointless + LOGGER.trace("No modifications for connector object specified. Skipping processing of subject executeModify."); } + Collection> sideEffectDeltas = convertToPropertyDelta(sideEffectOperations); + /* * State of the shadow after execution of the deltas - e.g. with new DN (if it was part of the delta), because this one should be recorded * in groups of which this account is a member of. (In case of object->subject associations.) */ - PrismObject shadowAfter = shadow.clone(); + PrismObject shadowAfter = preReadShadow == null ? repoShadow.clone() : preReadShadow.clone(); for (ItemDelta itemDelta : itemDeltas) { itemDelta.applyTo(shadowAfter); } @@ -490,14 +532,69 @@ public Collection modifyResourceObject( if (isRename(operations)){ Collection renameOperations = distillRenameDeltas(itemDeltas, shadowAfter, objectClassDefinition); LOGGER.trace("Determining rename operation {}", renameOperations); - sideEffectChanges.addAll(renameOperations); + sideEffectOperations.addAll(renameOperations); } + + PrismObject postReadShadow = null; + if (hasVolatilityTriggerModification) { + // There may be other changes that were not detected by the connector. Re-read the object and compare. + LOGGER.trace("Post-reading resource shadow"); + postReadShadow = preReadShadow(ctx, identifiers, operations, true, parentResult); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Post-read object:\n{}", postReadShadow.debugDump()); + } + ObjectDelta resourceShadowDelta = preReadShadow.diff(postReadShadow); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Determined side-effect changes by old-new diff:\n{}", resourceShadowDelta.debugDump()); + } + for (ItemDelta modification: resourceShadowDelta.getModifications()) { + if (modification.getParentPath().startsWithName(ShadowType.F_ATTRIBUTES) && !ItemDelta.hasEquivalent(itemDeltas, modification)) { + ItemDelta.merge(sideEffectDeltas, modification); + } + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Side-effect changes after merging with old-new diff:\n{}", DebugUtil.debugDump(sideEffectDeltas)); + } + } + Collection allDeltas = new ArrayList<>(); + ((Collection)allDeltas).addAll(itemDeltas); + ((Collection)allDeltas).addAll(sideEffectDeltas); + // Execute entitlement modification on other objects (if needed) - shadowAfter = executeEntitlementChangesModify(ctx, shadowBefore, shadowAfter, scripts, itemDeltas, parentResult); - + shadowAfter = executeEntitlementChangesModify(ctx, + preReadShadow == null ? repoShadow : preReadShadow, + postReadShadow == null ? shadowAfter : postReadShadow, + scripts, allDeltas, parentResult); + + if (!sideEffectDeltas.isEmpty()) { + if (preReadShadow != null) { + PrismUtil.setDeltaOldValue(preReadShadow, sideEffectDeltas); + } else { + PrismUtil.setDeltaOldValue(repoShadow, sideEffectDeltas); + } + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Modificaiton side-effect changes:\n{}", DebugUtil.debugDump(sideEffectDeltas)); + } + + LOGGER.trace("Modified resource object {}", repoShadow); + parentResult.recordSuccess(); - return sideEffectChanges; + return sideEffectDeltas; + } + + private Collection> convertToPropertyDelta( + Collection sideEffectOperations) { + Collection> sideEffectDeltas = new ArrayList>(); + if (sideEffectOperations != null) { + for (PropertyModificationOperation mod : sideEffectOperations){ + sideEffectDeltas.add(mod.getPropertyDelta()); + } + } + + return sideEffectDeltas; } private Collection executeModify(ProvisioningContext ctx, @@ -507,8 +604,10 @@ private Collection executeModify(ProvisioningCont RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition(); if (operations.isEmpty()){ - LOGGER.trace("No modifications for connector object. Skipping modification."); - // TODO [mederly] shouldn't "return new HashSet<>()" be here? + LOGGER.trace("No modifications for resource object. Skipping modification."); + return new ArrayList<>(); + } else { + LOGGER.trace("Resource object modification operations: {}", operations); } // Invoke ICF @@ -519,7 +618,7 @@ private Collection executeModify(ProvisioningCont if (currentShadow == null) { LOGGER.trace("Fetching shadow for duplicate filtering"); - currentShadow = getShadowToFilterDuplicates(ctx, identifiers, operations, false, parentResult); + currentShadow = preReadShadow(ctx, identifiers, operations, false, parentResult); } Collection filteredOperations = new ArrayList(operations.size()); @@ -532,13 +631,13 @@ private Collection executeModify(ProvisioningCont if (filteredDelta != null && !filteredDelta.isEmpty()) { if (propertyDelta == filteredDelta) { filteredOperations.add(origOperation); - } else if (filteredDelta == null || filteredDelta.isEmpty()) { - // nothing to do } else { PropertyModificationOperation newOp = new PropertyModificationOperation(filteredDelta); newOp.setMatchingRuleQName(modificationOperation.getMatchingRuleQName()); filteredOperations.add(newOp); } + } else { + LOGGER.trace("Filtering out modification {} because it has empty delta after narrow", propertyDelta); } } else if (origOperation instanceof ExecuteProvisioningScriptOperation){ filteredOperations.add(origOperation); @@ -555,7 +654,7 @@ private Collection executeModify(ProvisioningCont if (LOGGER.isDebugEnabled()) { LOGGER.debug( "PROVISIONING MODIFY operation on {}\n MODIFY object, object class {}, identified by:\n{}\n changes:\n{}", - new Object[] { ctx.getResource(), PrettyPrinter.prettyPrint(objectClassDefinition.getTypeName()), + new Object[] { ctx.getResource(), objectClassDefinition.getHumanReadableName(), SchemaDebugUtil.debugDump(identifiers,1), SchemaDebugUtil.debugDump(operations,1) }); } @@ -594,8 +693,7 @@ private Collection executeModify(ProvisioningCont } if (LOGGER.isDebugEnabled()) { - LOGGER.debug("PROVISIONING MODIFY successful, side-effect changes {}", - SchemaDebugUtil.debugDump(sideEffectChanges)); + LOGGER.debug("PROVISIONING MODIFY successful, side-effect changes {}", DebugUtil.debugDump(sideEffectChanges)); } } catch (ObjectNotFoundException ex) { @@ -628,7 +726,7 @@ private Collection executeModify(ProvisioningCont return sideEffectChanges; } - private PrismObject getShadowToFilterDuplicates(ProvisioningContext ctx, + private PrismObject preReadShadow(ProvisioningContext ctx, Collection> identifiers, Collection operations, boolean fetchEntitlements, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, SecurityViolationException, ConfigurationException { @@ -829,19 +927,6 @@ private boolean isRename(Collection modifications){ } return false; } - - private boolean isRename(ItemDelta itemDelta){ - - if (!(itemDelta instanceof PropertyDelta)){ - return false; - } - - if (itemDelta.getPath().equivalent(new ItemPath(ShadowType.F_ATTRIBUTES, ConnectorFactoryIcfImpl.ICFS_NAME))){ - return true; - } - return false; - } - private Collection distillRenameDeltas(Collection modifications, PrismObject shadow, RefinedObjectClassDefinition objectClassDefinition) throws SchemaException { @@ -892,21 +977,21 @@ private PrismObject executeEntitlementChangesAdd(ProvisioningContext private PrismObject executeEntitlementChangesModify(ProvisioningContext ctx, PrismObject subjectShadowBefore, PrismObject subjectShadowAfter, - OperationProvisioningScriptsType scripts, Collection objectDeltas, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ConfigurationException, ObjectAlreadyExistsException { + OperationProvisioningScriptsType scripts, Collection subjectDeltas, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ConfigurationException, ObjectAlreadyExistsException { Map roMap = new HashMap<>(); - for (ItemDelta itemDelta : objectDeltas) { - if (new ItemPath(ShadowType.F_ASSOCIATION).equivalent(itemDelta.getPath())) { - ContainerDelta containerDelta = (ContainerDelta)itemDelta; + for (ItemDelta subjectDelta : subjectDeltas) { + if (new ItemPath(ShadowType.F_ASSOCIATION).equivalent(subjectDelta.getPath())) { + ContainerDelta containerDelta = (ContainerDelta)subjectDelta; subjectShadowAfter = entitlementConverter.collectEntitlementsAsObjectOperation(ctx, roMap, containerDelta, subjectShadowBefore, subjectShadowAfter, parentResult); - } else if (isRename(itemDelta)) { + } else { ContainerDelta associationDelta = ContainerDelta.createDelta(ShadowType.F_ASSOCIATION, subjectShadowBefore.getDefinition()); - PrismContainer association = subjectShadowBefore.findContainer(ShadowType.F_ASSOCIATION); - if (association == null || association.isEmpty()){ + PrismContainer associationContainer = subjectShadowBefore.findContainer(ShadowType.F_ASSOCIATION); + if (associationContainer == null || associationContainer.isEmpty()){ LOGGER.trace("No shadow association container in old shadow. Skipping processing entitlements change."); continue; } @@ -914,20 +999,25 @@ private PrismObject executeEntitlementChangesModify(ProvisioningCont // Delete + re-add association values that should ensure correct functioning in case of rename // This has to be done only for associations that require explicit referential integrity. // For these that do not, it is harmful (), so it must be skipped. - for (PrismContainerValue associationValue : association.getValues()) { + for (PrismContainerValue associationValue : associationContainer.getValues()) { QName associationName = associationValue.asContainerable().getName(); if (associationName == null) { throw new IllegalStateException("No association name in " + associationValue); } - LOGGER.trace("Processing association {} on rename", associationName); RefinedAssociationDefinition associationDefinition = ctx.getObjectClassDefinition().findAssociation(associationName); if (associationDefinition == null) { throw new IllegalStateException("No association definition for " + associationValue); } - if (associationDefinition.requiresExplicitReferentialIntegrity()) { - associationDelta.addValuesToDelete(associationValue.clone()); - associationDelta.addValuesToAdd(associationValue.clone()); + if (!associationDefinition.requiresExplicitReferentialIntegrity()) { + continue; + } + QName valueAttributeName = associationDefinition.getResourceObjectAssociationType().getValueAttribute(); + if (!ShadowUtil.matchesAttribute(subjectDelta.getPath(), valueAttributeName)) { + continue; } + LOGGER.trace("Processing association {} on rename", associationName); + associationDelta.addValuesToDelete(associationValue.clone()); + associationDelta.addValuesToAdd(associationValue.clone()); } if (!associationDelta.isEmpty()) { entitlementConverter.collectEntitlementsAsObjectOperation(ctx, roMap, associationDelta, subjectShadowBefore, subjectShadowAfter, parentResult); @@ -992,6 +1082,9 @@ public SearchResultMetadata searchResourceObjects(final ProvisioningContext ctx, final ResultHandler resultHandler, ObjectQuery query, final boolean fetchAssociations, final OperationResult parentResult) throws SchemaException, CommunicationException, ObjectNotFoundException, ConfigurationException, SecurityViolationException { + + LOGGER.trace("Searching resource objects, query: {}", query); + RefinedObjectClassDefinition objectClassDef = ctx.getObjectClassDefinition(); AttributesToReturn attributesToReturn = ProvisioningUtil.createAttributesToReturn(ctx); SearchHierarchyConstraints searchHierarchyConstraints = null; @@ -1071,6 +1164,8 @@ public boolean handle(PrismObject shadow) { } } + LOGGER.trace("Searching resource objects done"); + parentResult.recordSuccess(); return metadata; } @@ -1080,6 +1175,8 @@ public PrismProperty fetchCurrentToken(ProvisioningContext ctx, OperationResult throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException { Validate.notNull(parentResult, "Operation result must not be null."); + LOGGER.trace("Fetcing current sync token for {}", ctx); + PrismProperty lastToken = null; ConnectorInstance connector = ctx.getConnector(parentResult); try { 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 65aed3e2b1c..1bb839078be 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 @@ -33,6 +33,7 @@ import com.evolveum.midpoint.common.InternalsConfig; import com.evolveum.midpoint.common.monitor.InternalMonitor; import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition; +import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.common.refinery.ShadowDiscriminatorObjectDelta; import com.evolveum.midpoint.prism.Containerable; @@ -262,7 +263,7 @@ public PrismObject getShadow(String oid, PrismObject rep // (we do not have raw/noFetch option) InternalMonitor.recordShadowFetchOperation(); - resourceShadow = resouceObjectConverter.getResourceObject(ctx, identifiers, parentResult); + resourceShadow = resouceObjectConverter.getResourceObject(ctx, identifiers, true, parentResult); resourceManager.modifyResourceAvailabilityStatus(resource.asPrismObject(), AvailabilityStatusType.UP, parentResult); @@ -393,21 +394,17 @@ public abstract void afterModifyOnResource(ProvisioningContext ctx, PrismObject< public abstract Collection beforeModifyOnResource(PrismObject shadow, ProvisioningOperationOptions options, Collection modifications) throws SchemaException; - public String modifyShadow(PrismObject shadow, String oid, + public String modifyShadow(PrismObject repoShadow, String oid, Collection modifications, OperationProvisioningScriptsType scripts, ProvisioningOperationOptions options, Task task, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, ObjectNotFoundException, SchemaException, ConfigurationException, SecurityViolationException { - Validate.notNull(shadow, "Object to modify must not be null."); + Validate.notNull(repoShadow, "Object to modify must not be null."); Validate.notNull(oid, "OID must not be null."); Validate.notNull(modifications, "Object modification must not be null."); InternalMonitor.recordShadowChangeOperation(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Modifying resource shadow:\n{}", shadow.debugDump()); - } - Collection additionalAuxiliaryObjectClassQNames = new ArrayList<>(); ItemPath auxPath = new ItemPath(ShadowType.F_AUXILIARY_OBJECT_CLASS); for (ItemDelta modification: modifications) { @@ -419,30 +416,32 @@ public String modifyShadow(PrismObject shadow, String oid, } } - ProvisioningContext ctx = ctxFactory.create(shadow, additionalAuxiliaryObjectClassQNames, task, parentResult); - Collection sideEffectChanges; + ProvisioningContext ctx = ctxFactory.create(repoShadow, additionalAuxiliaryObjectClassQNames, task, parentResult); + + Collection> sideEffectChanges; try { ctx.assertDefinition(); + RefinedObjectClassDefinition rOCDef = ctx.getObjectClassDefinition(); - applyAttributesDefinition(ctx, shadow); + applyAttributesDefinition(ctx, repoShadow); - accessChecker.checkModify(ctx.getResource(), shadow, modifications, ctx.getObjectClassDefinition(), parentResult); + accessChecker.checkModify(ctx.getResource(), repoShadow, modifications, ctx.getObjectClassDefinition(), parentResult); preprocessEntitlements(ctx, modifications, "delta for shadow "+oid, parentResult); - modifications = beforeModifyOnResource(shadow, options, modifications); + modifications = beforeModifyOnResource(repoShadow, options, modifications); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Applying change: {}", DebugUtil.debugDump(modifications)); } - sideEffectChanges = resouceObjectConverter.modifyResourceObject(ctx, shadow, scripts, modifications, + sideEffectChanges = resouceObjectConverter.modifyResourceObject(ctx, repoShadow, scripts, modifications, parentResult); } catch (Exception ex) { LOGGER.debug("Provisioning exception: {}:{}, attempting to handle it", new Object[] { ex.getClass(), ex.getMessage(), ex }); try { - shadow = handleError(ctx, ex, shadow, FailedOperation.MODIFY, modifications, + repoShadow = handleError(ctx, ex, repoShadow, FailedOperation.MODIFY, modifications, ProvisioningOperationOptions.isCompletePostponed(options), parentResult); parentResult.computeStatus(); } catch (ObjectAlreadyExistsException e) { @@ -452,38 +451,23 @@ public String modifyShadow(PrismObject shadow, String oid, throw new SystemException(e); } - return shadow.getOid(); + return repoShadow.getOid(); } - - Collection> sideEffectDeltas = convertToPropertyDelta(sideEffectChanges); - if (!sideEffectDeltas.isEmpty()) { - PrismUtil.setDeltaOldValue(shadow, sideEffectDeltas); - ItemDelta.addAll(modifications, (Collection) sideEffectDeltas); + + if (sideEffectChanges != null) { + ItemDelta.addAll(modifications, sideEffectChanges); } - afterModifyOnResource(ctx, shadow, modifications, parentResult); - - ObjectDelta delta = ObjectDelta.createModifyDelta(shadow.getOid(), modifications, shadow.getCompileTimeClass(), prismContext); - ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, shadow, + afterModifyOnResource(ctx, repoShadow, modifications, parentResult); + + ObjectDelta delta = ObjectDelta.createModifyDelta(repoShadow.getOid(), modifications, repoShadow.getCompileTimeClass(), prismContext); + ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, repoShadow, delta, parentResult); operationListener.notifySuccess(operationDescription, task, parentResult); parentResult.recordSuccess(); return oid; } - - private Collection> convertToPropertyDelta( - Collection sideEffectChanges) { - Collection> sideEffectDelta = new ArrayList>(); - if (sideEffectChanges != null) { - for (PropertyModificationOperation mod : sideEffectChanges){ - sideEffectDelta.add(mod.getPropertyDelta()); - } - } - - return sideEffectDelta; - } - public void deleteShadow(PrismObject shadow, ProvisioningOperationOptions options, OperationProvisioningScriptsType scripts, Task task, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, ObjectNotFoundException, SchemaException, ConfigurationException, diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCacheProvisioner.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCacheProvisioner.java index f9dc2021810..a73eb2038e6 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCacheProvisioner.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCacheProvisioner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,7 +113,6 @@ private Collection extractRepoShadowChanges(ProvisioningCon throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException { RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition(); - Collection shadowChanges = new ArrayList(); for (ItemDelta itemDelta : objectChange) { if (new ItemPath(ShadowType.F_ATTRIBUTES).equivalent(itemDelta.getParentPath())) { 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 38ef4fcbdda..c2418e4e65b 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 @@ -68,6 +68,7 @@ import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.schema.util.SchemaDebugUtil; import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; @@ -768,6 +769,44 @@ public PrismObject createRepositoryShadow(ProvisioningContext ctx, P return repoShadow; } + + public Collection updateShadow(ProvisioningContext ctx, PrismObject resourceShadow, + Collection aprioriDeltas, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException { + PrismObject repoShadow = repositoryService.getObject(ShadowType.class, resourceShadow.getOid(), null, result); + RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition(); + Collection repoShadowChanges = new ArrayList(); + for (RefinedAttributeDefinition attrDef: objectClassDefinition.getAttributeDefinitions()) { + if (ProvisioningUtil.shouldStoreAtributeInShadow(objectClassDefinition, attrDef.getName())) { + ResourceAttribute resourceAttr = ShadowUtil.getAttribute(resourceShadow, attrDef.getName()); + PrismProperty repoAttr = repoShadow.findProperty(new ItemPath(ShadowType.F_ATTRIBUTES, attrDef.getName())); + PropertyDelta attrDelta; + if (repoAttr == null && repoAttr == null) { + continue; + } + if (repoAttr == null) { + attrDelta = attrDef.createEmptyDelta(new ItemPath(ShadowType.F_ATTRIBUTES, attrDef.getName())); + attrDelta.setValuesToReplace(PrismValue.cloneCollection(resourceAttr.getValues())); + } else { + attrDelta = repoAttr.diff(resourceAttr); + } + if (attrDelta != null && !attrDelta.isEmpty()) { + normalizeDelta(attrDelta, attrDef); + repoShadowChanges.add(attrDelta); + } + } + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Updating repo shadow {}:\n{}", resourceShadow.getOid(), DebugUtil.debugDump(repoShadowChanges)); + } + try { + repositoryService.modifyObject(ShadowType.class, resourceShadow.getOid(), repoShadowChanges, result); + } catch (ObjectAlreadyExistsException e) { + // We are not renaming the object here. This should not happen. + throw new SystemException(e.getMessage(), e); + } + return repoShadowChanges; + } + public void setKindIfNecessary(ShadowType repoShadowType, RefinedObjectClassDefinition objectClassDefinition) { if (repoShadowType.getKind() == null && objectClassDefinition != null) { @@ -879,6 +918,6 @@ public boolean compareAttribute(RefinedObjectClassDefinition refinedObjectCl refinedAttributeDefinition = refinedObjectClassDefinition.findAttributeDefinition(attributeA.getElementName()); Collection valuesB = getNormalizedAttributeValues(attributeB, refinedAttributeDefinition); return MiscUtil.unorderedCollectionEquals(valuesA, valuesB); - } + } } diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/AbstractDummyTest.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/AbstractDummyTest.java index 60857010e54..fa23366df2f 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/AbstractDummyTest.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/AbstractDummyTest.java @@ -252,6 +252,10 @@ protected String transformNameFromResource(String origName) { return origName; } + protected String transformNameToResource(String origName) { + return origName; + } + protected void assertShadowName(PrismObject shadow, String expectedName) { PrismAsserts.assertEqualsPolyString("Shadow name is wrong in "+shadow, expectedName, shadow.asObjectable().getName()); } diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummy.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummy.java index 6eee020e5a9..9400e5bba8d 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummy.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummy.java @@ -3217,7 +3217,7 @@ public void test220EntitleAccountWillPirates() throws Exception { } DummyGroup group = getDummyGroupAssert(GROUP_PIRATES_NAME, piratesIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(group, transformNameToResource(ACCOUNT_WILL_USERNAME)); syncServiceMock.assertNotifySuccessOnly(); assertDummyResourceGroupMembersReadCountIncrement(null, 0); @@ -3254,7 +3254,7 @@ public void test221GetPirateWill() throws Exception { // Just make sure nothing has changed DummyGroup group = getDummyGroupAssert(GROUP_PIRATES_NAME, piratesIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(group, transformNameToResource(ACCOUNT_WILL_USERNAME)); assertDummyResourceGroupMembersReadCountIncrement(null, 0); assertSteadyResource(); @@ -3305,7 +3305,7 @@ public void test222EntitleAccountWillPillage() throws Exception { // Make sure that the groups is still there and will is a member DummyGroup group = getDummyGroupAssert(GROUP_PIRATES_NAME, piratesIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(group, transformNameToResource(ACCOUNT_WILL_USERNAME)); syncServiceMock.assertNotifySuccessOnly(); assertDummyResourceGroupMembersReadCountIncrement(null, 0); @@ -3359,7 +3359,7 @@ public void test223EntitleAccountWillBargain() throws Exception { // Make sure that the groups is still there and will is a member DummyGroup group = getDummyGroupAssert(GROUP_PIRATES_NAME, piratesIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(group, transformNameToResource(ACCOUNT_WILL_USERNAME)); syncServiceMock.assertNotifySuccessOnly(); @@ -3412,7 +3412,7 @@ public void test224GetPillagingPirateWill() throws Exception { assertNotNull("Privilege object (bargain) is gone!", priv2); DummyGroup group = getDummyGroupAssert(GROUP_PIRATES_NAME, piratesIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(group, transformNameToResource(ACCOUNT_WILL_USERNAME)); assertDummyResourceGroupMembersReadCountIncrement(null, 0); assertSteadyResource(); @@ -3481,11 +3481,11 @@ public void test225GetFoolishPirateWill() throws Exception { assertDummyResourceGroupMembersReadCountIncrement(null, 0); DummyGroup group = getDummyGroupAssert(GROUP_PIRATES_NAME, piratesIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(group, transformNameToResource(ACCOUNT_WILL_USERNAME)); String foolsIcfUid = getIcfUid(foolsShadow); groupFools = getDummyGroupAssert("fools", foolsIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(groupFools, transformNameToResource(ACCOUNT_WILL_USERNAME)); assertDummyResourceGroupMembersReadCountIncrement(null, 0); assertSteadyResource(); @@ -3550,11 +3550,11 @@ public void test226WillNonsensePrivilege() throws Exception { assertDummyResourceGroupMembersReadCountIncrement(null, 0); DummyGroup group = getDummyGroupAssert(GROUP_PIRATES_NAME, piratesIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(group, transformNameToResource(ACCOUNT_WILL_USERNAME)); String foolsIcfUid = getIcfUid(foolsShadow); DummyGroup groupFools = getDummyGroupAssert("fools", foolsIcfUid); - assertMember(group, transformNameFromResource(getWillRepoIcfName())); + assertMember(groupFools, transformNameToResource(ACCOUNT_WILL_USERNAME)); assertDummyResourceGroupMembersReadCountIncrement(null, 0); assertSteadyResource(); diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummyCaseIgnoreUpcaseName.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummyCaseIgnoreUpcaseName.java index 803402fec74..329bd124409 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummyCaseIgnoreUpcaseName.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/test/impl/TestDummyCaseIgnoreUpcaseName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2014 Evolveum + * Copyright (c) 2010-2016 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,6 +71,11 @@ protected String transformNameFromResource(String origName) { return origName.toUpperCase(); } + @Override + protected String transformNameToResource(String origName) { + return origName.toUpperCase(); + } + @Override protected void assertShadowName(PrismObject shadow, String expectedName) { assertEquals("Shadow name is wrong in "+shadow, expectedName.toLowerCase(), shadow.asObjectable().getName().getOrig().toLowerCase()); diff --git a/testing/story/src/test/resources/unix/resource-opendj.xml b/testing/story/src/test/resources/unix/resource-opendj.xml index 70a9377967c..d2b0762a57d 100644 --- a/testing/story/src/test/resources/unix/resource-opendj.xml +++ b/testing/story/src/test/resources/unix/resource-opendj.xml @@ -87,9 +87,10 @@ ri:dn Distinguished Name + true mr:stringIgnoreCase - + $user/name