From 739c93db806cdd55af6c177e613ffba9212e5d8e Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Tue, 17 Mar 2020 06:32:01 +0100 Subject: [PATCH] removing model-impl dependecy from report dependencies. required moving of some classes and small api changes. --- .../midpoint/schema/util/MiscSchemaUtil.java | 1074 ++--- .../impl/AccCertExpressionHelper.java | 250 +- .../impl/AccCertReviewersHelper.java | 574 +-- .../model/api/ModelPublicConstants.java | 4 + .../api/context/EvaluatedAssignment.java | 215 +- .../midpoint/model/api/context/Mapping.java | 59 +- .../model/api/context/ModelContext.java | 192 +- .../api/context/ModelElementContext.java | 146 +- .../api/context/ModelProjectionContext.java | 100 +- .../api/context}/SynchronizationIntent.java | 173 +- .../expression}/ExpressionEnvironment.java | 204 +- .../ModelExpressionThreadLocalHolder.java | 306 +- .../model/impl/ClusterRestService.java | 706 ++- .../impl/controller/MappingDiagEvaluator.java | 276 +- .../model/impl/expr/ExpressionHandler.java | 357 +- .../impl/expr/MidpointFunctionsImpl.java | 4044 ++++++++--------- .../SequentialValueExpressionEvaluator.java | 174 +- .../model/impl/lens/AssignmentEvaluator.java | 3092 ++++++------- .../model/impl/lens/ChangeExecutor.java | 5 +- .../impl/lens/EvaluatedAssignmentImpl.java | 1192 +++-- .../impl/lens/LensContextPlaceholder.java | 97 +- .../model/impl/lens/LensElementContext.java | 1709 ++++--- .../impl/lens/LensProjectionContext.java | 3009 ++++++------ .../midpoint/model/impl/lens/LensUtil.java | 4 +- .../lens/projector/ActivationProcessor.java | 1887 ++++---- .../impl/lens/projector/ContextLoader.java | 2 +- .../focus/FocusLifecycleProcessor.java | 386 +- .../projector/mappings/MappingEvaluator.java | 1444 +++--- .../model/impl/scripting/VariablesUtil.java | 562 +-- .../impl/security/GuiProfileCompiler.java | 1116 ++--- .../impl/sync/SynchronizationContext.java | 1002 ++-- .../SynchronizationExpressionsEvaluator.java | 840 ++-- .../impl/sync/SynchronizationServiceImpl.java | 2442 +++++----- .../sync/SynchronizationServiceUtils.java | 218 +- .../impl/sync/action/DeleteShadowAction.java | 76 +- .../model/impl/sync/action/UnlinkAction.java | 74 +- ...stractSearchIterativeModelTaskHandler.java | 318 +- .../model/impl/util/ModelImplUtils.java | 1676 ++++--- .../model/impl/expr/TestModelExpressions.java | 668 +-- .../model/intest/orgstruct/TestOrgStruct.java | 3456 +++++++------- .../impl/api/transports/CustomTransport.java | 424 +- .../api/transports/SimpleSmsTransport.java | 831 ++-- .../helpers/NotificationExpressionHelper.java | 370 +- .../impl/notifiers/ConfirmationNotifier.java | 166 +- .../impl/notifiers/CustomNotifier.java | 300 +- .../midpoint/report/api/ReportService.java | 118 +- model/report-impl/pom.xml | 623 ++- .../impl/MidPointLocalQueryExecutor.java | 300 +- .../report/impl/MidPointQueryExecutor.java | 408 +- .../midpoint/report/impl/ReportFunctions.java | 1112 +++-- .../impl/ReportHTMLCreateTaskHandler.java | 2133 ++++----- .../report/impl/ReportManagerImpl.java | 970 ++-- .../report/impl/ReportServiceImpl.java | 907 ++-- .../report/impl/ReportWebService.java | 426 +- .../common/ExpressionEvaluationHelper.java | 244 +- .../general/GcpExpressionHelper.java | 190 +- .../aspect/BasePrimaryChangeAspect.java | 411 +- .../aspect/PrimaryChangeAspectHelper.java | 298 +- 58 files changed, 22175 insertions(+), 22185 deletions(-) rename model/{model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens => model-api/src/main/java/com/evolveum/midpoint/model/api/context}/SynchronizationIntent.java (94%) rename model/{model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr => model-common/src/main/java/com/evolveum/midpoint/model/common/expression}/ExpressionEnvironment.java (74%) rename model/{model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr => model-common/src/main/java/com/evolveum/midpoint/model/common/expression}/ModelExpressionThreadLocalHolder.java (93%) 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 55f2152d672..b01de88b531 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 @@ -1,513 +1,561 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.schema.util; - -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.xml.XmlTypeConverter; -import com.evolveum.midpoint.schema.*; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.QNameUtil; -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.api_types_3.ImportOptionsType; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectListType; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PropertyReferenceListType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; -import org.jetbrains.annotations.NotNull; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; -import java.util.*; - -/** - * @author Radovan Semancik - * - */ -public class MiscSchemaUtil { - - private static final Trace LOGGER = TraceManager.getTrace(MiscSchemaUtil.class); - private static final Random RND = new Random(); - - public static ObjectListType toObjectListType(List> list) { - ObjectListType listType = new ObjectListType(); - for (PrismObject o : list) { - listType.getObject().add(o.asObjectable()); - } - return listType; - } - - public static List> toList(Class type, ObjectListType listType) { - List> list = new ArrayList<>(); - for (ObjectType o : listType.getObject()) { - list.add((PrismObject) o.asPrismObject()); - } - return list; - } - - public static List toObjectableList(List> objectList) { - if (objectList == null) { - return null; - } - List objectableList = new ArrayList<>(objectList.size()); - for (PrismObject object: objectList) { - objectableList.add(object.asObjectable()); - } - return objectableList; - } - - public static ImportOptionsType getDefaultImportOptions() { - ImportOptionsType options = new ImportOptionsType(); - options.setOverwrite(false); - options.setValidateStaticSchema(false); - options.setValidateDynamicSchema(false); - options.setEncryptProtectedValues(true); - options.setFetchResourceSchema(false); - options.setSummarizeErrors(true); - options.setSummarizeSucceses(true); - return options; - } - - public static CachingMetadataType generateCachingMetadata() { - CachingMetadataType cmd = new CachingMetadataType(); - XMLGregorianCalendar xmlGregorianCalendarNow = XmlTypeConverter.createXMLGregorianCalendar(System.currentTimeMillis()); - cmd.setRetrievalTimestamp(xmlGregorianCalendarNow); - cmd.setSerialNumber(generateSerialNumber()); - return cmd; - } - - private static String generateSerialNumber() { - return Long.toHexString(RND.nextLong())+"-"+Long.toHexString(RND.nextLong()); - } - - public static boolean isNullOrEmpty(ProtectedStringType ps) { - return (ps == null || ps.isEmpty()); - } - - public static void setPassword(CredentialsType credentials, ProtectedStringType password) { - PasswordType credPass = credentials.getPassword(); - if (credPass == null) { - credPass = new PasswordType(); - credentials.setPassword(credPass); - } - credPass.setValue(password); - } - - public static Collection toCollection(String entry) { - List list = new ArrayList<>(1); - list.add(entry); - return list; - } - - public static Collection itemReferenceListTypeToItemPathList(PropertyReferenceListType resolve, PrismContext prismContext) { - Collection itemPathList = new ArrayList<>(resolve.getProperty().size()); - for (ItemPathType itemXPathElement: resolve.getProperty()) { - itemPathList.add(prismContext.toPath(itemXPathElement)); - } - return itemPathList; - } - - public static SelectorQualifiedGetOptionsType optionsToOptionsType(Collection> options){ - SelectorQualifiedGetOptionsType optionsType = new SelectorQualifiedGetOptionsType(); - List retval = new ArrayList<>(); - for (SelectorOptions option: options){ - retval.add(selectorOptionToSelectorQualifiedGetOptionType(option)); - } - optionsType.getOption().addAll(retval); - return optionsType; - } - - private static SelectorQualifiedGetOptionType selectorOptionToSelectorQualifiedGetOptionType(SelectorOptions selectorOption) { - OptionObjectSelectorType selectorType = selectorToSelectorType(selectorOption.getSelector()); - GetOperationOptionsType getOptionsType = getOptionsToGetOptionsType(selectorOption.getOptions()); - SelectorQualifiedGetOptionType selectorOptionType = new SelectorQualifiedGetOptionType(); - selectorOptionType.setOptions(getOptionsType); - selectorOptionType.setSelector(selectorType); - return selectorOptionType; - } - - private static OptionObjectSelectorType selectorToSelectorType(ObjectSelector selector) { - if (selector == null) { - return null; - } - OptionObjectSelectorType selectorType = new OptionObjectSelectorType(); - selectorType.setPath(new ItemPathType(selector.getPath())); - return selectorType; - } - - private static GetOperationOptionsType getOptionsToGetOptionsType(GetOperationOptions options) { - GetOperationOptionsType optionsType = new GetOperationOptionsType(); - optionsType.setRetrieve(RetrieveOption.toRetrieveOptionType(options.getRetrieve())); - optionsType.setResolve(options.getResolve()); - optionsType.setResolveNames(options.getResolveNames()); - optionsType.setNoFetch(options.getNoFetch()); - optionsType.setRaw(options.getRaw()); - optionsType.setTolerateRawData(options.getTolerateRawData()); - optionsType.setNoDiscovery(options.getDoNotDiscovery()); - // TODO relational value search query (but it might become obsolete) - optionsType.setAllowNotFound(options.getAllowNotFound()); - optionsType.setPointInTimeType(PointInTimeType.toPointInTimeTypeType(options.getPointInTimeType())); - optionsType.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOptionType(options.getDefinitionProcessing())); - optionsType.setStaleness(options.getStaleness()); - optionsType.setDistinct(options.getDistinct()); - return optionsType; - } - - public static List> optionsTypeToOptions( - SelectorQualifiedGetOptionsType objectOptionsType, PrismContext prismContext) { - if (objectOptionsType == null) { - return null; - } - List> retval = new ArrayList<>(); - for (SelectorQualifiedGetOptionType optionType : objectOptionsType.getOption()) { - retval.add(selectorQualifiedGetOptionTypeToSelectorOption(optionType, prismContext)); - } - return retval; - } - - private static SelectorOptions selectorQualifiedGetOptionTypeToSelectorOption( - SelectorQualifiedGetOptionType objectOptionsType, PrismContext prismContext) { - ObjectSelector selector = selectorTypeToSelector(objectOptionsType.getSelector(), prismContext); - GetOperationOptions options = getOptionsTypeToGetOptions(objectOptionsType.getOptions()); - return new SelectorOptions<>(selector, options); - } - - private static GetOperationOptions getOptionsTypeToGetOptions(GetOperationOptionsType optionsType) { - GetOperationOptions options = new GetOperationOptions(); - options.setRetrieve(RetrieveOption.fromRetrieveOptionType(optionsType.getRetrieve())); - options.setResolve(optionsType.isResolve()); - options.setResolveNames(optionsType.isResolveNames()); - options.setNoFetch(optionsType.isNoFetch()); - options.setRaw(optionsType.isRaw()); - options.setTolerateRawData(optionsType.isTolerateRawData()); - options.setDoNotDiscovery(optionsType.isNoDiscovery()); - // TODO relational value search query (but it might become obsolete) - options.setAllowNotFound(optionsType.isAllowNotFound()); - options.setPointInTimeType(PointInTimeType.toPointInTimeType(optionsType.getPointInTimeType())); - options.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOption(optionsType.getDefinitionProcessing())); - options.setStaleness(optionsType.getStaleness()); - options.setDistinct(optionsType.isDistinct()); - return options; - } - - private static ObjectSelector selectorTypeToSelector(OptionObjectSelectorType selectorType, - PrismContext prismContext) { - if (selectorType == null) { - return null; - } - return new ObjectSelector(prismContext.toUniformPath(selectorType.getPath())); - } - - /** - * Convenience method that helps avoid some compiler warnings. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static Collection> createCollection(ObjectDelta... deltas) { - return (Collection)MiscUtil.createCollection(deltas); - } - - /** - * Convenience method that helps avoid some compiler warnings. - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static Collection> createCollection(ItemDelta... deltas) { - return MiscUtil.createCollection(deltas); - } - - public static Collection> cloneObjectDeltaCollection( - Collection> origCollection) { - if (origCollection == null) { - return null; - } - Collection> clonedCollection = new ArrayList<>(origCollection.size()); - for (ObjectDelta origDelta: origCollection) { - clonedCollection.add(origDelta.clone()); - } - return clonedCollection; - } - - public static Collection> cloneObjectDeltaOperationCollection( - Collection> origCollection) { - if (origCollection == null) { - return null; - } - Collection> clonedCollection = new ArrayList<>(origCollection.size()); - for (ObjectDeltaOperation origDelta: origCollection) { - clonedCollection.add(origDelta.clone()); - } - return clonedCollection; - } - - public static ObjectReferenceType createObjectReference(String oid, QName type) { - ObjectReferenceType ref = new ObjectReferenceType(); - ref.setOid(oid); - ref.setType(type); - return ref; - } - - public static ObjectReferenceType createObjectReference(PrismObject object, Class implicitReferenceTargetType) { - ObjectReferenceType ref = new ObjectReferenceType(); - ref.setOid(object.getOid()); - if (implicitReferenceTargetType == null || !implicitReferenceTargetType.equals(object.getCompileTimeClass())) { - ref.setType(ObjectTypes.getObjectType(object.getCompileTimeClass()).getTypeQName()); - } - ref.setTargetName(PolyString.toPolyStringType(object.getName())); - return ref; - } - - - public static boolean equalsIntent(String intent1, String intent2) { - if (intent1 == null) { - intent1 = SchemaConstants.INTENT_DEFAULT; - } - if (intent2 == null) { - intent2 = SchemaConstants.INTENT_DEFAULT; - } - return intent1.equals(intent2); - } - - public static boolean matchesKind(ShadowKindType expectedKind, ShadowKindType actualKind) { - if (expectedKind == null) { - return true; - } - return expectedKind.equals(actualKind); - } - - - public static AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType( - ProjectionPolicyType accountSynchronizationSettings) { - if (accountSynchronizationSettings == null) { - // default - return AssignmentPolicyEnforcementType.RELATIVE; - } - AssignmentPolicyEnforcementType assignmentPolicyEnforcement = accountSynchronizationSettings.getAssignmentPolicyEnforcement(); - if (assignmentPolicyEnforcement == null) { - return AssignmentPolicyEnforcementType.RELATIVE; - } - return assignmentPolicyEnforcement; - } - - public static PrismReferenceValue objectReferenceTypeToReferenceValue(ObjectReferenceType refType, - PrismContext prismContext) { - if (refType == null) { - return null; - } - PrismReferenceValue rval = prismContext.itemFactory().createReferenceValue(); - rval.setOid(refType.getOid()); - rval.setDescription(refType.getDescription()); - rval.setFilter(refType.getFilter()); - rval.setRelation(refType.getRelation()); - rval.setTargetType(refType.getType()); - return rval; - } - - public static PropertyLimitationsType getLimitationsType(List limitationsTypes, LayerType layer) throws SchemaException { - if (limitationsTypes == null) { - return null; - } - PropertyLimitationsType found = null; - for (PropertyLimitationsType limitType: limitationsTypes) { - if (contains(limitType.getLayer(),layer)) { - if (found == null) { - found = limitType; - } else { - throw new SchemaException("Duplicate definition of limitations for layer '"+layer+"'"); - } - } - } - return found; - } - - private static boolean contains(List layers, LayerType layer) { - if (layers == null || layers.isEmpty()) { - return layer == null; - } - return layers.contains(layer); - } - - public static boolean contains(Collection collection, ObjectReferenceType item) { - for (ObjectReferenceType collectionItem: collection) { - if (matches(collectionItem, item)) { - return true; - } - } - return false; - } - - private static boolean matches(ObjectReferenceType a, ObjectReferenceType b) { - if (a == null && b == null) { - return true; - } - if (a == null || b == null) { - return false; - } - return MiscUtil.equals(a.getOid(), b.getOid()); - } - - // Some searches may return duplicate objects. This is an utility method to remove the duplicates. - public static void reduceSearchResult(List> results) { - if (results == null || results.isEmpty()) { - return; - } - Set oidsSeen = new HashSet<>(); - Iterator> iterator = results.iterator(); - while (iterator.hasNext()) { - PrismObject prismObject = iterator.next(); - if (oidsSeen.contains(prismObject.getOid())) { - iterator.remove(); - } else { - oidsSeen.add(prismObject.getOid()); - } - } - } - - /** - * Returns modification time or creation time (if there was no mo - */ - public static XMLGregorianCalendar getChangeTimestamp(MetadataType metadata) { - if (metadata == null) { - return null; - } - XMLGregorianCalendar modifyTimestamp = metadata.getModifyTimestamp(); - if (modifyTimestamp != null) { - return modifyTimestamp; - } else { - return metadata.getCreateTimestamp(); - } - } - - - public static boolean referenceMatches(ObjectReferenceType refPattern, ObjectReferenceType ref, - PrismContext prismContext) { - if (refPattern.getOid() != null && !refPattern.getOid().equals(ref.getOid())) { - return false; - } - if (refPattern.getType() != null && !QNameUtil.match(refPattern.getType(), ref.getType())) { - return false; - } - if (!prismContext.relationMatches(refPattern.getRelation(), ref.getRelation())) { - return false; - } - return true; - } - - /** - * Make quick and reasonably reliable comparison. E.g. compare prism objects only by - * comparing OIDs. This is ideal for cases when the compare is called often and the - * objects are unlikely to change (e.g. user interface selectable beans). - */ - @SuppressWarnings("rawtypes") - public static boolean quickEquals(Object a, Object b) { - if (a == null && b == null) { - return true; - } - if (a == null || b == null) { - return false; - } - if (a instanceof PrismObject) { - if (b instanceof PrismObject) { - // In case both values are objects then compare only OIDs. - // that should be enough. Comparing complete objects may be slow - // (e.g. if the objects have many assignments) - String aOid = ((PrismObject)a).getOid(); - String bOid = ((PrismObject)b).getOid(); - if (aOid != null && bOid != null) { - return aOid.equals(bOid); - } - } else { - return false; - } - } - if (a instanceof ObjectType) { - if (b instanceof ObjectType) { - // In case both values are objects then compare only OIDs. - // that should be enough. Comparing complete objects may be slow - // (e.g. if the objects have many assignments) - String aOid = ((ObjectType)a).getOid(); - String bOid = ((ObjectType)b).getOid(); - if (aOid != null && bOid != null) { - return aOid.equals(bOid); - } - } else { - return false; - } - } - return a.equals(b); - } - - @NotNull - public static InformationType createInformationType(List messages) { - InformationType rv = new InformationType(); - messages.forEach(s -> rv.getPart().add(new InformationPartType().localizableText(s))); - return rv; - } - - public static ItemProcessing toItemProcessing(ItemProcessingType type) { - if (type == null) { - return null; - } - switch (type) { - case IGNORE: - return ItemProcessing.IGNORE; - case MINIMAL: - return ItemProcessing.MINIMAL; - case AUTO: - return ItemProcessing.AUTO; - default: - throw new IllegalArgumentException("Unknown processing "+type); - } - } - - public static boolean canBeAssignedFrom(QName requiredTypeQName, Class providedTypeClass) { - Class requiredTypeClass = ObjectTypes.getObjectTypeFromTypeQName(requiredTypeQName).getClassDefinition(); - return requiredTypeClass.isAssignableFrom(providedTypeClass); - } - - /** - * This is NOT A REAL METHOD. It just returns null. It is here to mark all the places - * where proper handling of expression profiles should be later added. - */ - public static ExpressionProfile getExpressionProfile() { - return null; - } - - public static void mergeDisplay(DisplayType viewDisplay, DisplayType archetypeDisplay) { - if (viewDisplay.getLabel() == null) { - viewDisplay.setLabel(archetypeDisplay.getLabel()); - } - if (viewDisplay.getSingularLabel() == null) { - viewDisplay.setSingularLabel(archetypeDisplay.getSingularLabel()); - } - if (viewDisplay.getPluralLabel() == null) { - viewDisplay.setPluralLabel(archetypeDisplay.getPluralLabel()); - } - IconType archetypeIcon = archetypeDisplay.getIcon(); - if (archetypeIcon != null) { - IconType viewIcon = viewDisplay.getIcon(); - if (viewIcon == null) { - viewIcon = new IconType(); - viewDisplay.setIcon(viewIcon); - } - if (viewIcon.getCssClass() == null) { - viewIcon.setCssClass(archetypeIcon.getCssClass()); - } - if (viewIcon.getColor() == null) { - viewIcon.setColor(archetypeIcon.getColor()); - } - } - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.schema.util; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.QNameUtil; +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.api_types_3.ImportOptionsType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ObjectListType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PropertyReferenceListType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import org.jetbrains.annotations.NotNull; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; +import java.util.*; + +/** + * @author Radovan Semancik + * + */ +public class MiscSchemaUtil { + + private static final Trace LOGGER = TraceManager.getTrace(MiscSchemaUtil.class); + private static final Random RND = new Random(); + + public static ObjectListType toObjectListType(List> list) { + ObjectListType listType = new ObjectListType(); + for (PrismObject o : list) { + listType.getObject().add(o.asObjectable()); + } + return listType; + } + + public static List> toList(Class type, ObjectListType listType) { + List> list = new ArrayList<>(); + for (ObjectType o : listType.getObject()) { + list.add((PrismObject) o.asPrismObject()); + } + return list; + } + + public static List toObjectableList(List> objectList) { + if (objectList == null) { + return null; + } + List objectableList = new ArrayList<>(objectList.size()); + for (PrismObject object: objectList) { + objectableList.add(object.asObjectable()); + } + return objectableList; + } + + public static ImportOptionsType getDefaultImportOptions() { + ImportOptionsType options = new ImportOptionsType(); + options.setOverwrite(false); + options.setValidateStaticSchema(false); + options.setValidateDynamicSchema(false); + options.setEncryptProtectedValues(true); + options.setFetchResourceSchema(false); + options.setSummarizeErrors(true); + options.setSummarizeSucceses(true); + return options; + } + + public static CachingMetadataType generateCachingMetadata() { + CachingMetadataType cmd = new CachingMetadataType(); + XMLGregorianCalendar xmlGregorianCalendarNow = XmlTypeConverter.createXMLGregorianCalendar(System.currentTimeMillis()); + cmd.setRetrievalTimestamp(xmlGregorianCalendarNow); + cmd.setSerialNumber(generateSerialNumber()); + return cmd; + } + + private static String generateSerialNumber() { + return Long.toHexString(RND.nextLong())+"-"+Long.toHexString(RND.nextLong()); + } + + public static boolean isNullOrEmpty(ProtectedStringType ps) { + return (ps == null || ps.isEmpty()); + } + + public static void setPassword(CredentialsType credentials, ProtectedStringType password) { + PasswordType credPass = credentials.getPassword(); + if (credPass == null) { + credPass = new PasswordType(); + credentials.setPassword(credPass); + } + credPass.setValue(password); + } + + public static Collection toCollection(String entry) { + List list = new ArrayList<>(1); + list.add(entry); + return list; + } + + public static Collection itemReferenceListTypeToItemPathList(PropertyReferenceListType resolve, PrismContext prismContext) { + Collection itemPathList = new ArrayList<>(resolve.getProperty().size()); + for (ItemPathType itemXPathElement: resolve.getProperty()) { + itemPathList.add(prismContext.toPath(itemXPathElement)); + } + return itemPathList; + } + + public static SelectorQualifiedGetOptionsType optionsToOptionsType(Collection> options){ + SelectorQualifiedGetOptionsType optionsType = new SelectorQualifiedGetOptionsType(); + List retval = new ArrayList<>(); + for (SelectorOptions option: options){ + retval.add(selectorOptionToSelectorQualifiedGetOptionType(option)); + } + optionsType.getOption().addAll(retval); + return optionsType; + } + + private static SelectorQualifiedGetOptionType selectorOptionToSelectorQualifiedGetOptionType(SelectorOptions selectorOption) { + OptionObjectSelectorType selectorType = selectorToSelectorType(selectorOption.getSelector()); + GetOperationOptionsType getOptionsType = getOptionsToGetOptionsType(selectorOption.getOptions()); + SelectorQualifiedGetOptionType selectorOptionType = new SelectorQualifiedGetOptionType(); + selectorOptionType.setOptions(getOptionsType); + selectorOptionType.setSelector(selectorType); + return selectorOptionType; + } + + private static OptionObjectSelectorType selectorToSelectorType(ObjectSelector selector) { + if (selector == null) { + return null; + } + OptionObjectSelectorType selectorType = new OptionObjectSelectorType(); + selectorType.setPath(new ItemPathType(selector.getPath())); + return selectorType; + } + + private static GetOperationOptionsType getOptionsToGetOptionsType(GetOperationOptions options) { + GetOperationOptionsType optionsType = new GetOperationOptionsType(); + optionsType.setRetrieve(RetrieveOption.toRetrieveOptionType(options.getRetrieve())); + optionsType.setResolve(options.getResolve()); + optionsType.setResolveNames(options.getResolveNames()); + optionsType.setNoFetch(options.getNoFetch()); + optionsType.setRaw(options.getRaw()); + optionsType.setTolerateRawData(options.getTolerateRawData()); + optionsType.setNoDiscovery(options.getDoNotDiscovery()); + // TODO relational value search query (but it might become obsolete) + optionsType.setAllowNotFound(options.getAllowNotFound()); + optionsType.setPointInTimeType(PointInTimeType.toPointInTimeTypeType(options.getPointInTimeType())); + optionsType.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOptionType(options.getDefinitionProcessing())); + optionsType.setStaleness(options.getStaleness()); + optionsType.setDistinct(options.getDistinct()); + return optionsType; + } + + public static List> optionsTypeToOptions( + SelectorQualifiedGetOptionsType objectOptionsType, PrismContext prismContext) { + if (objectOptionsType == null) { + return null; + } + List> retval = new ArrayList<>(); + for (SelectorQualifiedGetOptionType optionType : objectOptionsType.getOption()) { + retval.add(selectorQualifiedGetOptionTypeToSelectorOption(optionType, prismContext)); + } + return retval; + } + + private static SelectorOptions selectorQualifiedGetOptionTypeToSelectorOption( + SelectorQualifiedGetOptionType objectOptionsType, PrismContext prismContext) { + ObjectSelector selector = selectorTypeToSelector(objectOptionsType.getSelector(), prismContext); + GetOperationOptions options = getOptionsTypeToGetOptions(objectOptionsType.getOptions()); + return new SelectorOptions<>(selector, options); + } + + private static GetOperationOptions getOptionsTypeToGetOptions(GetOperationOptionsType optionsType) { + GetOperationOptions options = new GetOperationOptions(); + options.setRetrieve(RetrieveOption.fromRetrieveOptionType(optionsType.getRetrieve())); + options.setResolve(optionsType.isResolve()); + options.setResolveNames(optionsType.isResolveNames()); + options.setNoFetch(optionsType.isNoFetch()); + options.setRaw(optionsType.isRaw()); + options.setTolerateRawData(optionsType.isTolerateRawData()); + options.setDoNotDiscovery(optionsType.isNoDiscovery()); + // TODO relational value search query (but it might become obsolete) + options.setAllowNotFound(optionsType.isAllowNotFound()); + options.setPointInTimeType(PointInTimeType.toPointInTimeType(optionsType.getPointInTimeType())); + options.setDefinitionProcessing(DefinitionProcessingOption.toDefinitionProcessingOption(optionsType.getDefinitionProcessing())); + options.setStaleness(optionsType.getStaleness()); + options.setDistinct(optionsType.isDistinct()); + return options; + } + + private static ObjectSelector selectorTypeToSelector(OptionObjectSelectorType selectorType, + PrismContext prismContext) { + if (selectorType == null) { + return null; + } + return new ObjectSelector(prismContext.toUniformPath(selectorType.getPath())); + } + + /** + * Convenience method that helps avoid some compiler warnings. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Collection> createCollection(ObjectDelta... deltas) { + return (Collection)MiscUtil.createCollection(deltas); + } + + /** + * Convenience method that helps avoid some compiler warnings. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static Collection> createCollection(ItemDelta... deltas) { + return MiscUtil.createCollection(deltas); + } + + public static Collection> cloneObjectDeltaCollection( + Collection> origCollection) { + if (origCollection == null) { + return null; + } + Collection> clonedCollection = new ArrayList<>(origCollection.size()); + for (ObjectDelta origDelta: origCollection) { + clonedCollection.add(origDelta.clone()); + } + return clonedCollection; + } + + public static Collection> cloneObjectDeltaOperationCollection( + Collection> origCollection) { + if (origCollection == null) { + return null; + } + Collection> clonedCollection = new ArrayList<>(origCollection.size()); + for (ObjectDeltaOperation origDelta: origCollection) { + clonedCollection.add(origDelta.clone()); + } + return clonedCollection; + } + + public static ObjectReferenceType createObjectReference(String oid, QName type) { + ObjectReferenceType ref = new ObjectReferenceType(); + ref.setOid(oid); + ref.setType(type); + return ref; + } + + public static ObjectReferenceType createObjectReference(PrismObject object, Class implicitReferenceTargetType) { + ObjectReferenceType ref = new ObjectReferenceType(); + ref.setOid(object.getOid()); + if (implicitReferenceTargetType == null || !implicitReferenceTargetType.equals(object.getCompileTimeClass())) { + ref.setType(ObjectTypes.getObjectType(object.getCompileTimeClass()).getTypeQName()); + } + ref.setTargetName(PolyString.toPolyStringType(object.getName())); + return ref; + } + + + public static boolean equalsIntent(String intent1, String intent2) { + if (intent1 == null) { + intent1 = SchemaConstants.INTENT_DEFAULT; + } + if (intent2 == null) { + intent2 = SchemaConstants.INTENT_DEFAULT; + } + return intent1.equals(intent2); + } + + public static boolean matchesKind(ShadowKindType expectedKind, ShadowKindType actualKind) { + if (expectedKind == null) { + return true; + } + return expectedKind.equals(actualKind); + } + + + public static AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType( + ProjectionPolicyType accountSynchronizationSettings) { + if (accountSynchronizationSettings == null) { + // default + return AssignmentPolicyEnforcementType.RELATIVE; + } + AssignmentPolicyEnforcementType assignmentPolicyEnforcement = accountSynchronizationSettings.getAssignmentPolicyEnforcement(); + if (assignmentPolicyEnforcement == null) { + return AssignmentPolicyEnforcementType.RELATIVE; + } + return assignmentPolicyEnforcement; + } + + public static PrismReferenceValue objectReferenceTypeToReferenceValue(ObjectReferenceType refType, + PrismContext prismContext) { + if (refType == null) { + return null; + } + PrismReferenceValue rval = prismContext.itemFactory().createReferenceValue(); + rval.setOid(refType.getOid()); + rval.setDescription(refType.getDescription()); + rval.setFilter(refType.getFilter()); + rval.setRelation(refType.getRelation()); + rval.setTargetType(refType.getType()); + return rval; + } + + public static PropertyLimitationsType getLimitationsType(List limitationsTypes, LayerType layer) throws SchemaException { + if (limitationsTypes == null) { + return null; + } + PropertyLimitationsType found = null; + for (PropertyLimitationsType limitType: limitationsTypes) { + if (contains(limitType.getLayer(),layer)) { + if (found == null) { + found = limitType; + } else { + throw new SchemaException("Duplicate definition of limitations for layer '"+layer+"'"); + } + } + } + return found; + } + + private static boolean contains(List layers, LayerType layer) { + if (layers == null || layers.isEmpty()) { + return layer == null; + } + return layers.contains(layer); + } + + public static boolean contains(Collection collection, ObjectReferenceType item) { + for (ObjectReferenceType collectionItem: collection) { + if (matches(collectionItem, item)) { + return true; + } + } + return false; + } + + private static boolean matches(ObjectReferenceType a, ObjectReferenceType b) { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + return false; + } + return MiscUtil.equals(a.getOid(), b.getOid()); + } + + // Some searches may return duplicate objects. This is an utility method to remove the duplicates. + public static void reduceSearchResult(List> results) { + if (results == null || results.isEmpty()) { + return; + } + Set oidsSeen = new HashSet<>(); + Iterator> iterator = results.iterator(); + while (iterator.hasNext()) { + PrismObject prismObject = iterator.next(); + if (oidsSeen.contains(prismObject.getOid())) { + iterator.remove(); + } else { + oidsSeen.add(prismObject.getOid()); + } + } + } + + /** + * Returns modification time or creation time (if there was no mo + */ + public static XMLGregorianCalendar getChangeTimestamp(MetadataType metadata) { + if (metadata == null) { + return null; + } + XMLGregorianCalendar modifyTimestamp = metadata.getModifyTimestamp(); + if (modifyTimestamp != null) { + return modifyTimestamp; + } else { + return metadata.getCreateTimestamp(); + } + } + + + public static boolean referenceMatches(ObjectReferenceType refPattern, ObjectReferenceType ref, + PrismContext prismContext) { + if (refPattern.getOid() != null && !refPattern.getOid().equals(ref.getOid())) { + return false; + } + if (refPattern.getType() != null && !QNameUtil.match(refPattern.getType(), ref.getType())) { + return false; + } + if (!prismContext.relationMatches(refPattern.getRelation(), ref.getRelation())) { + return false; + } + return true; + } + + /** + * Make quick and reasonably reliable comparison. E.g. compare prism objects only by + * comparing OIDs. This is ideal for cases when the compare is called often and the + * objects are unlikely to change (e.g. user interface selectable beans). + */ + @SuppressWarnings("rawtypes") + public static boolean quickEquals(Object a, Object b) { + if (a == null && b == null) { + return true; + } + if (a == null || b == null) { + return false; + } + if (a instanceof PrismObject) { + if (b instanceof PrismObject) { + // In case both values are objects then compare only OIDs. + // that should be enough. Comparing complete objects may be slow + // (e.g. if the objects have many assignments) + String aOid = ((PrismObject)a).getOid(); + String bOid = ((PrismObject)b).getOid(); + if (aOid != null && bOid != null) { + return aOid.equals(bOid); + } + } else { + return false; + } + } + if (a instanceof ObjectType) { + if (b instanceof ObjectType) { + // In case both values are objects then compare only OIDs. + // that should be enough. Comparing complete objects may be slow + // (e.g. if the objects have many assignments) + String aOid = ((ObjectType)a).getOid(); + String bOid = ((ObjectType)b).getOid(); + if (aOid != null && bOid != null) { + return aOid.equals(bOid); + } + } else { + return false; + } + } + return a.equals(b); + } + + @NotNull + public static InformationType createInformationType(List messages) { + InformationType rv = new InformationType(); + messages.forEach(s -> rv.getPart().add(new InformationPartType().localizableText(s))); + return rv; + } + + public static ItemProcessing toItemProcessing(ItemProcessingType type) { + if (type == null) { + return null; + } + switch (type) { + case IGNORE: + return ItemProcessing.IGNORE; + case MINIMAL: + return ItemProcessing.MINIMAL; + case AUTO: + return ItemProcessing.AUTO; + default: + throw new IllegalArgumentException("Unknown processing "+type); + } + } + + public static boolean canBeAssignedFrom(QName requiredTypeQName, Class providedTypeClass) { + Class requiredTypeClass = ObjectTypes.getObjectTypeFromTypeQName(requiredTypeQName).getClassDefinition(); + return requiredTypeClass.isAssignableFrom(providedTypeClass); + } + + /** + * This is NOT A REAL METHOD. It just returns null. It is here to mark all the places + * where proper handling of expression profiles should be later added. + */ + public static ExpressionProfile getExpressionProfile() { + return null; + } + + public static void mergeDisplay(DisplayType viewDisplay, DisplayType archetypeDisplay) { + if (viewDisplay.getLabel() == null) { + viewDisplay.setLabel(archetypeDisplay.getLabel()); + } + if (viewDisplay.getSingularLabel() == null) { + viewDisplay.setSingularLabel(archetypeDisplay.getSingularLabel()); + } + if (viewDisplay.getPluralLabel() == null) { + viewDisplay.setPluralLabel(archetypeDisplay.getPluralLabel()); + } + IconType archetypeIcon = archetypeDisplay.getIcon(); + if (archetypeIcon != null) { + IconType viewIcon = viewDisplay.getIcon(); + if (viewIcon == null) { + viewIcon = new IconType(); + viewDisplay.setIcon(viewIcon); + } + if (viewIcon.getCssClass() == null) { + viewIcon.setCssClass(archetypeIcon.getCssClass()); + } + if (viewIcon.getColor() == null) { + viewIcon.setColor(archetypeIcon.getColor()); + } + } + } + + /* + the ordering algorithm is: the first level is occupied by + the column which previousColumn == null || "" || notExistingColumnNameValue. + Each next level contains columns which + previousColumn == columnNameFromPreviousLevel + */ + public static List orderCustomColumns(List customColumns){ + if (customColumns == null || customColumns.size() == 0){ + return new ArrayList<>(); + } + List customColumnsList = new ArrayList<>(customColumns); + List previousColumnValues = new ArrayList<>(); + previousColumnValues.add(null); + previousColumnValues.add(""); + + Map columnRefsMap = new HashMap<>(); + for (GuiObjectColumnType column : customColumns){ + columnRefsMap.put(column.getName(), column.getPreviousColumn() == null ? "" : column.getPreviousColumn()); + } + + List temp = new ArrayList<> (); + int index = 0; + while (index < customColumns.size()){ + int sortFrom = index; + for (int i = index; i < customColumnsList.size(); i++){ + GuiObjectColumnType column = customColumnsList.get(i); + if (previousColumnValues.contains(column.getPreviousColumn()) || + !columnRefsMap.containsKey(column.getPreviousColumn())){ + Collections.swap(customColumnsList, index, i); + index++; + temp.add(column.getName()); + } + } + if (temp.size() == 0){ + temp.add(customColumnsList.get(index).getName()); + index++; + } + if (index - sortFrom > 1){ + customColumnsList.subList(sortFrom, index - 1) + .sort((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName())); + } + previousColumnValues.clear(); + previousColumnValues.addAll(temp); + temp.clear(); + } + return customColumnsList; + } +} diff --git a/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertExpressionHelper.java b/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertExpressionHelper.java index 23890b303af..67f808c168d 100644 --- a/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertExpressionHelper.java +++ b/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertExpressionHelper.java @@ -1,125 +1,125 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.certification.impl; - -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.xml.XsdTypeMapper; -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.List; - -/** - * @author mederly - */ -@Component -public class AccCertExpressionHelper { - - private static final Trace LOGGER = TraceManager.getTrace(AccCertExpressionHelper.class); - - @Autowired private PrismContext prismContext; - @Autowired private ExpressionFactory expressionFactory; - - @SuppressWarnings("SameParameterValue") - private List evaluateExpression(Class resultClass, ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - QName xsdType = XsdTypeMapper.toXsdType(resultClass); - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, xsdType); - - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - - PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); - - List retval = new ArrayList<>(); - for (PrismPropertyValue item : exprResult.getZeroSet()) { - retval.add(item.getValue()); - } - return retval; - } - - List evaluateRefExpressionChecked(ExpressionType expressionType, - ExpressionVariables expressionVariables, String shortDesc, Task task, OperationResult result) { - try { - return evaluateRefExpression(expressionType, expressionVariables, shortDesc, task, result); - } catch (CommonException|RuntimeException e) { - LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); - result.recordFatalError("Couldn't evaluate " + shortDesc, e); - throw new SystemException(e); - } - } - - private List evaluateRefExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismReferenceDefinition resultDef = prismContext.definitionFactory().createReferenceDefinition(resultName, ObjectReferenceType.COMPLEX_TYPE); - - Expression expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - context.setAdditionalConvertor(ExpressionUtil.createRefConvertor(UserType.COMPLEX_TYPE)); - PrismValueDeltaSetTriple exprResult = - ModelExpressionThreadLocalHolder.evaluateRefExpressionInContext(expression, context, task, result); - - List retval = new ArrayList<>(); - for (PrismReferenceValue value : exprResult.getZeroSet()) { - ObjectReferenceType ort = new ObjectReferenceType(); - ort.setupReferenceValue(value); - retval.add(ort); - } - return retval; - } - -// public boolean evaluateBooleanExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, -// String shortDesc, Task task, OperationResult result) { -// -// try { -// return evaluateBooleanExpression(expressionType, expressionVariables, shortDesc, task, result); -// } catch (ObjectNotFoundException|SchemaException|ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { -// LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); -// result.recordFatalError("Couldn't evaluate " + shortDesc, e); -// throw new SystemException(e); -// } -// } - - public boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, - Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - List exprResult = evaluateExpression(Boolean.class, expressionType, expressionVariables, shortDesc, task, result); - if (exprResult.size() == 0) { - return false; - } else if (exprResult.size() > 1) { - throw new IllegalStateException("Filter expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); - } - Boolean boolResult = exprResult.get(0); - return boolResult != null ? boolResult : false; - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.certification.impl; + +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.xml.XsdTypeMapper; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.List; + +/** + * @author mederly + */ +@Component +public class AccCertExpressionHelper { + + private static final Trace LOGGER = TraceManager.getTrace(AccCertExpressionHelper.class); + + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + + @SuppressWarnings("SameParameterValue") + private List evaluateExpression(Class resultClass, ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + QName xsdType = XsdTypeMapper.toXsdType(resultClass); + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, xsdType); + + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + + PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); + + List retval = new ArrayList<>(); + for (PrismPropertyValue item : exprResult.getZeroSet()) { + retval.add(item.getValue()); + } + return retval; + } + + List evaluateRefExpressionChecked(ExpressionType expressionType, + ExpressionVariables expressionVariables, String shortDesc, Task task, OperationResult result) { + try { + return evaluateRefExpression(expressionType, expressionVariables, shortDesc, task, result); + } catch (CommonException|RuntimeException e) { + LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); + result.recordFatalError("Couldn't evaluate " + shortDesc, e); + throw new SystemException(e); + } + } + + private List evaluateRefExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismReferenceDefinition resultDef = prismContext.definitionFactory().createReferenceDefinition(resultName, ObjectReferenceType.COMPLEX_TYPE); + + Expression expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + context.setAdditionalConvertor(ExpressionUtil.createRefConvertor(UserType.COMPLEX_TYPE)); + PrismValueDeltaSetTriple exprResult = + ModelExpressionThreadLocalHolder.evaluateRefExpressionInContext(expression, context, task, result); + + List retval = new ArrayList<>(); + for (PrismReferenceValue value : exprResult.getZeroSet()) { + ObjectReferenceType ort = new ObjectReferenceType(); + ort.setupReferenceValue(value); + retval.add(ort); + } + return retval; + } + +// public boolean evaluateBooleanExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, +// String shortDesc, Task task, OperationResult result) { +// +// try { +// return evaluateBooleanExpression(expressionType, expressionVariables, shortDesc, task, result); +// } catch (ObjectNotFoundException|SchemaException|ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { +// LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); +// result.recordFatalError("Couldn't evaluate " + shortDesc, e); +// throw new SystemException(e); +// } +// } + + public boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + List exprResult = evaluateExpression(Boolean.class, expressionType, expressionVariables, shortDesc, task, result); + if (exprResult.size() == 0) { + return false; + } else if (exprResult.size() > 1) { + throw new IllegalStateException("Filter expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); + } + Boolean boolResult = exprResult.get(0); + return boolResult != null ? boolResult : false; + } +} diff --git a/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertReviewersHelper.java b/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertReviewersHelper.java index d6632f3a378..a630b2685df 100644 --- a/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertReviewersHelper.java +++ b/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/AccCertReviewersHelper.java @@ -1,287 +1,287 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.certification.impl; - -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.api.expr.OrgStructFunctions; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismReferenceValue; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.CertCampaignTypeUtil; -import com.evolveum.midpoint.schema.util.ObjectQueryUtil; -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.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 org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import javax.xml.namespace.QName; -import java.util.*; -import java.util.stream.Collectors; - -/** - * @author mederly - */ -@Component -public class AccCertReviewersHelper { - - private static final Trace LOGGER = TraceManager.getTrace(AccCertReviewersHelper.class); - - @Autowired - @Qualifier("cacheRepositoryService") - private RepositoryService repositoryService; - - @Autowired private OrgStructFunctions orgStructFunctions; - @Autowired private PrismContext prismContext; - @Autowired private AccCertExpressionHelper expressionHelper; - @Autowired private RelationRegistry relationRegistry; - - AccessCertificationReviewerSpecificationType findReviewersSpecification(AccessCertificationCampaignType campaign, int stage) { - AccessCertificationStageDefinitionType stageDef = CertCampaignTypeUtil.findStageDefinition(campaign, stage); - return stageDef.getReviewerSpecification(); - } - - List getReviewersForCase(AccessCertificationCaseType _case, AccessCertificationCampaignType campaign, - AccessCertificationReviewerSpecificationType reviewerSpec, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { - if (reviewerSpec == null) { - return Collections.emptyList(); // TODO issue a warning here? - } - - List reviewers = new ArrayList<>(); - if (Boolean.TRUE.equals(reviewerSpec.isUseTargetOwner())) { - cloneAndMerge(reviewers, getTargetObjectOwners(_case, result)); - } - if (Boolean.TRUE.equals(reviewerSpec.isUseTargetApprover())) { - cloneAndMerge(reviewers, getTargetObjectApprovers(_case, result)); - } - if (Boolean.TRUE.equals(reviewerSpec.isUseObjectOwner())) { - cloneAndMerge(reviewers, getObjectOwners(_case, result)); - } - if (Boolean.TRUE.equals(reviewerSpec.isUseObjectApprover())) { - cloneAndMerge(reviewers, getObjectApprovers(_case, result)); - } - if (reviewerSpec.getUseObjectManager() != null) { - cloneAndMerge(reviewers, getObjectManagers(_case, reviewerSpec.getUseObjectManager(), task, result)); - } - for (ExpressionType reviewerExpression : reviewerSpec.getReviewerExpression()) { - ExpressionVariables variables = new ExpressionVariables(); - // The _case does NOT have definition here. Can we have it? - variables.put(ExpressionConstants.VAR_CERTIFICATION_CASE, _case, AccessCertificationCaseType.class); - variables.putObject(ExpressionConstants.VAR_CAMPAIGN, campaign, AccessCertificationCampaignType.class); - variables.put(ExpressionConstants.VAR_REVIEWER_SPECIFICATION, reviewerSpec, AccessCertificationReviewerSpecificationType.class); - List refList = expressionHelper - .evaluateRefExpressionChecked(reviewerExpression, variables, "reviewer expression", task, result); - cloneAndMerge(reviewers, refList); - } - resolveRoleReviewers(reviewers, result); - if (reviewers.isEmpty()) { - cloneAndMerge(reviewers, reviewerSpec.getDefaultReviewerRef()); - } - cloneAndMerge(reviewers, reviewerSpec.getAdditionalReviewerRef()); - resolveRoleReviewers(reviewers, result); - - return reviewers; - } - - private void resolveRoleReviewers(List reviewers, OperationResult result) - throws SchemaException { - List resolved = new ArrayList<>(); - for (Iterator iterator = reviewers.iterator(); iterator.hasNext(); ) { - ObjectReferenceType reviewer = iterator.next(); - if (QNameUtil.match(reviewer.getType(), RoleType.COMPLEX_TYPE) || - QNameUtil.match(reviewer.getType(), OrgType.COMPLEX_TYPE) || - QNameUtil.match(reviewer.getType(), ServiceType.COMPLEX_TYPE)) { - iterator.remove(); - resolved.addAll(getMembers(reviewer, result)); - } - } - for (ObjectReferenceType ref : resolved) { - if (!containsOid(reviewers, ref.getOid())) { - reviewers.add(ref); - } - } - } - - private List getMembers(ObjectReferenceType abstractRoleRef, OperationResult result) - throws SchemaException { - Collection references = ObjectQueryUtil - .createReferences(abstractRoleRef.getOid(), RelationKindType.MEMBER, relationRegistry); - ObjectQuery query = references.isEmpty() - ? prismContext.queryFor(UserType.class).none().build() - : prismContext.queryFor(UserType.class) - .item(UserType.F_ROLE_MEMBERSHIP_REF).ref(references) - .build(); - return repositoryService.searchObjects(UserType.class, query, null, result).stream() - .map(obj -> ObjectTypeUtil.createObjectRef(obj, prismContext)) - .collect(Collectors.toList()); - } - - private void cloneAndMerge(List reviewers, Collection newReviewers) { - if (newReviewers == null) { - return; - } - for (ObjectReferenceType newReviewer : newReviewers) { - if (!containsOid(reviewers, newReviewer.getOid())) { - reviewers.add(newReviewer.clone()); - } - } - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean containsOid(List reviewers, String oid) { - for (ObjectReferenceType reviewer : reviewers) { - if (reviewer.getOid().equals(oid)) { - return true; - } - } - return false; - } - - private Collection getObjectManagers(AccessCertificationCaseType _case, ManagerSearchType managerSearch, - Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - try { - ObjectReferenceType objectRef = _case.getObjectRef(); - ObjectType object = resolveReference(objectRef, ObjectType.class, result); - - String orgType = managerSearch.getOrgType(); - boolean allowSelf = Boolean.TRUE.equals(managerSearch.isAllowSelf()); - Collection managers; - if (object instanceof UserType) { - managers = orgStructFunctions.getManagers((UserType) object, orgType, allowSelf, true); - } else if (object instanceof OrgType) { - // TODO more elaborate behavior; eliminate unneeded resolveReference above - managers = orgStructFunctions.getManagersOfOrg(object.getOid(), true); - } else if (object instanceof RoleType || object instanceof ServiceType) { - // TODO implement - managers = new HashSet<>(); - } else { - // TODO warning? - managers = new HashSet<>(); - } - List retval = new ArrayList<>(managers.size()); - for (UserType manager : managers) { - retval.add(ObjectTypeUtil.createObjectRef(manager, prismContext)); - } - return retval; - } catch (SecurityViolationException e) { - // never occurs, as preAuthorized is TRUE above - throw new IllegalStateException("Impossible has happened: " + e.getMessage(), e); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - private List getTargetObjectOwners(AccessCertificationCaseType _case, OperationResult result) - throws SchemaException, ObjectNotFoundException { - if (_case.getTargetRef() == null) { - return null; - } - ObjectType target = resolveReference(_case.getTargetRef(), ObjectType.class, result); - if (target instanceof AbstractRoleType) { - return getAssignees((AbstractRoleType) target, RelationKindType.OWNER, result); - } else if (target instanceof ResourceType) { - return ResourceTypeUtil.getOwnerRef((ResourceType) target); - } else { - return null; - } - } - - private List getAssignees(AbstractRoleType role, RelationKindType relationKind, OperationResult result) - throws SchemaException { - List rv = new ArrayList<>(); - if (relationKind != RelationKindType.OWNER && relationKind != RelationKindType.APPROVER) { - throw new AssertionError(relationKind); - } - // TODO in theory, we could look for approvers/owners of UserType, right? - Collection values = new ArrayList<>(); - for (QName relation : relationRegistry.getAllRelationsFor(relationKind)) { - PrismReferenceValue ref = prismContext.itemFactory().createReferenceValue(role.getOid()); - ref.setRelation(relation); - values.add(ref); - } - ObjectQuery query = prismContext.queryFor(FocusType.class) - .item(FocusType.F_ROLE_MEMBERSHIP_REF).ref(values) - .build(); - List> assignees = repositoryService.searchObjects(FocusType.class, query, null, result); - LOGGER.trace("Looking for '{}' of {} using {}: found: {}", relationKind, role, query, assignees); - assignees.forEach(o -> rv.add(ObjectTypeUtil.createObjectRef(o, prismContext))); - return rv; - } - - private List getObjectOwners(AccessCertificationCaseType _case, OperationResult result) - throws SchemaException, ObjectNotFoundException { - if (_case.getObjectRef() == null) { - return null; - } - ObjectType object = resolveReference(_case.getObjectRef(), ObjectType.class, result); - if (object instanceof AbstractRoleType) { - return getAssignees((AbstractRoleType) object, RelationKindType.OWNER, result); - } else { - return null; - } - } - - private Collection getTargetObjectApprovers(AccessCertificationCaseType _case, - OperationResult result) throws SchemaException, ObjectNotFoundException { - if (_case.getTargetRef() == null) { - return null; - } - ObjectType target = resolveReference(_case.getTargetRef(), ObjectType.class, result); - if (target instanceof AbstractRoleType) { - return getAssignees((AbstractRoleType) target, RelationKindType.APPROVER, result); - } else if (target instanceof ResourceType) { - return ResourceTypeUtil.getApproverRef((ResourceType) target); - } else { - return null; - } - } - - private Collection getObjectApprovers(AccessCertificationCaseType _case, - OperationResult result) throws SchemaException, ObjectNotFoundException { - if (_case.getObjectRef() == null) { - return null; - } - ObjectType object = resolveReference(_case.getObjectRef(), ObjectType.class, result); - if (object instanceof AbstractRoleType) { - return getAssignees((AbstractRoleType) object, RelationKindType.APPROVER, result); - } else { - return null; - } - } - - @SuppressWarnings("SameParameterValue") - private ObjectType resolveReference(ObjectReferenceType objectRef, Class defaultObjectTypeClass, - OperationResult result) throws SchemaException, ObjectNotFoundException { - final Class objectTypeClass; - if (objectRef.getType() != null) { - //noinspection unchecked - objectTypeClass = (Class) prismContext.getSchemaRegistry().getCompileTimeClassForObjectType(objectRef.getType()); - if (objectTypeClass == null) { - throw new SchemaException("No object class found for " + objectRef.getType()); - } - } else { - objectTypeClass = defaultObjectTypeClass; - } - PrismObject object = repositoryService.getObject(objectTypeClass, objectRef.getOid(), null, result); - return object.asObjectable(); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.certification.impl; + +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.api.expr.OrgStructFunctions; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.CertCampaignTypeUtil; +import com.evolveum.midpoint.schema.util.ObjectQueryUtil; +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.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 org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author mederly + */ +@Component +public class AccCertReviewersHelper { + + private static final Trace LOGGER = TraceManager.getTrace(AccCertReviewersHelper.class); + + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; + + @Autowired private OrgStructFunctions orgStructFunctions; + @Autowired private PrismContext prismContext; + @Autowired private AccCertExpressionHelper expressionHelper; + @Autowired private RelationRegistry relationRegistry; + + AccessCertificationReviewerSpecificationType findReviewersSpecification(AccessCertificationCampaignType campaign, int stage) { + AccessCertificationStageDefinitionType stageDef = CertCampaignTypeUtil.findStageDefinition(campaign, stage); + return stageDef.getReviewerSpecification(); + } + + List getReviewersForCase(AccessCertificationCaseType _case, AccessCertificationCampaignType campaign, + AccessCertificationReviewerSpecificationType reviewerSpec, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { + if (reviewerSpec == null) { + return Collections.emptyList(); // TODO issue a warning here? + } + + List reviewers = new ArrayList<>(); + if (Boolean.TRUE.equals(reviewerSpec.isUseTargetOwner())) { + cloneAndMerge(reviewers, getTargetObjectOwners(_case, result)); + } + if (Boolean.TRUE.equals(reviewerSpec.isUseTargetApprover())) { + cloneAndMerge(reviewers, getTargetObjectApprovers(_case, result)); + } + if (Boolean.TRUE.equals(reviewerSpec.isUseObjectOwner())) { + cloneAndMerge(reviewers, getObjectOwners(_case, result)); + } + if (Boolean.TRUE.equals(reviewerSpec.isUseObjectApprover())) { + cloneAndMerge(reviewers, getObjectApprovers(_case, result)); + } + if (reviewerSpec.getUseObjectManager() != null) { + cloneAndMerge(reviewers, getObjectManagers(_case, reviewerSpec.getUseObjectManager(), task, result)); + } + for (ExpressionType reviewerExpression : reviewerSpec.getReviewerExpression()) { + ExpressionVariables variables = new ExpressionVariables(); + // The _case does NOT have definition here. Can we have it? + variables.put(ExpressionConstants.VAR_CERTIFICATION_CASE, _case, AccessCertificationCaseType.class); + variables.putObject(ExpressionConstants.VAR_CAMPAIGN, campaign, AccessCertificationCampaignType.class); + variables.put(ExpressionConstants.VAR_REVIEWER_SPECIFICATION, reviewerSpec, AccessCertificationReviewerSpecificationType.class); + List refList = expressionHelper + .evaluateRefExpressionChecked(reviewerExpression, variables, "reviewer expression", task, result); + cloneAndMerge(reviewers, refList); + } + resolveRoleReviewers(reviewers, result); + if (reviewers.isEmpty()) { + cloneAndMerge(reviewers, reviewerSpec.getDefaultReviewerRef()); + } + cloneAndMerge(reviewers, reviewerSpec.getAdditionalReviewerRef()); + resolveRoleReviewers(reviewers, result); + + return reviewers; + } + + private void resolveRoleReviewers(List reviewers, OperationResult result) + throws SchemaException { + List resolved = new ArrayList<>(); + for (Iterator iterator = reviewers.iterator(); iterator.hasNext(); ) { + ObjectReferenceType reviewer = iterator.next(); + if (QNameUtil.match(reviewer.getType(), RoleType.COMPLEX_TYPE) || + QNameUtil.match(reviewer.getType(), OrgType.COMPLEX_TYPE) || + QNameUtil.match(reviewer.getType(), ServiceType.COMPLEX_TYPE)) { + iterator.remove(); + resolved.addAll(getMembers(reviewer, result)); + } + } + for (ObjectReferenceType ref : resolved) { + if (!containsOid(reviewers, ref.getOid())) { + reviewers.add(ref); + } + } + } + + private List getMembers(ObjectReferenceType abstractRoleRef, OperationResult result) + throws SchemaException { + Collection references = ObjectQueryUtil + .createReferences(abstractRoleRef.getOid(), RelationKindType.MEMBER, relationRegistry); + ObjectQuery query = references.isEmpty() + ? prismContext.queryFor(UserType.class).none().build() + : prismContext.queryFor(UserType.class) + .item(UserType.F_ROLE_MEMBERSHIP_REF).ref(references) + .build(); + return repositoryService.searchObjects(UserType.class, query, null, result).stream() + .map(obj -> ObjectTypeUtil.createObjectRef(obj, prismContext)) + .collect(Collectors.toList()); + } + + private void cloneAndMerge(List reviewers, Collection newReviewers) { + if (newReviewers == null) { + return; + } + for (ObjectReferenceType newReviewer : newReviewers) { + if (!containsOid(reviewers, newReviewer.getOid())) { + reviewers.add(newReviewer.clone()); + } + } + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean containsOid(List reviewers, String oid) { + for (ObjectReferenceType reviewer : reviewers) { + if (reviewer.getOid().equals(oid)) { + return true; + } + } + return false; + } + + private Collection getObjectManagers(AccessCertificationCaseType _case, ManagerSearchType managerSearch, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + try { + ObjectReferenceType objectRef = _case.getObjectRef(); + ObjectType object = resolveReference(objectRef, ObjectType.class, result); + + String orgType = managerSearch.getOrgType(); + boolean allowSelf = Boolean.TRUE.equals(managerSearch.isAllowSelf()); + Collection managers; + if (object instanceof UserType) { + managers = orgStructFunctions.getManagers((UserType) object, orgType, allowSelf, true); + } else if (object instanceof OrgType) { + // TODO more elaborate behavior; eliminate unneeded resolveReference above + managers = orgStructFunctions.getManagersOfOrg(object.getOid(), true); + } else if (object instanceof RoleType || object instanceof ServiceType) { + // TODO implement + managers = new HashSet<>(); + } else { + // TODO warning? + managers = new HashSet<>(); + } + List retval = new ArrayList<>(managers.size()); + for (UserType manager : managers) { + retval.add(ObjectTypeUtil.createObjectRef(manager, prismContext)); + } + return retval; + } catch (SecurityViolationException e) { + // never occurs, as preAuthorized is TRUE above + throw new IllegalStateException("Impossible has happened: " + e.getMessage(), e); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + private List getTargetObjectOwners(AccessCertificationCaseType _case, OperationResult result) + throws SchemaException, ObjectNotFoundException { + if (_case.getTargetRef() == null) { + return null; + } + ObjectType target = resolveReference(_case.getTargetRef(), ObjectType.class, result); + if (target instanceof AbstractRoleType) { + return getAssignees((AbstractRoleType) target, RelationKindType.OWNER, result); + } else if (target instanceof ResourceType) { + return ResourceTypeUtil.getOwnerRef((ResourceType) target); + } else { + return null; + } + } + + private List getAssignees(AbstractRoleType role, RelationKindType relationKind, OperationResult result) + throws SchemaException { + List rv = new ArrayList<>(); + if (relationKind != RelationKindType.OWNER && relationKind != RelationKindType.APPROVER) { + throw new AssertionError(relationKind); + } + // TODO in theory, we could look for approvers/owners of UserType, right? + Collection values = new ArrayList<>(); + for (QName relation : relationRegistry.getAllRelationsFor(relationKind)) { + PrismReferenceValue ref = prismContext.itemFactory().createReferenceValue(role.getOid()); + ref.setRelation(relation); + values.add(ref); + } + ObjectQuery query = prismContext.queryFor(FocusType.class) + .item(FocusType.F_ROLE_MEMBERSHIP_REF).ref(values) + .build(); + List> assignees = repositoryService.searchObjects(FocusType.class, query, null, result); + LOGGER.trace("Looking for '{}' of {} using {}: found: {}", relationKind, role, query, assignees); + assignees.forEach(o -> rv.add(ObjectTypeUtil.createObjectRef(o, prismContext))); + return rv; + } + + private List getObjectOwners(AccessCertificationCaseType _case, OperationResult result) + throws SchemaException, ObjectNotFoundException { + if (_case.getObjectRef() == null) { + return null; + } + ObjectType object = resolveReference(_case.getObjectRef(), ObjectType.class, result); + if (object instanceof AbstractRoleType) { + return getAssignees((AbstractRoleType) object, RelationKindType.OWNER, result); + } else { + return null; + } + } + + private Collection getTargetObjectApprovers(AccessCertificationCaseType _case, + OperationResult result) throws SchemaException, ObjectNotFoundException { + if (_case.getTargetRef() == null) { + return null; + } + ObjectType target = resolveReference(_case.getTargetRef(), ObjectType.class, result); + if (target instanceof AbstractRoleType) { + return getAssignees((AbstractRoleType) target, RelationKindType.APPROVER, result); + } else if (target instanceof ResourceType) { + return ResourceTypeUtil.getApproverRef((ResourceType) target); + } else { + return null; + } + } + + private Collection getObjectApprovers(AccessCertificationCaseType _case, + OperationResult result) throws SchemaException, ObjectNotFoundException { + if (_case.getObjectRef() == null) { + return null; + } + ObjectType object = resolveReference(_case.getObjectRef(), ObjectType.class, result); + if (object instanceof AbstractRoleType) { + return getAssignees((AbstractRoleType) object, RelationKindType.APPROVER, result); + } else { + return null; + } + } + + @SuppressWarnings("SameParameterValue") + private ObjectType resolveReference(ObjectReferenceType objectRef, Class defaultObjectTypeClass, + OperationResult result) throws SchemaException, ObjectNotFoundException { + final Class objectTypeClass; + if (objectRef.getType() != null) { + //noinspection unchecked + objectTypeClass = (Class) prismContext.getSchemaRegistry().getCompileTimeClassForObjectType(objectRef.getType()); + if (objectTypeClass == null) { + throw new SchemaException("No object class found for " + objectRef.getType()); + } + } else { + objectTypeClass = defaultObjectTypeClass; + } + PrismObject object = repositoryService.getObject(objectTypeClass, objectRef.getOid(), null, result); + return object.asObjectable(); + } +} diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelPublicConstants.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelPublicConstants.java index 02c4e1a4de8..1febb823c1a 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelPublicConstants.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelPublicConstants.java @@ -43,4 +43,8 @@ public class ModelPublicConstants { public static final String EXECUTE_CHANGES_TASK_HANDLER_URI = NS_SYNCHRONIZATION_TASK_PREFIX + "/execute/handler-3"; public static final String DELETE_NOT_UPDATE_SHADOW_TASK_HANDLER_URI = NS_SYNCHRONIZATION_TASK_PREFIX + "/delete-not-updated-shadow/handler-3"; public static final String RECOMPUTE_HANDLER_URI = NS_SYNCHRONIZATION_TASK_PREFIX + "/recompute/handler-3"; + + //not sure if this is correct place + public static final String CLUSTER_REPORT_FILE_PATH = "/reportFiles"; + public static final String CLUSTER_REPORT_FILE_FILENAME_PARAMETER = "filename"; } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java index 570bbe17785..ef60f60b09e 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/EvaluatedAssignment.java @@ -1,104 +1,111 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.api.context; - -import java.util.Collection; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.security.api.Authorization; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DebugDumpable; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AdminGuiConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import org.jetbrains.annotations.NotNull; - -public interface EvaluatedAssignment extends DebugDumpable { - - AssignmentType getAssignmentType(); - - Long getAssignmentId(); - - Collection getAuthorizations(); - - Collection getAdminGuiConfigurations(); - - DeltaSetTriple getRoles(); - - DeltaSetTriple getEvaluatedConstructions(Task task, OperationResult result) throws SchemaException, ObjectNotFoundException; - - PrismObject getTarget(); - - AssignmentType getAssignmentType(boolean old); - - // return value of null is ambiguous: either targetRef is null or targetRef.relation is null - QName getRelation(); - - QName getNormalizedRelation(RelationRegistry relationRegistry); - - boolean isValid(); - - boolean isPresentInCurrentObject(); - - boolean isPresentInOldObject(); - - /** - * Returns all policy rules that apply to the focal object and are derived from this assignment - * - even those that were not triggered. The policy rules are compiled from all the applicable - * sources (target, meta-roles, etc.) - */ - @NotNull - Collection getFocusPolicyRules(); - - /** - * Returns all policy rules that directly apply to the target object of this assignment - * (and are derived from this assignment) - even those that were not triggered. The policy rules - * are compiled from all the applicable sources (target, meta-roles, etc.) - */ - @NotNull - Collection getThisTargetPolicyRules(); - - /** - * Returns all policy rules that apply to some other target object of this assignment - * (and are derived from this assignment) - even those that were not triggered. The policy rules - * are compiled from all the applicable sources (target, meta-roles, etc.) - */ - @NotNull - Collection getOtherTargetsPolicyRules(); - - /** - * Returns all policy rules that apply to any of the target objects provided by this assignment - * (and are derived from this assignment) - even those that were not triggered. The policy rules - * are compiled from all the applicable sources (target, meta-roles, etc.) - * - * The difference to getThisTargetPolicyRules is that if e.g. - * jack is a Pirate, and Pirate induces Sailor, then - * - getThisTargetPolicyRules will show rules that are attached to Pirate - * - getAllTargetsPolicyRules will show rules that are attached to Pirate and Sailor - * - getOtherTargetsPolicyRules will show rules that are attached to Sailor - */ - @NotNull - Collection getAllTargetsPolicyRules(); - - /** - * How many target policy rules are there. This is more efficient than getAllTargetsPolicyRules().size(), as the - * collection of all targets policy rules is computed on demand. - */ - int getAllTargetsPolicyRulesCount(); - - Collection getPolicySituations(); - - void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers); - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.api.context; + +import java.util.Collection; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.api.Authorization; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AdminGuiConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentHolderType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; +import org.jetbrains.annotations.NotNull; + +public interface EvaluatedAssignment extends DebugDumpable { + + AssignmentType getAssignmentType(); + + Long getAssignmentId(); + + Collection getAuthorizations(); + + Collection getAdminGuiConfigurations(); + + DeltaSetTriple getRoles(); + + DeltaSetTriple getEvaluatedConstructions(Task task, OperationResult result) throws SchemaException, ObjectNotFoundException; + + PrismObject getTarget(); + + AssignmentType getAssignmentType(boolean old); + + // return value of null is ambiguous: either targetRef is null or targetRef.relation is null + QName getRelation(); + + QName getNormalizedRelation(RelationRegistry relationRegistry); + + boolean isValid(); + + boolean isPresentInCurrentObject(); + + boolean isPresentInOldObject(); + + /** + * Returns all policy rules that apply to the focal object and are derived from this assignment + * - even those that were not triggered. The policy rules are compiled from all the applicable + * sources (target, meta-roles, etc.) + */ + @NotNull + Collection getFocusPolicyRules(); + + /** + * Returns all policy rules that directly apply to the target object of this assignment + * (and are derived from this assignment) - even those that were not triggered. The policy rules + * are compiled from all the applicable sources (target, meta-roles, etc.) + */ + @NotNull + Collection getThisTargetPolicyRules(); + + /** + * Returns all policy rules that apply to some other target object of this assignment + * (and are derived from this assignment) - even those that were not triggered. The policy rules + * are compiled from all the applicable sources (target, meta-roles, etc.) + */ + @NotNull + Collection getOtherTargetsPolicyRules(); + + /** + * Returns all policy rules that apply to any of the target objects provided by this assignment + * (and are derived from this assignment) - even those that were not triggered. The policy rules + * are compiled from all the applicable sources (target, meta-roles, etc.) + * + * The difference to getThisTargetPolicyRules is that if e.g. + * jack is a Pirate, and Pirate induces Sailor, then + * - getThisTargetPolicyRules will show rules that are attached to Pirate + * - getAllTargetsPolicyRules will show rules that are attached to Pirate and Sailor + * - getOtherTargetsPolicyRules will show rules that are attached to Sailor + */ + @NotNull + Collection getAllTargetsPolicyRules(); + + /** + * How many target policy rules are there. This is more efficient than getAllTargetsPolicyRules().size(), as the + * collection of all targets policy rules is computed on demand. + */ + int getAllTargetsPolicyRulesCount(); + + Collection getPolicySituations(); + + void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers); + + + /** + * These are evaluated focus mappings. Since 4.0.1 the evaluation is carried out not during assignment evaluation + * but afterwards. + */ + Collection> getFocusMappings(); + +} diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/Mapping.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/Mapping.java index 67df494bc64..1a7acaea3fc 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/Mapping.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/Mapping.java @@ -1,26 +1,33 @@ -/* - * Copyright (c) 2018-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.api.context; - -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismValue; - -/** - * @author semancik - * - */ -public interface Mapping { - - /** - * Returns elapsed time in milliseconds. - */ - Long getEtime(); - - T getStateProperty(String propertyName); - - T setStateProperty(String propertyName, T value); -} +/* + * Copyright (c) 2018-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.api.context; + +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.util.exception.SchemaException; + +/** + * @author semancik + * + */ +public interface Mapping { + + /** + * Returns elapsed time in milliseconds. + */ + Long getEtime(); + + T getStateProperty(String propertyName); + + T setStateProperty(String propertyName, T value); + + PrismValueDeltaSetTriple getOutputTriple(); + + ItemPath getOutputPath() throws SchemaException; +} diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelContext.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelContext.java index 5ee3f61cea7..0df87bf189c 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelContext.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelContext.java @@ -1,93 +1,99 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.api.context; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.ProgressInformation; -import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.schema.ObjectTreeDeltas; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.util.DebugDumpable; -import com.evolveum.midpoint.util.exception.SchemaException; -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.PolicyRuleEnforcerPreviewOutputType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * @author semancik - * - */ -public interface ModelContext extends Serializable, DebugDumpable { - - String getRequestIdentifier(); - - ModelState getState(); - - ModelElementContext getFocusContext(); - - Collection getProjectionContexts(); - - ModelProjectionContext findProjectionContext(ResourceShadowDiscriminator rat); - - ModelExecuteOptions getOptions(); - - @NotNull - PartialProcessingOptionsType getPartialProcessingOptions(); - - Class getFocusClass(); - - void reportProgress(ProgressInformation progress); - - DeltaSetTriple> getEvaluatedAssignmentTriple(); - - PrismContext getPrismContext(); // use with care - - PrismObject getSystemConfiguration(); // beware, may be null - use only as a performance optimization - - String getChannel(); - - Collection> getAllChanges() throws SchemaException; - - // For diagnostic purposes (this is more detailed than rule-related part of LensContext debugDump, - // while less detailed than that part of detailed LensContext debugDump). - default String dumpAssignmentPolicyRules(int indent) { - return dumpAssignmentPolicyRules(indent, false); - } - - String dumpAssignmentPolicyRules(int indent, boolean alsoMessages); - - default String dumpFocusPolicyRules(int indent) { - return dumpFocusPolicyRules(indent, false); - } - - String dumpFocusPolicyRules(int indent, boolean alsoMessages); - - Map> getHookPreviewResultsMap(); - - @NotNull - List getHookPreviewResults(@NotNull Class clazz); - - @Nullable - PolicyRuleEnforcerPreviewOutputType getPolicyRuleEnforcerPreviewOutput(); - - boolean isPreview(); - - @NotNull - ObjectTreeDeltas getTreeDeltas(); -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.api.context; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.ProgressInformation; +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.ObjectTreeDeltas; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.exception.SchemaException; +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.PolicyRuleEnforcerPreviewOutputType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author semancik + * + */ +public interface ModelContext extends Serializable, DebugDumpable { + + String getRequestIdentifier(); + + ModelState getState(); + + ModelElementContext getFocusContext(); + + Collection getProjectionContexts(); + + ModelProjectionContext findProjectionContext(ResourceShadowDiscriminator rat); + + ModelExecuteOptions getOptions(); + + @NotNull + PartialProcessingOptionsType getPartialProcessingOptions(); + + Class getFocusClass(); + + void reportProgress(ProgressInformation progress); + + DeltaSetTriple> getEvaluatedAssignmentTriple(); + + PrismContext getPrismContext(); // use with care + + PrismObject getSystemConfiguration(); // beware, may be null - use only as a performance optimization + + String getChannel(); + + Collection> getAllChanges() throws SchemaException; + + // For diagnostic purposes (this is more detailed than rule-related part of LensContext debugDump, + // while less detailed than that part of detailed LensContext debugDump). + default String dumpAssignmentPolicyRules(int indent) { + return dumpAssignmentPolicyRules(indent, false); + } + + String dumpAssignmentPolicyRules(int indent, boolean alsoMessages); + + default String dumpFocusPolicyRules(int indent) { + return dumpFocusPolicyRules(indent, false); + } + + String dumpFocusPolicyRules(int indent, boolean alsoMessages); + + Map> getHookPreviewResultsMap(); + + @NotNull + List getHookPreviewResults(@NotNull Class clazz); + + @Nullable + PolicyRuleEnforcerPreviewOutputType getPolicyRuleEnforcerPreviewOutput(); + + boolean isPreview(); + + @NotNull + ObjectTreeDeltas getTreeDeltas(); + + Collection getHistoricResourceObjects(); + + Long getSequenceCounter(String sequenceOid); + + void setSequenceCounter(String sequenceOid, long counter); +} diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelElementContext.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelElementContext.java index 7029b38fec2..5fa6ec556d8 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelElementContext.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelElementContext.java @@ -1,63 +1,83 @@ -/* - * Copyright (c) 2010-2018 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.api.context; - -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.schema.ObjectDeltaOperation; -import com.evolveum.midpoint.util.DebugDumpable; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ArchetypeType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import org.jetbrains.annotations.NotNull; - -import java.io.Serializable; -import java.util.Collection; -import java.util.List; - -/** - * @author semancik - * - */ -public interface ModelElementContext extends Serializable, DebugDumpable { - - Class getObjectTypeClass(); - - PrismObject getObjectOld(); - - void setObjectOld(PrismObject objectOld); - - PrismObject getObjectNew(); - - PrismObject getObjectCurrent(); - - void setObjectNew(PrismObject objectNew); - - ObjectDelta getPrimaryDelta(); - - void setPrimaryDelta(ObjectDelta primaryDelta); - - void addPrimaryDelta(ObjectDelta value) throws SchemaException; - - ObjectDelta getSecondaryDelta(); - - void setSecondaryDelta(ObjectDelta secondaryDelta); - - List getExecutedDeltas(); - - String getOid(); - - /** - * Returns all policy rules that apply to this object - even those that were not triggered. - * The policy rules are compiled from all the applicable sources (target, meta-roles, etc.) - */ - @NotNull - Collection getPolicyRules(); - - boolean isOfType(Class aClass); - -} +/* + * Copyright (c) 2010-2018 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.api.context; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ArchetypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SynchronizationIntentType; + +import org.jetbrains.annotations.NotNull; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; + +/** + * @author semancik + * + */ +public interface ModelElementContext extends Serializable, DebugDumpable { + + Class getObjectTypeClass(); + + PrismObject getObjectOld(); + + void setObjectOld(PrismObject objectOld); + + PrismObject getObjectNew(); + + PrismObject getObjectCurrent(); + + PrismObject getObjectAny(); + + void setObjectNew(PrismObject objectNew); + + ObjectDelta getPrimaryDelta(); + + void setPrimaryDelta(ObjectDelta primaryDelta); + + void addPrimaryDelta(ObjectDelta value) throws SchemaException; + + ObjectDelta getSecondaryDelta(); + + void setSecondaryDelta(ObjectDelta secondaryDelta); + + List getExecutedDeltas(); + + String getOid(); + + /** + * Returns all policy rules that apply to this object - even those that were not triggered. + * The policy rules are compiled from all the applicable sources (target, meta-roles, etc.) + */ + @NotNull + Collection getPolicyRules(); + + boolean isOfType(Class aClass); + + /** + * Initial intent regarding the account. It indicated what the initiator of the operation WANTS TO DO with the + * context. + * If set to null then the decision is left to "the engine". Null is also a typical value + * when the context is created. It may be pre-set under some circumstances, e.g. if an account is being unlinked. + */ + SynchronizationIntent getSynchronizationIntent(); + + boolean isAdd(); + + boolean isDelete(); + + ObjectDelta getDelta() throws SchemaException; + + ArchetypeType getArchetype(); + +} diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelProjectionContext.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelProjectionContext.java index ddd16b95979..53c54512f6a 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelProjectionContext.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/ModelProjectionContext.java @@ -1,49 +1,51 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.api.context; - -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; - -/** - * @author semancik - * - */ -public interface ModelProjectionContext extends ModelElementContext { - - /** - * Returns synchronization delta. - * - * Synchronization delta describes changes that have recently happened. MidPoint reacts to these - * changes by "pulling them in" (e.g. using them in inbound mappings). - */ - ObjectDelta getSyncDelta(); - - void setSyncDelta(ObjectDelta syncDelta); - - ResourceShadowDiscriminator getResourceShadowDiscriminator(); - - /** - * Decision regarding the account. It describes the overall situation of the account e.g. whether account - * is added, is to be deleted, unliked, etc. - * - * If set to null no decision was made yet. Null is also a typical value when the context is created. - * - * @see SynchronizationPolicyDecision - */ - SynchronizationPolicyDecision getSynchronizationPolicyDecision(); - - ObjectDelta getExecutableDelta() throws SchemaException; - - boolean isFullShadow(); - - Boolean isLegal(); - - boolean isExists(); -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.api.context; + +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; + +/** + * @author semancik + * + */ +public interface ModelProjectionContext extends ModelElementContext { + + /** + * Returns synchronization delta. + * + * Synchronization delta describes changes that have recently happened. MidPoint reacts to these + * changes by "pulling them in" (e.g. using them in inbound mappings). + */ + ObjectDelta getSyncDelta(); + + void setSyncDelta(ObjectDelta syncDelta); + + ResourceShadowDiscriminator getResourceShadowDiscriminator(); + + /** + * Decision regarding the account. It describes the overall situation of the account e.g. whether account + * is added, is to be deleted, unliked, etc. + * + * If set to null no decision was made yet. Null is also a typical value when the context is created. + * + * @see SynchronizationPolicyDecision + */ + SynchronizationPolicyDecision getSynchronizationPolicyDecision(); + + ObjectDelta getExecutableDelta() throws SchemaException; + + boolean isFullShadow(); + + Boolean isLegal(); + + boolean isExists(); + + boolean isTombstone(); +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/SynchronizationIntent.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/SynchronizationIntent.java similarity index 94% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/SynchronizationIntent.java rename to model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/SynchronizationIntent.java index 32e0d32b425..6dadc26d18e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/SynchronizationIntent.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/context/SynchronizationIntent.java @@ -1,87 +1,86 @@ -/* - * Copyright (c) 2010-2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SynchronizationIntentType; - -/** - * @author semancik - * - */ -public enum SynchronizationIntent { - - /** - * New account that should be added (and linked) - */ - ADD, - - /** - * Existing account that should be deleted (and unlinked) - */ - DELETE, - - /** - * Existing account that is kept as it is (remains linked). - */ - KEEP, - - /** - * Existing account that should be unlinked (but NOT deleted) - */ - UNLINK, - - /** - * Existing account that belongs to the user and needs to be synchronized. - * This may include deleting, archiving or disabling the account. - */ - SYNCHRONIZE; - - public SynchronizationPolicyDecision toSynchronizationPolicyDecision() { - if (this == ADD) { - return SynchronizationPolicyDecision.ADD; - } - if (this == DELETE) { - return SynchronizationPolicyDecision.DELETE; - } - if (this == KEEP) { - return SynchronizationPolicyDecision.KEEP; - } - if (this == UNLINK) { - return SynchronizationPolicyDecision.UNLINK; - } - if (this == SYNCHRONIZE) { - return null; - } - throw new IllegalStateException("Unexpected value "+this); - } - - public SynchronizationIntentType toSynchronizationIntentType() { - switch(this) { - case ADD: return SynchronizationIntentType.ADD; - case DELETE: return SynchronizationIntentType.DELETE; - case KEEP: return SynchronizationIntentType.KEEP; - case UNLINK: return SynchronizationIntentType.UNLINK; - case SYNCHRONIZE: return SynchronizationIntentType.SYNCHRONIZE; - default: throw new AssertionError("Unknown value of SynchronizationIntent: " + this); - } - } - - public static SynchronizationIntent fromSynchronizationIntentType(SynchronizationIntentType value) { - if (value == null) { - return null; - } - switch (value) { - case ADD: return ADD; - case DELETE: return DELETE; - case KEEP: return KEEP; - case UNLINK: return UNLINK; - case SYNCHRONIZE: return SYNCHRONIZE; - default: throw new AssertionError("Unknown value of SynchronizationIntentType: " + value); - } - } -} +/* + * Copyright (c) 2010-2013 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.api.context; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.SynchronizationIntentType; + +/** + * @author semancik + * + */ +public enum SynchronizationIntent { + + /** + * New account that should be added (and linked) + */ + ADD, + + /** + * Existing account that should be deleted (and unlinked) + */ + DELETE, + + /** + * Existing account that is kept as it is (remains linked). + */ + KEEP, + + /** + * Existing account that should be unlinked (but NOT deleted) + */ + UNLINK, + + /** + * Existing account that belongs to the user and needs to be synchronized. + * This may include deleting, archiving or disabling the account. + */ + SYNCHRONIZE; + + public SynchronizationPolicyDecision toSynchronizationPolicyDecision() { + if (this == ADD) { + return SynchronizationPolicyDecision.ADD; + } + if (this == DELETE) { + return SynchronizationPolicyDecision.DELETE; + } + if (this == KEEP) { + return SynchronizationPolicyDecision.KEEP; + } + if (this == UNLINK) { + return SynchronizationPolicyDecision.UNLINK; + } + if (this == SYNCHRONIZE) { + return null; + } + throw new IllegalStateException("Unexpected value "+this); + } + + public SynchronizationIntentType toSynchronizationIntentType() { + switch(this) { + case ADD: return SynchronizationIntentType.ADD; + case DELETE: return SynchronizationIntentType.DELETE; + case KEEP: return SynchronizationIntentType.KEEP; + case UNLINK: return SynchronizationIntentType.UNLINK; + case SYNCHRONIZE: return SynchronizationIntentType.SYNCHRONIZE; + default: throw new AssertionError("Unknown value of SynchronizationIntent: " + this); + } + } + + public static SynchronizationIntent fromSynchronizationIntentType(SynchronizationIntentType value) { + if (value == null) { + return null; + } + switch (value) { + case ADD: return ADD; + case DELETE: return DELETE; + case KEEP: return KEEP; + case UNLINK: return UNLINK; + case SYNCHRONIZE: return SYNCHRONIZE; + default: throw new AssertionError("Unknown value of SynchronizationIntentType: " + value); + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ExpressionEnvironment.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionEnvironment.java similarity index 74% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ExpressionEnvironment.java rename to model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionEnvironment.java index 4c1338cf2ed..606b5468a93 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ExpressionEnvironment.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ExpressionEnvironment.java @@ -1,102 +1,102 @@ -/* - * Copyright (c) 2017-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.expr; - -import com.evolveum.midpoint.model.api.context.Mapping; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismValue; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; - -/** - * @author semancik - * - */ -public class ExpressionEnvironment { - - private LensContext lensContext; - private LensProjectionContext projectionContext; - private Mapping mapping; - private OperationResult currentResult; - private Task currentTask; - - public ExpressionEnvironment() { - } - - public ExpressionEnvironment(Task currentTask, OperationResult currentResult) { - this.currentResult = currentResult; - this.currentTask = currentTask; - } - - public ExpressionEnvironment(LensContext lensContext, LensProjectionContext projectionContext, - Task currentTask, OperationResult currentResult) { - this.lensContext = lensContext; - this.projectionContext = projectionContext; - this.currentResult = currentResult; - this.currentTask = currentTask; - } - - public ExpressionEnvironment(LensContext lensContext, LensProjectionContext projectionContext, Mapping mapping, - Task currentTask, OperationResult currentResult) { - this.lensContext = lensContext; - this.projectionContext = projectionContext; - this.mapping = mapping; - this.currentResult = currentResult; - this.currentTask = currentTask; - } - - public LensContext getLensContext() { - return lensContext; - } - - public void setLensContext(LensContext lensContext) { - this.lensContext = lensContext; - } - - public LensProjectionContext getProjectionContext() { - return projectionContext; - } - - public void setProjectionContext(LensProjectionContext projectionContext) { - this.projectionContext = projectionContext; - } - - public Mapping getMapping() { - return mapping; - } - - public void setMapping(Mapping mapping) { - this.mapping = mapping; - } - - public OperationResult getCurrentResult() { - return currentResult; - } - - public void setCurrentResult(OperationResult currentResult) { - this.currentResult = currentResult; - } - - public Task getCurrentTask() { - return currentTask; - } - - public void setCurrentTask(Task currentTask) { - this.currentTask = currentTask; - } - - @Override - public String toString() { - return "ExpressionEnvironment(lensContext=" + lensContext + ", projectionContext=" - + projectionContext + ", currentResult=" + currentResult + ", currentTask=" + currentTask - + ")"; - } - -} +/* + * Copyright (c) 2017-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.common.expression; + +import com.evolveum.midpoint.model.api.context.Mapping; +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.api.context.ModelProjectionContext; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * @author semancik + * + */ +public class ExpressionEnvironment { + + private ModelContext lensContext; + private ModelProjectionContext projectionContext; + private Mapping mapping; + private OperationResult currentResult; + private Task currentTask; + + public ExpressionEnvironment() { + } + + public ExpressionEnvironment(Task currentTask, OperationResult currentResult) { + this.currentResult = currentResult; + this.currentTask = currentTask; + } + + public ExpressionEnvironment(ModelContext lensContext, ModelProjectionContext projectionContext, + Task currentTask, OperationResult currentResult) { + this.lensContext = lensContext; + this.projectionContext = projectionContext; + this.currentResult = currentResult; + this.currentTask = currentTask; + } + + public ExpressionEnvironment(ModelContext lensContext, ModelProjectionContext projectionContext, Mapping mapping, + Task currentTask, OperationResult currentResult) { + this.lensContext = lensContext; + this.projectionContext = projectionContext; + this.mapping = mapping; + this.currentResult = currentResult; + this.currentTask = currentTask; + } + + public ModelContext getLensContext() { + return lensContext; + } + + public void setLensContext(ModelContext lensContext) { + this.lensContext = lensContext; + } + + public ModelProjectionContext getProjectionContext() { + return projectionContext; + } + + public void setProjectionContext(ModelProjectionContext projectionContext) { + this.projectionContext = projectionContext; + } + + public Mapping getMapping() { + return mapping; + } + + public void setMapping(Mapping mapping) { + this.mapping = mapping; + } + + public OperationResult getCurrentResult() { + return currentResult; + } + + public void setCurrentResult(OperationResult currentResult) { + this.currentResult = currentResult; + } + + public Task getCurrentTask() { + return currentTask; + } + + public void setCurrentTask(Task currentTask) { + this.currentTask = currentTask; + } + + @Override + public String toString() { + return "ExpressionEnvironment(lensContext=" + lensContext + ", projectionContext=" + + projectionContext + ", currentResult=" + currentResult + ", currentTask=" + currentTask + + ")"; + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ModelExpressionThreadLocalHolder.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ModelExpressionThreadLocalHolder.java similarity index 93% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ModelExpressionThreadLocalHolder.java rename to model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ModelExpressionThreadLocalHolder.java index f54ecfeaaab..08ebdc2a314 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ModelExpressionThreadLocalHolder.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/ModelExpressionThreadLocalHolder.java @@ -1,153 +1,153 @@ -/* - * Copyright (c) 2013-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.expr; - -import java.util.ArrayDeque; -import java.util.Deque; - -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.model.api.context.Mapping; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.PrismReferenceDefinition; -import com.evolveum.midpoint.prism.PrismReferenceValue; -import com.evolveum.midpoint.prism.PrismValue; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -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.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; - -/** - * @author Radovan Semancik - * - */ -public class ModelExpressionThreadLocalHolder { - - private static ThreadLocal>> expressionEnvironmentStackTl = - new ThreadLocal<>(); - - public static void pushExpressionEnvironment(ExpressionEnvironment env) { - Deque> stack = expressionEnvironmentStackTl.get(); - if (stack == null) { - stack = new ArrayDeque<>(); - expressionEnvironmentStackTl.set(stack); - } - stack.push((ExpressionEnvironment)env); - } - - public static void popExpressionEnvironment() { - Deque> stack = expressionEnvironmentStackTl.get(); - stack.pop(); - } - - public static ExpressionEnvironment getExpressionEnvironment() { - Deque> stack = expressionEnvironmentStackTl.get(); - if (stack == null) { - return null; - } - return (ExpressionEnvironment) stack.peek(); - } - - public static LensContext getLensContext() { - ExpressionEnvironment env = getExpressionEnvironment(); - if (env == null) { - return null; - } - return (LensContext) env.getLensContext(); - } - - public static Mapping getMapping() { - ExpressionEnvironment env = getExpressionEnvironment(); - if (env == null) { - return null; - } - return (Mapping) env.getMapping(); - } - - public static LensProjectionContext getProjectionContext() { - ExpressionEnvironment env = getExpressionEnvironment(); - if (env == null) { - return null; - } - return env.getProjectionContext(); - } - - public static Task getCurrentTask() { - ExpressionEnvironment env = getExpressionEnvironment(); - if (env == null) { - return null; - } - return env.getCurrentTask(); - } - - public static OperationResult getCurrentResult() { - ExpressionEnvironment env = getExpressionEnvironment(); - if (env == null) { - return null; - } - return env.getCurrentResult(); - } - - // TODO move to better place - public static PrismValueDeltaSetTriple evaluateAnyExpressionInContext(Expression expression, - ExpressionEvaluationContext context, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - try { - return expression.evaluate(context, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - public static PrismValueDeltaSetTriple> evaluateExpressionInContext(Expression, - PrismPropertyDefinition> expression, ExpressionEvaluationContext eeContext, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - try { - return expression.evaluate(eeContext, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - public static PrismValueDeltaSetTriple evaluateRefExpressionInContext(Expression expression, ExpressionEvaluationContext eeContext, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - try { - return expression.evaluate(eeContext, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - public static PrismValueDeltaSetTriple> evaluateExpressionInContext( - Expression, PrismPropertyDefinition> expression, - ExpressionEvaluationContext eeContext, - ExpressionEnvironment env, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); - PrismValueDeltaSetTriple> exprResultTriple; - try { - exprResultTriple = expression.evaluate(eeContext, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - return exprResultTriple; - } -} +/* + * Copyright (c) 2013-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.common.expression; + +import java.util.ArrayDeque; +import java.util.Deque; + +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.api.context.ModelProjectionContext; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.model.api.context.Mapping; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.PrismReferenceDefinition; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.PrismValue; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +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.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * @author Radovan Semancik + * + */ +public class ModelExpressionThreadLocalHolder { + + private static ThreadLocal>> expressionEnvironmentStackTl = + new ThreadLocal<>(); + + public static void pushExpressionEnvironment(ExpressionEnvironment env) { + Deque> stack = expressionEnvironmentStackTl.get(); + if (stack == null) { + stack = new ArrayDeque<>(); + expressionEnvironmentStackTl.set(stack); + } + stack.push((ExpressionEnvironment)env); + } + + public static void popExpressionEnvironment() { + Deque> stack = expressionEnvironmentStackTl.get(); + stack.pop(); + } + + public static ExpressionEnvironment getExpressionEnvironment() { + Deque> stack = expressionEnvironmentStackTl.get(); + if (stack == null) { + return null; + } + return (ExpressionEnvironment) stack.peek(); + } + + public static ModelContext getLensContext() { + ExpressionEnvironment env = getExpressionEnvironment(); + if (env == null) { + return null; + } + return (ModelContext) env.getLensContext(); + } + + public static Mapping getMapping() { + ExpressionEnvironment env = getExpressionEnvironment(); + if (env == null) { + return null; + } + return (Mapping) env.getMapping(); + } + + public static ModelProjectionContext getProjectionContext() { + ExpressionEnvironment env = getExpressionEnvironment(); + if (env == null) { + return null; + } + return env.getProjectionContext(); + } + + public static Task getCurrentTask() { + ExpressionEnvironment env = getExpressionEnvironment(); + if (env == null) { + return null; + } + return env.getCurrentTask(); + } + + public static OperationResult getCurrentResult() { + ExpressionEnvironment env = getExpressionEnvironment(); + if (env == null) { + return null; + } + return env.getCurrentResult(); + } + + // TODO move to better place + public static PrismValueDeltaSetTriple evaluateAnyExpressionInContext(Expression expression, + ExpressionEvaluationContext context, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + try { + return expression.evaluate(context, result); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + public static PrismValueDeltaSetTriple> evaluateExpressionInContext(Expression, + PrismPropertyDefinition> expression, ExpressionEvaluationContext eeContext, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + try { + return expression.evaluate(eeContext, result); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + public static PrismValueDeltaSetTriple evaluateRefExpressionInContext(Expression expression, ExpressionEvaluationContext eeContext, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + try { + return expression.evaluate(eeContext, result); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + public static PrismValueDeltaSetTriple> evaluateExpressionInContext( + Expression, PrismPropertyDefinition> expression, + ExpressionEvaluationContext eeContext, + ExpressionEnvironment env, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); + PrismValueDeltaSetTriple> exprResultTriple; + try { + exprResultTriple = expression.evaluate(eeContext, result); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + return exprResultTriple; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java index 73589331a91..3ebb5bab4b9 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/ClusterRestService.java @@ -1,354 +1,352 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl; - -import com.evolveum.midpoint.CacheInvalidationContext; -import com.evolveum.midpoint.TerminateSessionEvent; -import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; -import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipalManager; -import com.evolveum.midpoint.model.impl.security.NodeAuthenticationToken; -import com.evolveum.midpoint.model.impl.security.SecurityHelper; -import com.evolveum.midpoint.model.impl.util.RestServiceUtil; -import com.evolveum.midpoint.repo.api.CacheDispatcher; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskConstants; -import com.evolveum.midpoint.task.api.TaskManager; -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.api_types_3.TerminateSessionEventType; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementListType; -import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SchedulerInformationType; -import org.apache.commons.io.IOUtils; -import org.apache.cxf.jaxrs.ext.MessageContext; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Service; - -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.StreamingOutput; -import java.io.File; -import java.io.FileInputStream; -import java.nio.file.Paths; -import java.util.List; - -/** - * REST service used for inter-cluster communication. - * - * These methods are NOT to be called generally by clients. - * They are to be called internally by midPoint running on other cluster nodes. - * - * So the usual form of authentication will be CLUSTER (a.k.a. node authentication). - * However, for diagnostic purposes we might allow also administrator access sometimes in the future. - */ -@Service -@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) -public class ClusterRestService { - - public static final String CLASS_DOT = ClusterRestService.class.getName() + "."; - - private static final String OPERATION_EXECUTE_CLUSTER_CACHE_INVALIDATION_EVENT = CLASS_DOT + "executeClusterCacheInvalidationEvent"; - private static final String OPERATION_EXECUTE_CLUSTER_TERMINATE_SESSION_EVENT = CLASS_DOT + "executeClusterTerminateSessionEvent"; - private static final String OPERATION_GET_LOCAL_SCHEDULER_INFORMATION = CLASS_DOT + "getLocalSchedulerInformation"; - private static final String OPERATION_STOP_LOCAL_SCHEDULER = CLASS_DOT + "stopLocalScheduler"; - private static final String OPERATION_START_LOCAL_SCHEDULER = CLASS_DOT + "startLocalScheduler"; - private static final String OPERATION_STOP_LOCAL_TASK = CLASS_DOT + "stopLocalTask"; - - private static final String OPERATION_GET_REPORT_FILE = CLASS_DOT + "getReportFile"; - private static final String OPERATION_DELETE_REPORT_FILE = CLASS_DOT + "deleteReportFile"; - - private static final String EXPORT_DIR = "export/"; - - public static final String EVENT_INVALIDATION = "/event/invalidation/"; - public static final String EVENT_TERMINATE_SESSION = "/event/terminateSession/"; - public static final String EVENT_LIST_USER_SESSION = "/event/listUserSession"; - - @Autowired private SecurityHelper securityHelper; - @Autowired private TaskManager taskManager; - @Autowired private MidpointConfiguration midpointConfiguration; - @Autowired private GuiProfiledPrincipalManager focusProfileService; - - @Autowired private CacheDispatcher cacheDispatcher; - - private static final Trace LOGGER = TraceManager.getTrace(ClusterRestService.class); - - public ClusterRestService() { - // nothing to do - } - - @POST - @Path(EVENT_INVALIDATION + "{type}") - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response executeClusterCacheInvalidationEvent(@PathParam("type") String type, @Context MessageContext mc) { - return executeClusterCacheInvalidationEvent(type, null, mc); - } - - @POST - @Path(EVENT_INVALIDATION + "{type}/{oid}") - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response executeClusterCacheInvalidationEvent(@PathParam("type") String type, @PathParam("oid") String oid, @Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_EXECUTE_CLUSTER_CACHE_INVALIDATION_EVENT); - - Response response; - try { - checkNodeAuthentication(); - - Class clazz = type != null ? ObjectTypes.getClassFromRestType(type) : null; - - // clusterwide is false: we got this from another node so we don't need to redistribute it - cacheDispatcher.dispatchInvalidation(clazz, oid, false, new CacheInvalidationContext(true, null)); - - result.recordSuccess(); - response = RestServiceUtil.createResponse(Status.OK, result); - } catch (Throwable t) { - response = RestServiceUtil.handleException(result, t); - } - finishRequest(task); - return response; - } - - @POST - @Path(EVENT_TERMINATE_SESSION) - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response executeClusterTerminateSessionEvent(TerminateSessionEventType event, @Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_EXECUTE_CLUSTER_TERMINATE_SESSION_EVENT); - - Response response; - try { - checkNodeAuthentication(); - - focusProfileService.terminateLocalSessions(TerminateSessionEvent.fromEventType(event)); - - result.recordSuccess(); - response = RestServiceUtil.createResponse(Status.OK, result); - } catch (Throwable t) { - response = RestServiceUtil.handleException(result, t); - } - finishRequest(task); - return response; - } - - @GET - @Path(EVENT_LIST_USER_SESSION) - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response listUserSession(@Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_GET_LOCAL_SCHEDULER_INFORMATION); - - Response response; - try { - checkNodeAuthentication(); - List principals = focusProfileService.getLocalLoggedInPrincipals(); - - UserSessionManagementListType list = new UserSessionManagementListType(); - list.getSession().addAll(principals); - - response = RestServiceUtil.createResponse(Status.OK, list, result); - } catch (Throwable t) { - response = RestServiceUtil.handleException(result, t); - } - result.computeStatus(); - finishRequest(task); - return response; - } - - @GET - @Path(TaskConstants.GET_LOCAL_SCHEDULER_INFORMATION_REST_PATH) - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response getLocalSchedulerInformation(@Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_GET_LOCAL_SCHEDULER_INFORMATION); - - Response response; - try { - checkNodeAuthentication(); - SchedulerInformationType schedulerInformation = taskManager.getLocalSchedulerInformation(result); - response = RestServiceUtil.createResponse(Status.OK, schedulerInformation, result); - } catch (Throwable t) { - response = RestServiceUtil.handleException(result, t); - } - result.computeStatus(); - finishRequest(task); - return response; - } - - @POST - @Path(TaskConstants.STOP_LOCAL_SCHEDULER_REST_PATH) - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response stopLocalScheduler(@Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_STOP_LOCAL_SCHEDULER); - - Response response; - try { - checkNodeAuthentication(); - taskManager.stopLocalScheduler(result); - response = RestServiceUtil.createResponse(Status.OK, result); - } catch (Throwable t) { - response = RestServiceUtil.handleException(result, t); - } - result.computeStatus(); - finishRequest(task); - return response; - } - - @POST - @Path(TaskConstants.START_LOCAL_SCHEDULER_REST_PATH) - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response startLocalScheduler(@Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_START_LOCAL_SCHEDULER); - - Response response; - try { - checkNodeAuthentication(); - taskManager.startLocalScheduler(result); - response = RestServiceUtil.createResponse(Status.OK, result); - } catch (Throwable t) { - response = RestServiceUtil.handleException(result, t); - } - result.computeStatus(); - finishRequest(task); - return response; - } - - @POST - @Path(TaskConstants.STOP_LOCAL_TASK_REST_PATH_PREFIX + "{oid}" + TaskConstants.STOP_LOCAL_TASK_REST_PATH_SUFFIX) - @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) - public Response stopLocalTask(@PathParam("oid") String oid, @Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_STOP_LOCAL_TASK); - - Response response; - try { - checkNodeAuthentication(); - taskManager.stopLocalTask(oid, result); - response = RestServiceUtil.createResponse(Status.OK, result); - } catch (Throwable t) { - response = RestServiceUtil.handleException(result, t); - } - result.computeStatus(); - finishRequest(task); - return response; - } - - public static final String REPORT_FILE_PATH = "/reportFiles"; - public static final String REPORT_FILE_FILENAME_PARAMETER = "filename"; - - @GET - @Path(REPORT_FILE_PATH) - @Produces("application/octet-stream") - public Response getReportFile(@QueryParam(REPORT_FILE_FILENAME_PARAMETER) String fileName, @Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_GET_REPORT_FILE); - - Response response; - try { - checkNodeAuthentication(); - FileResolution resolution = resolveFile(fileName); - if (resolution.status == null) { - StreamingOutput streaming = outputStream -> { - try (FileInputStream fileInputStream = new FileInputStream(resolution.file)) { - IOUtils.copy(fileInputStream, outputStream); - } - }; - response = Response.ok(streaming).build(); // we are only interested in the content, not in its type nor length - } else { - response = Response.status(resolution.status).build(); - } - result.computeStatus(); - } catch (Throwable t) { - response = RestServiceUtil.handleException(null, t); // we don't return the operation result - } - finishRequest(task); - return response; - } - - @DELETE - @Path(REPORT_FILE_PATH) - public Response deleteReportFile(@QueryParam(REPORT_FILE_FILENAME_PARAMETER) String fileName, @Context MessageContext mc) { - Task task = RestServiceUtil.initRequest(mc); - OperationResult result = new OperationResult(OPERATION_DELETE_REPORT_FILE); - - Response response; - try { - checkNodeAuthentication(); - FileResolution resolution = resolveFile(fileName); - if (resolution.status == null) { - if (!resolution.file.delete()) { - LOGGER.warn("Couldn't delete report output file {}", resolution.file); - } - response = Response.ok().build(); - } else { - response = Response.status(resolution.status).build(); - } - result.computeStatus(); - } catch (Throwable t) { - response = RestServiceUtil.handleException(null, t); // we don't return the operation result - } - finishRequest(task); - return response; - } - - static class FileResolution { - File file; - Status status; - } - - private FileResolution resolveFile(String fileName) { - FileResolution rv = new FileResolution(); - rv.file = Paths.get(midpointConfiguration.getMidpointHome(), EXPORT_DIR, fileName).toFile(); - - if (forbiddenFileName(fileName)) { - LOGGER.warn("File name '{}' is forbidden", fileName); - rv.status = Status.FORBIDDEN; - } else if (!rv.file.exists()) { - LOGGER.warn("Report output file '{}' does not exist", rv.file); - rv.status = Status.NOT_FOUND; - } else if (rv.file.isDirectory()) { - LOGGER.warn("Report output file '{}' is a directory", rv.file); - rv.status = Status.FORBIDDEN; - } - return rv; - } - - private void finishRequest(Task task) { - RestServiceUtil.finishRequest(task, securityHelper); - } - - private boolean forbiddenFileName(String fileName) { - return fileName.contains("/../"); - } - - private void checkNodeAuthentication() throws SecurityViolationException { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (!(authentication instanceof NodeAuthenticationToken)) { - throw new SecurityViolationException("Node authentication is expected but not present"); - } - // TODO consider allowing administrator access here as well - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl; + +import com.evolveum.midpoint.CacheInvalidationContext; +import com.evolveum.midpoint.TerminateSessionEvent; +import com.evolveum.midpoint.common.configuration.api.MidpointConfiguration; +import com.evolveum.midpoint.model.api.ModelPublicConstants; +import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipalManager; +import com.evolveum.midpoint.model.impl.security.NodeAuthenticationToken; +import com.evolveum.midpoint.model.impl.security.SecurityHelper; +import com.evolveum.midpoint.model.impl.util.RestServiceUtil; +import com.evolveum.midpoint.repo.api.CacheDispatcher; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskConstants; +import com.evolveum.midpoint.task.api.TaskManager; +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.api_types_3.TerminateSessionEventType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementListType; +import com.evolveum.midpoint.xml.ns._public.common.api_types_3.UserSessionManagementType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SchedulerInformationType; +import org.apache.commons.io.IOUtils; +import org.apache.cxf.jaxrs.ext.MessageContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.StreamingOutput; +import java.io.File; +import java.io.FileInputStream; +import java.nio.file.Paths; +import java.util.List; + +/** + * REST service used for inter-cluster communication. + * + * These methods are NOT to be called generally by clients. + * They are to be called internally by midPoint running on other cluster nodes. + * + * So the usual form of authentication will be CLUSTER (a.k.a. node authentication). + * However, for diagnostic purposes we might allow also administrator access sometimes in the future. + */ +@Service +@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) +public class ClusterRestService { + + public static final String CLASS_DOT = ClusterRestService.class.getName() + "."; + + private static final String OPERATION_EXECUTE_CLUSTER_CACHE_INVALIDATION_EVENT = CLASS_DOT + "executeClusterCacheInvalidationEvent"; + private static final String OPERATION_EXECUTE_CLUSTER_TERMINATE_SESSION_EVENT = CLASS_DOT + "executeClusterTerminateSessionEvent"; + private static final String OPERATION_GET_LOCAL_SCHEDULER_INFORMATION = CLASS_DOT + "getLocalSchedulerInformation"; + private static final String OPERATION_STOP_LOCAL_SCHEDULER = CLASS_DOT + "stopLocalScheduler"; + private static final String OPERATION_START_LOCAL_SCHEDULER = CLASS_DOT + "startLocalScheduler"; + private static final String OPERATION_STOP_LOCAL_TASK = CLASS_DOT + "stopLocalTask"; + + private static final String OPERATION_GET_REPORT_FILE = CLASS_DOT + "getReportFile"; + private static final String OPERATION_DELETE_REPORT_FILE = CLASS_DOT + "deleteReportFile"; + + private static final String EXPORT_DIR = "export/"; + + public static final String EVENT_INVALIDATION = "/event/invalidation/"; + public static final String EVENT_TERMINATE_SESSION = "/event/terminateSession/"; + public static final String EVENT_LIST_USER_SESSION = "/event/listUserSession"; + + @Autowired private SecurityHelper securityHelper; + @Autowired private TaskManager taskManager; + @Autowired private MidpointConfiguration midpointConfiguration; + @Autowired private GuiProfiledPrincipalManager focusProfileService; + + @Autowired private CacheDispatcher cacheDispatcher; + + private static final Trace LOGGER = TraceManager.getTrace(ClusterRestService.class); + + public ClusterRestService() { + // nothing to do + } + + @POST + @Path(EVENT_INVALIDATION + "{type}") + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response executeClusterCacheInvalidationEvent(@PathParam("type") String type, @Context MessageContext mc) { + return executeClusterCacheInvalidationEvent(type, null, mc); + } + + @POST + @Path(EVENT_INVALIDATION + "{type}/{oid}") + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response executeClusterCacheInvalidationEvent(@PathParam("type") String type, @PathParam("oid") String oid, @Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_EXECUTE_CLUSTER_CACHE_INVALIDATION_EVENT); + + Response response; + try { + checkNodeAuthentication(); + + Class clazz = type != null ? ObjectTypes.getClassFromRestType(type) : null; + + // clusterwide is false: we got this from another node so we don't need to redistribute it + cacheDispatcher.dispatchInvalidation(clazz, oid, false, new CacheInvalidationContext(true, null)); + + result.recordSuccess(); + response = RestServiceUtil.createResponse(Status.OK, result); + } catch (Throwable t) { + response = RestServiceUtil.handleException(result, t); + } + finishRequest(task); + return response; + } + + @POST + @Path(EVENT_TERMINATE_SESSION) + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response executeClusterTerminateSessionEvent(TerminateSessionEventType event, @Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_EXECUTE_CLUSTER_TERMINATE_SESSION_EVENT); + + Response response; + try { + checkNodeAuthentication(); + + focusProfileService.terminateLocalSessions(TerminateSessionEvent.fromEventType(event)); + + result.recordSuccess(); + response = RestServiceUtil.createResponse(Status.OK, result); + } catch (Throwable t) { + response = RestServiceUtil.handleException(result, t); + } + finishRequest(task); + return response; + } + + @GET + @Path(EVENT_LIST_USER_SESSION) + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response listUserSession(@Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_GET_LOCAL_SCHEDULER_INFORMATION); + + Response response; + try { + checkNodeAuthentication(); + List principals = focusProfileService.getLocalLoggedInPrincipals(); + + UserSessionManagementListType list = new UserSessionManagementListType(); + list.getSession().addAll(principals); + + response = RestServiceUtil.createResponse(Status.OK, list, result); + } catch (Throwable t) { + response = RestServiceUtil.handleException(result, t); + } + result.computeStatus(); + finishRequest(task); + return response; + } + + @GET + @Path(TaskConstants.GET_LOCAL_SCHEDULER_INFORMATION_REST_PATH) + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response getLocalSchedulerInformation(@Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_GET_LOCAL_SCHEDULER_INFORMATION); + + Response response; + try { + checkNodeAuthentication(); + SchedulerInformationType schedulerInformation = taskManager.getLocalSchedulerInformation(result); + response = RestServiceUtil.createResponse(Status.OK, schedulerInformation, result); + } catch (Throwable t) { + response = RestServiceUtil.handleException(result, t); + } + result.computeStatus(); + finishRequest(task); + return response; + } + + @POST + @Path(TaskConstants.STOP_LOCAL_SCHEDULER_REST_PATH) + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response stopLocalScheduler(@Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_STOP_LOCAL_SCHEDULER); + + Response response; + try { + checkNodeAuthentication(); + taskManager.stopLocalScheduler(result); + response = RestServiceUtil.createResponse(Status.OK, result); + } catch (Throwable t) { + response = RestServiceUtil.handleException(result, t); + } + result.computeStatus(); + finishRequest(task); + return response; + } + + @POST + @Path(TaskConstants.START_LOCAL_SCHEDULER_REST_PATH) + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response startLocalScheduler(@Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_START_LOCAL_SCHEDULER); + + Response response; + try { + checkNodeAuthentication(); + taskManager.startLocalScheduler(result); + response = RestServiceUtil.createResponse(Status.OK, result); + } catch (Throwable t) { + response = RestServiceUtil.handleException(result, t); + } + result.computeStatus(); + finishRequest(task); + return response; + } + + @POST + @Path(TaskConstants.STOP_LOCAL_TASK_REST_PATH_PREFIX + "{oid}" + TaskConstants.STOP_LOCAL_TASK_REST_PATH_SUFFIX) + @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON, RestServiceUtil.APPLICATION_YAML}) + public Response stopLocalTask(@PathParam("oid") String oid, @Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_STOP_LOCAL_TASK); + + Response response; + try { + checkNodeAuthentication(); + taskManager.stopLocalTask(oid, result); + response = RestServiceUtil.createResponse(Status.OK, result); + } catch (Throwable t) { + response = RestServiceUtil.handleException(result, t); + } + result.computeStatus(); + finishRequest(task); + return response; + } + + @GET + @Path(ModelPublicConstants.CLUSTER_REPORT_FILE_PATH) + @Produces("application/octet-stream") + public Response getReportFile(@QueryParam(ModelPublicConstants.CLUSTER_REPORT_FILE_FILENAME_PARAMETER) String fileName, @Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_GET_REPORT_FILE); + + Response response; + try { + checkNodeAuthentication(); + FileResolution resolution = resolveFile(fileName); + if (resolution.status == null) { + StreamingOutput streaming = outputStream -> { + try (FileInputStream fileInputStream = new FileInputStream(resolution.file)) { + IOUtils.copy(fileInputStream, outputStream); + } + }; + response = Response.ok(streaming).build(); // we are only interested in the content, not in its type nor length + } else { + response = Response.status(resolution.status).build(); + } + result.computeStatus(); + } catch (Throwable t) { + response = RestServiceUtil.handleException(null, t); // we don't return the operation result + } + finishRequest(task); + return response; + } + + @DELETE + @Path(ModelPublicConstants.CLUSTER_REPORT_FILE_PATH) + public Response deleteReportFile(@QueryParam(ModelPublicConstants.CLUSTER_REPORT_FILE_FILENAME_PARAMETER) String fileName, @Context MessageContext mc) { + Task task = RestServiceUtil.initRequest(mc); + OperationResult result = new OperationResult(OPERATION_DELETE_REPORT_FILE); + + Response response; + try { + checkNodeAuthentication(); + FileResolution resolution = resolveFile(fileName); + if (resolution.status == null) { + if (!resolution.file.delete()) { + LOGGER.warn("Couldn't delete report output file {}", resolution.file); + } + response = Response.ok().build(); + } else { + response = Response.status(resolution.status).build(); + } + result.computeStatus(); + } catch (Throwable t) { + response = RestServiceUtil.handleException(null, t); // we don't return the operation result + } + finishRequest(task); + return response; + } + + static class FileResolution { + File file; + Status status; + } + + private FileResolution resolveFile(String fileName) { + FileResolution rv = new FileResolution(); + rv.file = Paths.get(midpointConfiguration.getMidpointHome(), EXPORT_DIR, fileName).toFile(); + + if (forbiddenFileName(fileName)) { + LOGGER.warn("File name '{}' is forbidden", fileName); + rv.status = Status.FORBIDDEN; + } else if (!rv.file.exists()) { + LOGGER.warn("Report output file '{}' does not exist", rv.file); + rv.status = Status.NOT_FOUND; + } else if (rv.file.isDirectory()) { + LOGGER.warn("Report output file '{}' is a directory", rv.file); + rv.status = Status.FORBIDDEN; + } + return rv; + } + + private void finishRequest(Task task) { + RestServiceUtil.finishRequest(task, securityHelper); + } + + private boolean forbiddenFileName(String fileName) { + return fileName.contains("/../"); + } + + private void checkNodeAuthentication() throws SecurityViolationException { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (!(authentication instanceof NodeAuthenticationToken)) { + throw new SecurityViolationException("Node authentication is expected but not present"); + } + // TODO consider allowing administrator access here as well + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java index 1652317e956..97f7a3b30d3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/MappingDiagEvaluator.java @@ -1,138 +1,138 @@ -/* - * Copyright (c) 2010-2018 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.controller; - -import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.DeltaConvertor; -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.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -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; - -/** - * Executes mappings in diagnostic mode. - * - * @author mederly - */ -@Component -public class MappingDiagEvaluator { - - @Autowired - private MappingFactory mappingFactory; - - @Autowired - private ModelService modelService; - - @Autowired - private ModelObjectResolver objectResolver; - - @Autowired - private PrismContext prismContext; - - @Autowired - private Clock clock; - - public MappingEvaluationResponseType evaluateMapping(@NotNull MappingEvaluationRequestType request, @NotNull Task task, - @NotNull OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { - - MappingImpl.Builder builder = mappingFactory.createMappingBuilder(); - - ObjectDeltaObject sourceContext = createSourceContext(request, task, result); - - builder = builder - .mappingType(request.getMapping()) - .contextDescription("mapping diagnostic execution") - .sourceContext(sourceContext) - .targetContext(createTargetContext(request, sourceContext)) - .profiling(true) - .now(clock.currentTimeXMLGregorianCalendar()); - - MappingImpl mapping = builder.build(); - - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - try { - mapping.evaluate(task, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - StringBuilder sb = new StringBuilder(); - sb.append("Output triple: "); - dumpOutputTriple(sb, mapping.getOutputTriple()); - sb.append("Condition output triple: "); - dumpOutputTriple(sb, mapping.getConditionOutputTriple()); - sb.append("Time constraint valid: ").append(mapping.evaluateTimeConstraintValid(task, result)).append("\n"); - sb.append("Next recompute time: ").append(mapping.getNextRecomputeTime()).append("\n"); - sb.append("\n"); - sb.append("Evaluation time: ").append(mapping.getEtime()).append(" ms\n"); - - MappingEvaluationResponseType response = new MappingEvaluationResponseType(); - response.setResponse(sb.toString()); - return response; - } - - private void dumpOutputTriple(StringBuilder sb, PrismValueDeltaSetTriple triple) { - if (triple != null) { - sb.append("\n").append(triple.debugDump(1)).append("\n\n"); - } else { - sb.append("(null)\n\n"); - } - } - - private PrismObjectDefinition createTargetContext(MappingEvaluationRequestType request, ObjectDeltaObject sourceContext) { - if (request.getTargetContext() == null) { - return sourceContext.getDefinition(); - } - return prismContext.getSchemaRegistry().findObjectDefinitionByType(request.getTargetContext()); - } - - private ObjectDeltaObject createSourceContext(MappingEvaluationRequestType request, Task task, - OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (request.getSourceContext() == null) { - return null; - } - MappingEvaluationSourceContextType ctx = request.getSourceContext(); - - PrismObject oldObject; - if (ctx.getObject() != null) { - oldObject = ctx.getObject().getValue().asPrismObject(); - } else if (ctx.getObjectRef() != null) { - oldObject = objectResolver.resolve(ctx.getObjectRef(), ObjectType.class, null, "resolving default source", task, result).asPrismObject(); - } else { - oldObject = null; - } - ObjectDelta delta; - if (ctx.getDelta() != null) { - delta = DeltaConvertor.createObjectDelta(ctx.getDelta(), prismContext); - } else { - delta = null; - } - return new ObjectDeltaObject(oldObject, delta, null, oldObject.getDefinition()); - } -} +/* + * Copyright (c) 2010-2018 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.controller; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.DeltaConvertor; +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.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +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; + +/** + * Executes mappings in diagnostic mode. + * + * @author mederly + */ +@Component +public class MappingDiagEvaluator { + + @Autowired + private MappingFactory mappingFactory; + + @Autowired + private ModelService modelService; + + @Autowired + private ModelObjectResolver objectResolver; + + @Autowired + private PrismContext prismContext; + + @Autowired + private Clock clock; + + public MappingEvaluationResponseType evaluateMapping(@NotNull MappingEvaluationRequestType request, @NotNull Task task, + @NotNull OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + + MappingImpl.Builder builder = mappingFactory.createMappingBuilder(); + + ObjectDeltaObject sourceContext = createSourceContext(request, task, result); + + builder = builder + .mappingType(request.getMapping()) + .contextDescription("mapping diagnostic execution") + .sourceContext(sourceContext) + .targetContext(createTargetContext(request, sourceContext)) + .profiling(true) + .now(clock.currentTimeXMLGregorianCalendar()); + + MappingImpl mapping = builder.build(); + + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + try { + mapping.evaluate(task, result); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + + StringBuilder sb = new StringBuilder(); + sb.append("Output triple: "); + dumpOutputTriple(sb, mapping.getOutputTriple()); + sb.append("Condition output triple: "); + dumpOutputTriple(sb, mapping.getConditionOutputTriple()); + sb.append("Time constraint valid: ").append(mapping.evaluateTimeConstraintValid(task, result)).append("\n"); + sb.append("Next recompute time: ").append(mapping.getNextRecomputeTime()).append("\n"); + sb.append("\n"); + sb.append("Evaluation time: ").append(mapping.getEtime()).append(" ms\n"); + + MappingEvaluationResponseType response = new MappingEvaluationResponseType(); + response.setResponse(sb.toString()); + return response; + } + + private void dumpOutputTriple(StringBuilder sb, PrismValueDeltaSetTriple triple) { + if (triple != null) { + sb.append("\n").append(triple.debugDump(1)).append("\n\n"); + } else { + sb.append("(null)\n\n"); + } + } + + private PrismObjectDefinition createTargetContext(MappingEvaluationRequestType request, ObjectDeltaObject sourceContext) { + if (request.getTargetContext() == null) { + return sourceContext.getDefinition(); + } + return prismContext.getSchemaRegistry().findObjectDefinitionByType(request.getTargetContext()); + } + + private ObjectDeltaObject createSourceContext(MappingEvaluationRequestType request, Task task, + OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (request.getSourceContext() == null) { + return null; + } + MappingEvaluationSourceContextType ctx = request.getSourceContext(); + + PrismObject oldObject; + if (ctx.getObject() != null) { + oldObject = ctx.getObject().getValue().asPrismObject(); + } else if (ctx.getObjectRef() != null) { + oldObject = objectResolver.resolve(ctx.getObjectRef(), ObjectType.class, null, "resolving default source", task, result).asPrismObject(); + } else { + oldObject = null; + } + ObjectDelta delta; + if (ctx.getDelta() != null) { + delta = DeltaConvertor.createObjectDelta(ctx.getDelta(), prismContext); + } else { + delta = null; + } + return new ObjectDeltaObject(oldObject, delta, null, oldObject.getDefinition()); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ExpressionHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ExpressionHandler.java index a3be22cda79..4ca4cc43fde 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ExpressionHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/ExpressionHandler.java @@ -1,178 +1,179 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.expr; - -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -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.evolveum.midpoint.xml.ns._public.common.common_3.*; -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; - -/** - * - * @author lazyman - * - */ -@Component -public class ExpressionHandler { - - private static final Trace LOGGER = TraceManager.getTrace(ExpressionHandler.class); - - @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; - @Autowired private ExpressionFactory expressionFactory; - @Autowired private ModelObjectResolver modelObjectResolver; - @Autowired private PrismContext prismContext; - - public String evaluateExpression(ShadowType shadow, ExpressionType expressionType, - String shortDesc, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - Validate.notNull(shadow, "Resource object shadow must not be null."); - Validate.notNull(expressionType, "Expression must not be null."); - Validate.notNull(result, "Operation result must not be null."); - - ResourceType resource = resolveResource(shadow, result); - - ExpressionVariables variables = getDefaultXPathVariables(null, shadow, resource); - - PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.OUTPUT_ELEMENT_NAME, - DOMUtil.XSD_STRING); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, - outputDefinition, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); - if (outputTriple == null) { - return null; - } - Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - return null; - } - if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Expression returned more than one value ("+nonNegativeValues.size()+") in "+shortDesc); - } - return nonNegativeValues.iterator().next().getValue(); - } - - public boolean evaluateConfirmationExpression(UserType user, ShadowType shadow, - ExpressionType expressionType, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - Validate.notNull(user, "User must not be null."); - Validate.notNull(shadow, "Resource object shadow must not be null."); - Validate.notNull(expressionType, "Expression must not be null."); - Validate.notNull(result, "Operation result must not be null."); - - ResourceType resource = resolveResource(shadow, result); - ExpressionVariables variables = getDefaultXPathVariables(user, shadow, resource); - String shortDesc = "confirmation expression for "+resource.asPrismObject(); - - PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.OUTPUT_ELEMENT_NAME, - DOMUtil.XSD_BOOLEAN); - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, - outputDefinition, expressionProfile, shortDesc, task, result); - - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); - Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); - } - if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Expression returned more than one value ("+nonNegativeValues.size()+") in "+shortDesc); - } - PrismPropertyValue resultpval = nonNegativeValues.iterator().next(); - if (resultpval == null) { - throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); - } - Boolean resultVal = resultpval.getValue(); - if (resultVal == null) { - throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); - } - return resultVal; - } - - // TODO: refactor - this method is also in SchemaHandlerImpl - private ResourceType resolveResource(ShadowType shadow, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - ObjectReferenceType ref = shadow.getResourceRef(); - if (ref == null) { - throw new ExpressionEvaluationException("Resource shadow object " + shadow + " doesn't have defined resource."); - } - PrismObject resource = ref.getObject(); - if (resource != null) { - return resource.asObjectable(); - } - if (ref.getOid() == null) { - throw new ExpressionEvaluationException("Resource shadow object " + shadow + " defines null resource OID."); - } - - return modelObjectResolver.getObjectSimple(ResourceType.class, ref.getOid(), null, null, result); - } - - public static ExpressionVariables getDefaultXPathVariables(UserType user, - ShadowType shadow, ResourceType resource) { - - ExpressionVariables variables = new ExpressionVariables(); - if (user != null) { - variables.put(ExpressionConstants.VAR_FOCUS, user.asPrismObject(), user.asPrismObject().getDefinition()); - variables.put(ExpressionConstants.VAR_USER, user.asPrismObject(), user.asPrismObject().getDefinition()); - } - - if (shadow != null) { - variables.addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, shadow.asPrismObject(), shadow.asPrismObject().getDefinition()); - variables.addVariableDefinition(ExpressionConstants.VAR_PROJECTION, shadow.asPrismObject(), shadow.asPrismObject().getDefinition()); - } - - if (resource != null) { - variables.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, resource.asPrismObject(), resource.asPrismObject().getDefinition()); - } - - return variables; - } - - // Called from the ObjectResolver.resolve - public ObjectType resolveRef(ObjectReferenceType ref, String contextDescription, OperationResult result) - throws ObjectNotFoundException, SchemaException { - - Class type = ObjectType.class; - if (ref.getType() != null) { - ObjectTypes objectTypeType = ObjectTypes.getObjectTypeFromTypeQName(ref.getType()); - type = objectTypeType.getClassDefinition(); - } - - return repositoryService.getObject(type, ref.getOid(), null, result).asObjectable(); - - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.expr; + +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +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.evolveum.midpoint.xml.ns._public.common.common_3.*; +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; + +/** + * + * @author lazyman + * + */ +@Component +public class ExpressionHandler { + + private static final Trace LOGGER = TraceManager.getTrace(ExpressionHandler.class); + + @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private ModelObjectResolver modelObjectResolver; + @Autowired private PrismContext prismContext; + + public String evaluateExpression(ShadowType shadow, ExpressionType expressionType, + String shortDesc, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + Validate.notNull(shadow, "Resource object shadow must not be null."); + Validate.notNull(expressionType, "Expression must not be null."); + Validate.notNull(result, "Operation result must not be null."); + + ResourceType resource = resolveResource(shadow, result); + + ExpressionVariables variables = getDefaultXPathVariables(null, shadow, resource); + + PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.OUTPUT_ELEMENT_NAME, + DOMUtil.XSD_STRING); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, + outputDefinition, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); + PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); + if (outputTriple == null) { + return null; + } + Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); + if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { + return null; + } + if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Expression returned more than one value ("+nonNegativeValues.size()+") in "+shortDesc); + } + return nonNegativeValues.iterator().next().getValue(); + } + + public boolean evaluateConfirmationExpression(UserType user, ShadowType shadow, + ExpressionType expressionType, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + Validate.notNull(user, "User must not be null."); + Validate.notNull(shadow, "Resource object shadow must not be null."); + Validate.notNull(expressionType, "Expression must not be null."); + Validate.notNull(result, "Operation result must not be null."); + + ResourceType resource = resolveResource(shadow, result); + ExpressionVariables variables = getDefaultXPathVariables(user, shadow, resource); + String shortDesc = "confirmation expression for "+resource.asPrismObject(); + + PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.OUTPUT_ELEMENT_NAME, + DOMUtil.XSD_BOOLEAN); + ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, + outputDefinition, expressionProfile, shortDesc, task, result); + + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); + PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); + Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); + if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { + throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); + } + if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Expression returned more than one value ("+nonNegativeValues.size()+") in "+shortDesc); + } + PrismPropertyValue resultpval = nonNegativeValues.iterator().next(); + if (resultpval == null) { + throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); + } + Boolean resultVal = resultpval.getValue(); + if (resultVal == null) { + throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); + } + return resultVal; + } + + // TODO: refactor - this method is also in SchemaHandlerImpl + private ResourceType resolveResource(ShadowType shadow, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { + ObjectReferenceType ref = shadow.getResourceRef(); + if (ref == null) { + throw new ExpressionEvaluationException("Resource shadow object " + shadow + " doesn't have defined resource."); + } + PrismObject resource = ref.getObject(); + if (resource != null) { + return resource.asObjectable(); + } + if (ref.getOid() == null) { + throw new ExpressionEvaluationException("Resource shadow object " + shadow + " defines null resource OID."); + } + + return modelObjectResolver.getObjectSimple(ResourceType.class, ref.getOid(), null, null, result); + } + + public static ExpressionVariables getDefaultXPathVariables(UserType user, + ShadowType shadow, ResourceType resource) { + + ExpressionVariables variables = new ExpressionVariables(); + if (user != null) { + variables.put(ExpressionConstants.VAR_FOCUS, user.asPrismObject(), user.asPrismObject().getDefinition()); + variables.put(ExpressionConstants.VAR_USER, user.asPrismObject(), user.asPrismObject().getDefinition()); + } + + if (shadow != null) { + variables.addVariableDefinition(ExpressionConstants.VAR_ACCOUNT, shadow.asPrismObject(), shadow.asPrismObject().getDefinition()); + variables.addVariableDefinition(ExpressionConstants.VAR_PROJECTION, shadow.asPrismObject(), shadow.asPrismObject().getDefinition()); + } + + if (resource != null) { + variables.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, resource.asPrismObject(), resource.asPrismObject().getDefinition()); + } + + return variables; + } + + // Called from the ObjectResolver.resolve + public ObjectType resolveRef(ObjectReferenceType ref, String contextDescription, OperationResult result) + throws ObjectNotFoundException, SchemaException { + + Class type = ObjectType.class; + if (ref.getType() != null) { + ObjectTypes objectTypeType = ObjectTypes.getObjectTypeFromTypeQName(ref.getType()); + type = objectTypeType.getClassDefinition(); + } + + return repositoryService.getObject(type, ref.getOid(), null, result).asObjectable(); + + } + +} 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 18e98e53e93..87302ab2941 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 @@ -1,2024 +1,2020 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.expr; - -import com.evolveum.midpoint.common.LocalizationService; -import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; -import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; -import com.evolveum.midpoint.model.api.*; -import com.evolveum.midpoint.model.api.context.AssignmentPath; -import com.evolveum.midpoint.model.api.context.Mapping; -import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.api.context.ModelElementContext; -import com.evolveum.midpoint.model.api.context.ModelProjectionContext; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.api.expr.MidpointFunctions; -import com.evolveum.midpoint.model.api.expr.OptimizingTriggerCreator; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.ConstantsManager; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.expr.triggerSetter.OptimizingTriggerCreatorImpl; -import com.evolveum.midpoint.model.impl.expr.triggerSetter.TriggerCreatorGlobalState; -import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; -import com.evolveum.midpoint.model.impl.lens.LensContext; -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.SynchronizationExpressionsEvaluator; -import com.evolveum.midpoint.model.impl.sync.SynchronizationContext; -import com.evolveum.midpoint.model.impl.sync.SynchronizationServiceUtils; -import com.evolveum.midpoint.model.impl.trigger.RecomputeTriggerHandler; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.crypto.EncryptionException; -import com.evolveum.midpoint.prism.crypto.Protector; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.xml.XmlTypeConverter; -import com.evolveum.midpoint.provisioning.api.ProvisioningService; -import com.evolveum.midpoint.repo.api.RepositoryService; -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.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.*; -import com.evolveum.midpoint.security.api.HttpConnectionInformation; -import com.evolveum.midpoint.security.api.MidPointPrincipal; -import com.evolveum.midpoint.security.api.SecurityContextManager; -import com.evolveum.midpoint.security.api.SecurityUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.Holder; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.Producer; -import com.evolveum.midpoint.util.annotation.Experimental; -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.CredentialsCapabilityType; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PasswordCapabilityType; -import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; - -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.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.Context; -import javax.xml.namespace.QName; -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamConstants; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.Characters; -import javax.xml.stream.events.EndElement; -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.*; -import java.util.stream.Collectors; - -import static com.evolveum.midpoint.schema.constants.SchemaConstants.PATH_CREDENTIALS_PASSWORD; -import static com.evolveum.midpoint.schema.constants.SchemaConstants.PATH_CREDENTIALS_PASSWORD_VALUE; -import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType.RUNNABLE; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; -import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; - -/** - * @author semancik - * - */ -@Component -public class MidpointFunctionsImpl implements MidpointFunctions { - - private static final Trace LOGGER = TraceManager.getTrace(MidpointFunctionsImpl.class); - - public static final String CLASS_DOT = MidpointFunctions.class.getName() + "."; - - @Autowired private PrismContext prismContext; - @Autowired private RelationRegistry relationRegistry; - @Autowired private ModelService modelService; - @Autowired private ModelInteractionService modelInteractionService; - @Autowired private ModelObjectResolver modelObjectResolver; - @Autowired private ProvisioningService provisioningService; - @Autowired private SecurityContextManager securityContextManager; - @Autowired private transient Protector protector; - @Autowired private OrgStructFunctionsImpl orgStructFunctions; - @Autowired private WorkflowService workflowService; - @Autowired private ConstantsManager constantsManager; - @Autowired private LocalizationService localizationService; - @Autowired private ExpressionFactory expressionFactory; - @Autowired private SynchronizationExpressionsEvaluator correlationConfirmationEvaluator; - @Autowired private ArchetypeManager archetypeManager; - @Autowired private TriggerCreatorGlobalState triggerCreatorGlobalState; - @Autowired private TaskManager taskManager; - @Context HttpServletRequest httpServletRequest; - - @Autowired - @Qualifier("cacheRepositoryService") - private RepositoryService repositoryService; - - public String hello(String name) { - return "Hello " + name; - } - - @Override - public PrismContext getPrismContext() { - return prismContext; - } - - @Override - public RelationRegistry getRelationRegistry() { - return relationRegistry; - } - - @Override - public List toList(String... s) { - return Arrays.asList(s); - } - - @Override - public UserType getUserByOid(String oid) throws ObjectNotFoundException, SchemaException { - return repositoryService.getObject(UserType.class, oid, null, new OperationResult("getUserByOid")).asObjectable(); - } - - @Override - public boolean isMemberOf(UserType user, String orgOid) { - for (ObjectReferenceType objectReferenceType : user.getParentOrgRef()) { - if (orgOid.equals(objectReferenceType.getOid())) { - return true; - } - } - return false; - } - - @Override - public String getPlaintextUserPassword(FocusType user) throws EncryptionException { - if (user == null || user.getCredentials() == null || user.getCredentials().getPassword() == null) { - return null; // todo log a warning here? - } - ProtectedStringType protectedStringType = user.getCredentials().getPassword().getValue(); - if (protectedStringType != null) { - return protector.decryptString(protectedStringType); - } else { - return null; - } - } - - @Override - public String getPlaintextAccountPassword(ShadowType account) throws EncryptionException { - if (account == null || account.getCredentials() == null || account.getCredentials().getPassword() == null) { - return null; // todo log a warning here? - } - ProtectedStringType protectedStringType = account.getCredentials().getPassword().getValue(); - if (protectedStringType != null) { - return protector.decryptString(protectedStringType); - } else { - return null; - } - } - - @Override - public String getPlaintextAccountPasswordFromDelta(ObjectDelta delta) throws EncryptionException { - - if (delta.isAdd()) { - ShadowType newShadow = delta.getObjectToAdd().asObjectable(); - return getPlaintextAccountPassword(newShadow); - } - if (!delta.isModify()) { - return null; - } - - List passwords = new ArrayList<>(); - for (ItemDelta itemDelta : delta.getModifications()) { - takePasswordsFromItemDelta(passwords, itemDelta); - } - LOGGER.trace("Found " + passwords.size() + " password change value(s)"); - if (!passwords.isEmpty()) { - return protector.decryptString(passwords.get(passwords.size() - 1)); - } else { - return null; - } - } - - private void takePasswordsFromItemDelta(List passwords, ItemDelta itemDelta) { - if (itemDelta.isDelete()) { - return; - } - - if (itemDelta.getPath().equivalent(PATH_CREDENTIALS_PASSWORD_VALUE)) { - LOGGER.trace("Found password value add/modify delta"); - //noinspection unchecked - Collection> values = itemDelta.isAdd() ? - itemDelta.getValuesToAdd() : - itemDelta.getValuesToReplace(); - for (PrismPropertyValue value : values) { - passwords.add(value.getValue()); - } - } else if (itemDelta.getPath().equivalent(PATH_CREDENTIALS_PASSWORD)) { - LOGGER.trace("Found password add/modify delta"); - //noinspection unchecked - Collection> values = itemDelta.isAdd() ? - itemDelta.getValuesToAdd() : - itemDelta.getValuesToReplace(); - for (PrismContainerValue value : values) { - if (value.asContainerable().getValue() != null) { - passwords.add(value.asContainerable().getValue()); - } - } - } else if (itemDelta.getPath().equivalent(ShadowType.F_CREDENTIALS)) { - LOGGER.trace("Found credentials add/modify delta"); - //noinspection unchecked - Collection> values = itemDelta.isAdd() ? - itemDelta.getValuesToAdd() : - itemDelta.getValuesToReplace(); - for (PrismContainerValue value : values) { - if (value.asContainerable().getPassword() != null && value.asContainerable().getPassword().getValue() != null) { - passwords.add(value.asContainerable().getPassword().getValue()); - } - } - } - } - - @Override - public String getPlaintextUserPasswordFromDeltas(List> objectDeltas) throws EncryptionException { - - List passwords = new ArrayList<>(); - - for (ObjectDelta delta : objectDeltas) { - - if (delta.isAdd()) { - FocusType newObject = delta.getObjectToAdd().asObjectable(); - return getPlaintextUserPassword(newObject); // for simplicity we do not look for other values - } - - if (!delta.isModify()) { - continue; - } - - for (ItemDelta itemDelta : delta.getModifications()) { - takePasswordsFromItemDelta(passwords, itemDelta); - } - } - LOGGER.trace("Found " + passwords.size() + " password change value(s)"); - if (!passwords.isEmpty()) { - return protector.decryptString(passwords.get(passwords.size() - 1)); - } else { - return null; - } - } - - @Override - public boolean hasLinkedAccount(String resourceOid) { - LensContext ctx = ModelExpressionThreadLocalHolder.getLensContext(); - if (ctx == null) { - throw new IllegalStateException("No lens context"); - } - LensFocusContext focusContext = ctx.getFocusContext(); - if (focusContext == null) { - throw new IllegalStateException("No focus in lens context"); - } - - ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal(); - - ResourceShadowDiscriminator rat = new ResourceShadowDiscriminator(resourceOid, ShadowKindType.ACCOUNT, null, null, false); - LensProjectionContext projectionContext = ctx.findProjectionContext(rat); - if (projectionContext == null) { - // but check if it is not among list of deleted contexts - if (scriptContext == null || scriptContext.isEvaluateNew()) { - return false; - } - // evaluating old state - for (ResourceShadowDiscriminator deletedOne : ctx.getHistoricResourceObjects()) { - if (resourceOid.equals(deletedOne.getResourceOid()) && deletedOne.getKind() == ShadowKindType.ACCOUNT - && deletedOne.getIntent() == null || "default" - .equals(deletedOne.getIntent())) { // TODO implement this seriously - LOGGER.trace("Found deleted one: {}", deletedOne); // TODO remove - return true; - } - } - return false; - } - - if (projectionContext.isTombstone()) { - return false; - } - - SynchronizationPolicyDecision synchronizationPolicyDecision = projectionContext.getSynchronizationPolicyDecision(); - SynchronizationIntent synchronizationIntent = projectionContext.getSynchronizationIntent(); - if (scriptContext == null) { - if (synchronizationPolicyDecision == null) { - return synchronizationIntent != SynchronizationIntent.DELETE && synchronizationIntent != SynchronizationIntent.UNLINK; - } else { - return synchronizationPolicyDecision != SynchronizationPolicyDecision.DELETE && synchronizationPolicyDecision != SynchronizationPolicyDecision.UNLINK; - } - } else if (scriptContext.isEvaluateNew()) { - // Evaluating new state - if (focusContext.isDelete()) { - return false; - } - if (synchronizationPolicyDecision == null) { - return synchronizationIntent != SynchronizationIntent.DELETE && synchronizationIntent != SynchronizationIntent.UNLINK; - } else { - return synchronizationPolicyDecision != SynchronizationPolicyDecision.DELETE && synchronizationPolicyDecision != SynchronizationPolicyDecision.UNLINK; - } - } else { - // Evaluating old state - if (focusContext.isAdd()) { - return false; - } - if (synchronizationPolicyDecision == null) { - return synchronizationIntent != SynchronizationIntent.ADD; - } else { - return synchronizationPolicyDecision != SynchronizationPolicyDecision.ADD; - } - } - } - - @Override - public boolean isDirectlyAssigned(F focusType, String targetOid) { - for (AssignmentType assignment : focusType.getAssignment()) { - ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && targetRef.getOid().equals(targetOid)) { - return true; - } - } - return false; - } - - @Override - public boolean isDirectlyAssigned(F focusType, ObjectType target) { - return isDirectlyAssigned(focusType, target.getOid()); - } - - @Override - public boolean isDirectlyAssigned(String targetOid) { - LensContext ctx = ModelExpressionThreadLocalHolder.getLensContext(); - if (ctx == null) { - throw new IllegalStateException("No lens context"); - } - LensFocusContext focusContext = ctx.getFocusContext(); - if (focusContext == null) { - throw new IllegalStateException("No focus in lens context"); - } - - PrismObject focus; - ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal(); - if (scriptContext == null) { - focus = focusContext.getObjectAny(); - } else if (scriptContext.isEvaluateNew()) { - // Evaluating new state - if (focusContext.isDelete()) { - return false; - } - focus = focusContext.getObjectNew(); - } else { - // Evaluating old state - if (focusContext.isAdd()) { - return false; - } - focus = focusContext.getObjectOld(); - } - if (focus == null) { - return false; - } - return isDirectlyAssigned(focus.asObjectable(), targetOid); - } - - @Override - public boolean isDirectlyAssigned(ObjectType target) { - return isDirectlyAssigned(target.getOid()); - } - - // EXPERIMENTAL!! - @SuppressWarnings("unused") - @Experimental - public boolean hasActiveAssignmentTargetSubtype(String roleSubtype) { - LensContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); - if (lensContext == null) { - throw new UnsupportedOperationException("hasActiveAssignmentRoleSubtype works only with model context"); - } - DeltaSetTriple> evaluatedAssignmentTriple = lensContext.getEvaluatedAssignmentTriple(); - if (evaluatedAssignmentTriple == null) { - throw new UnsupportedOperationException("hasActiveAssignmentRoleSubtype works only with evaluatedAssignmentTriple"); - } - Collection> nonNegativeEvaluatedAssignments = evaluatedAssignmentTriple.getNonNegativeValues(); - for (EvaluatedAssignmentImpl nonNegativeEvaluatedAssignment : nonNegativeEvaluatedAssignments) { - PrismObject target = nonNegativeEvaluatedAssignment.getTarget(); - if (target == null) { - continue; - } - //noinspection unchecked - Collection targetSubtypes = FocusTypeUtil.determineSubTypes((PrismObject) target); - if (targetSubtypes.contains(roleSubtype)) { - return true; - } - } - return false; - } - - @Override - public ShadowType getLinkedShadow(FocusType focus, ResourceType resource) throws SchemaException, - SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - return getLinkedShadow(focus, resource.getOid()); - } - - @Override - public ShadowType getLinkedShadow(FocusType focus, ResourceType resource, boolean repositoryObjectOnly) - throws SchemaException, - SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - return getLinkedShadow(focus, resource.getOid(), repositoryObjectOnly); - } - - @Override - public ShadowType getLinkedShadow(FocusType focus, String resourceOid) - throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, - ExpressionEvaluationException { - return getLinkedShadow(focus, resourceOid, false); - } - - @Override - public ShadowType getLinkedShadow(FocusType focus, String resourceOid, boolean repositoryObjectOnly) - throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, - ExpressionEvaluationException { - if (focus == null) { - return null; - } - List linkRefs = focus.getLinkRef(); - for (ObjectReferenceType linkRef : linkRefs) { - ShadowType shadowType; - try { - shadowType = getObject(ShadowType.class, linkRef.getOid(), - SelectorOptions.createCollection(GetOperationOptions.createNoFetch())); - } catch (ObjectNotFoundException e) { - // Shadow is gone in the meantime. MidPoint will resolve that by itself. - // It is safe to ignore this error in this method. - LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus - + " because it no longer exists in repository"); - continue; - } - if (shadowType.getResourceRef().getOid().equals(resourceOid)) { - // We have repo shadow here. Re-read resource shadow if necessary. - if (!repositoryObjectOnly) { - try { - shadowType = getObject(ShadowType.class, shadowType.getOid()); - } catch (ObjectNotFoundException e) { - // Shadow is gone in the meantime. MidPoint will resolve that by itself. - // It is safe to ignore this error in this method. - LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus - + " because it no longer exists on resource"); - continue; - } - } - return shadowType; - } - } - return null; - } - - @Override - public ShadowType getLinkedShadow(FocusType focus, String resourceOid, ShadowKindType kind, String intent) - throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, - ExpressionEvaluationException { - return getLinkedShadow(focus, resourceOid, kind, intent, false); - } - - @Override - public ShadowType getLinkedShadow(FocusType focus, String resourceOid, ShadowKindType kind, String intent, - boolean repositoryObjectOnly) - throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, - ExpressionEvaluationException { - List linkRefs = focus.getLinkRef(); - for (ObjectReferenceType linkRef : linkRefs) { - ShadowType shadowType; - try { - shadowType = getObject(ShadowType.class, linkRef.getOid(), - SelectorOptions.createCollection(GetOperationOptions.createNoFetch())); - } catch (ObjectNotFoundException e) { - // Shadow is gone in the meantime. MidPoint will resolve that by itself. - // It is safe to ignore this error in this method. - LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus - + " because it no longer exists in repository"); - continue; - } - if (ShadowUtil.matches(shadowType, resourceOid, kind, intent)) { - // We have repo shadow here. Re-read resource shadow if necessary. - if (!repositoryObjectOnly) { - try { - shadowType = getObject(ShadowType.class, shadowType.getOid()); - } catch (ObjectNotFoundException e) { - // Shadow is gone in the meantime. MidPoint will resolve that by itself. - // It is safe to ignore this error in this method. - LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus - + " because it no longer exists on resource"); - continue; - } - } - return shadowType; - } - } - return null; - } - - @Override - public boolean isFullShadow() { - ModelProjectionContext projectionContext = getProjectionContext(); - if (projectionContext == null) { - LOGGER.debug("Call to isFullShadow while there is no projection context"); - return false; - } - return projectionContext.isFullShadow(); - } - - @Override - public boolean isProjectionExists() { - ModelProjectionContext projectionContext = getProjectionContext(); - if (projectionContext == null) { - return false; - } - return projectionContext.isExists(); - } - - @Override - public Integer countAccounts(String resourceOid, QName attributeName, T attributeValue) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - OperationResult result = getCurrentResult(CLASS_DOT + "countAccounts"); - ResourceType resourceType = modelObjectResolver.getObjectSimple(ResourceType.class, resourceOid, null, null, result); - return countAccounts(resourceType, attributeName, attributeValue, getCurrentTask(), result); - } - - @Override - public Integer countAccounts(ResourceType resourceType, QName attributeName, T attributeValue) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "countAccounts"); - return countAccounts(resourceType, attributeName, attributeValue, getCurrentTask(), result); - } - - @Override - public Integer countAccounts(ResourceType resourceType, String attributeName, T attributeValue) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "countAccounts"); - QName attributeQName = new QName(ResourceTypeUtil.getResourceNamespace(resourceType), attributeName); - return countAccounts(resourceType, attributeQName, attributeValue, getCurrentTask(), result); - } - - private Integer countAccounts(ResourceType resourceType, QName attributeName, T attributeValue, Task task, - OperationResult result) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - ObjectQuery query = createAttributeQuery(resourceType, attributeName, attributeValue); - return modelObjectResolver.countObjects(ShadowType.class, query, null, task, result); - } - - @Override - public boolean isUniquePropertyValue(ObjectType objectType, String propertyPathString, T propertyValue) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - Validate.notEmpty(propertyPathString, "Empty property path"); - OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "isUniquePropertyValue"); - ItemPath propertyPath = prismContext.itemPathParser().asItemPath(propertyPathString); - return isUniquePropertyValue(objectType, propertyPath, propertyValue, getCurrentTask(), result); - } - - private boolean isUniquePropertyValue(final ObjectType objectType, ItemPath propertyPath, T propertyValue, Task task, - OperationResult result) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - List conflictingObjects = getObjectsInConflictOnPropertyValue(objectType, propertyPath, - propertyValue, null, false, task, result); - return conflictingObjects.isEmpty(); - } - - @Override - public List getObjectsInConflictOnPropertyValue(O objectType, String propertyPathString, - T propertyValue, boolean getAllConflicting) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - return getObjectsInConflictOnPropertyValue(objectType, propertyPathString, propertyValue, - PrismConstants.DEFAULT_MATCHING_RULE_NAME.getLocalPart(), getAllConflicting); - } - - public List getObjectsInConflictOnPropertyValue(O objectType, String propertyPathString, - T propertyValue, String matchingRuleName, boolean getAllConflicting) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - Validate.notEmpty(propertyPathString, "Empty property path"); - OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "getObjectsInConflictOnPropertyValue"); - ItemPath propertyPath = prismContext.itemPathParser().asItemPath(propertyPathString); - QName matchingRuleQName = new QName(matchingRuleName); // no namespace for now - return getObjectsInConflictOnPropertyValue(objectType, propertyPath, propertyValue, matchingRuleQName, getAllConflicting, - getCurrentTask(), result); - } - - private List getObjectsInConflictOnPropertyValue(final O objectType, ItemPath propertyPath, - T propertyValue, QName matchingRule, final boolean getAllConflicting, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - Validate.notNull(objectType, "Null object"); - Validate.notNull(propertyPath, "Null property path"); - Validate.notNull(propertyValue, "Null property value"); - PrismPropertyDefinition propertyDefinition = objectType.asPrismObject().getDefinition() - .findPropertyDefinition(propertyPath); - if (matchingRule == null) { - if (propertyDefinition != null && PolyStringType.COMPLEX_TYPE.equals(propertyDefinition.getTypeName())) { - matchingRule = PrismConstants.POLY_STRING_ORIG_MATCHING_RULE_NAME; - } else { - matchingRule = PrismConstants.DEFAULT_MATCHING_RULE_NAME; - } - } - ObjectQuery query = prismContext.queryFor(objectType.getClass()) - .item(propertyPath, propertyDefinition).eq(propertyValue).matching(matchingRule) - .build(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Determining uniqueness of property {} using query:\n{}", propertyPath, query.debugDump()); - } - - final List conflictingObjects = new ArrayList<>(); - ResultHandler handler = (object, parentResult) -> { - if (objectType.getOid() == null) { - // We have found a conflicting object - conflictingObjects.add(object.asObjectable()); - return getAllConflicting; - } else { - if (objectType.getOid().equals(object.getOid())) { - // We have found ourselves. No conflict (yet). Just go on. - return true; - } else { - // We have found someone else. Conflict. - conflictingObjects.add(object.asObjectable()); - return getAllConflicting; - } - } - }; - - //noinspection unchecked - modelObjectResolver.searchIterative((Class) objectType.getClass(), query, null, handler, task, result); - - return conflictingObjects; - } - - @Override - public boolean isUniqueAccountValue(ResourceType resourceType, ShadowType shadowType, String attributeName, - T attributeValue) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - Validate.notEmpty(attributeName, "Empty attribute name"); - OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "isUniqueAccountValue"); - QName attributeQName = new QName(ResourceTypeUtil.getResourceNamespace(resourceType), attributeName); - return isUniqueAccountValue(resourceType, shadowType, attributeQName, attributeValue, getCurrentTask(), result); - } - - private boolean isUniqueAccountValue(ResourceType resourceType, final ShadowType shadowType, - QName attributeName, T attributeValue, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - Validate.notNull(resourceType, "Null resource"); - Validate.notNull(shadowType, "Null shadow"); - Validate.notNull(attributeName, "Null attribute name"); - Validate.notNull(attributeValue, "Null attribute value"); - - ObjectQuery query = createAttributeQuery(resourceType, attributeName, attributeValue); - LOGGER.trace("Determining uniqueness of attribute {} using query:\n{}", attributeName, query.debugDumpLazily()); - - final Holder isUniqueHolder = new Holder<>(true); - ResultHandler handler = (object, parentResult) -> { - if (shadowType.getOid() == null) { - // We have found a conflicting object - isUniqueHolder.setValue(false); - return false; - } else { - if (shadowType.getOid().equals(object.getOid())) { - // We have found ourselves. No conflict (yet). Just go on. - return true; - } else { - // We have found someone else. Conflict. - isUniqueHolder.setValue(false); - return false; - } - } - }; - - modelObjectResolver.searchIterative(ShadowType.class, query, null, handler, task, result); - - return isUniqueHolder.getValue(); - } - - private ObjectQuery createAttributeQuery(ResourceType resourceType, QName attributeName, T attributeValue) throws SchemaException { - RefinedResourceSchema rSchema = RefinedResourceSchemaImpl.getRefinedSchema(resourceType); - RefinedObjectClassDefinition rAccountDef = rSchema.getDefaultRefinedDefinition(ShadowKindType.ACCOUNT); - RefinedAttributeDefinition attrDef = rAccountDef.findAttributeDefinition(attributeName); - if (attrDef == null) { - throw new SchemaException("No attribute '" + attributeName + "' in " + rAccountDef); - } - return prismContext.queryFor(ShadowType.class) - .itemWithDef(attrDef, ShadowType.F_ATTRIBUTES, attrDef.getItemName()).eq(attributeValue) - .and().item(ShadowType.F_OBJECT_CLASS).eq(rAccountDef.getObjectClassDefinition().getTypeName()) - .and().item(ShadowType.F_RESOURCE_REF).ref(resourceType.getOid()) - .build(); - } - - @Override - public ModelContext getModelContext() { - return ModelExpressionThreadLocalHolder.getLensContext(); - } - - @Override - public ModelElementContext getFocusContext() { - LensContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); - if (lensContext == null) { - return null; - } - //noinspection unchecked - return (ModelElementContext) lensContext.getFocusContext(); - } - - @Override - public ModelProjectionContext getProjectionContext() { - return ModelExpressionThreadLocalHolder.getProjectionContext(); - } - - @Override - public Mapping getMapping() { - return ModelExpressionThreadLocalHolder.getMapping(); - } - - @Override - public Task getCurrentTask() { - Task rv = ModelExpressionThreadLocalHolder.getCurrentTask(); - if (rv == null) { - // fallback (MID-4130): but maybe we should instead make sure ModelExpressionThreadLocalHolder is set up correctly - ScriptExpressionEvaluationContext ctx = ScriptExpressionEvaluationContext.getThreadLocal(); - if (ctx != null) { - rv = ctx.getTask(); - } - } - return rv; - } - - @Override - public OperationResult getCurrentResult() { - // This is the most current operation result, reflecting e.g. the fact that mapping evaluation was started. - ScriptExpressionEvaluationContext ctx = ScriptExpressionEvaluationContext.getThreadLocal(); - if (ctx != null) { - return ctx.getResult(); - } else { - // This is a bit older. But better than nothing. - return ModelExpressionThreadLocalHolder.getCurrentResult(); - } - } - - @Override - public OperationResult getCurrentResult(String operationName) { - OperationResult currentResult = getCurrentResult(); - if (currentResult != null) { - return currentResult; - } else { - LOGGER.warn("No operation result for {}, creating a new one", operationName); - return new OperationResult(operationName); - } - } - - // functions working with ModelContext - - @Override - public ModelContext unwrapModelContext(LensContextType lensContextType) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - ExpressionEvaluationException { - return LensContext.fromLensContextType(lensContextType, prismContext, provisioningService, getCurrentTask(), - getCurrentResult(MidpointFunctions.class.getName() + "getObject")); - } - - @Override - public LensContextType wrapModelContext(ModelContext lensContext) throws SchemaException { - return ((LensContext) lensContext).toLensContextType(); - } - - // Convenience functions - - @Override - public T createEmptyObject(Class type) throws SchemaException { - PrismObjectDefinition objectDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type); - PrismObject object = objectDefinition.instantiate(); - return object.asObjectable(); - } - - @Override - public T createEmptyObjectWithName(Class type, String name) throws SchemaException { - T objectType = createEmptyObject(type); - objectType.setName(new PolyStringType(name)); - return objectType; - } - - @Override - public T createEmptyObjectWithName(Class type, PolyString name) throws SchemaException { - T objectType = createEmptyObject(type); - objectType.setName(new PolyStringType(name)); - return objectType; - } - - @Override - public T createEmptyObjectWithName(Class type, PolyStringType name) throws SchemaException { - T objectType = createEmptyObject(type); - objectType.setName(name); - return objectType; - } - - // Functions accessing modelService - - @Override - public T resolveReference(ObjectReferenceType reference) - throws ObjectNotFoundException, SchemaException, - CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - if (reference == null) { - return null; - } - QName type = reference.getType(); // TODO what about implicitly specified types, like in resourceRef? - PrismObjectDefinition objectDefinition = prismContext.getSchemaRegistry() - .findObjectDefinitionByType(reference.getType()); - if (objectDefinition == null) { - throw new SchemaException("No definition for type " + type); - } - return modelService.getObject( - objectDefinition.getCompileTimeClass(), reference.getOid(), - SelectorOptions.createCollection(GetOperationOptions.createExecutionPhase()), getCurrentTask(), getCurrentResult()) - .asObjectable(); - } - - @Override - public T resolveReferenceIfExists(ObjectReferenceType reference) - throws SchemaException, - CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - try { - return resolveReference(reference); - } catch (ObjectNotFoundException e) { - return null; - } - } - - @Override - public T getObject(Class type, String oid, - Collection> options) - throws ObjectNotFoundException, SchemaException, - CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - return modelService.getObject(type, oid, options, getCurrentTask(), getCurrentResult()).asObjectable(); - } - - @Override - public T getObject(Class type, String oid) throws ObjectNotFoundException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - PrismObject prismObject = modelService.getObject(type, oid, - getDefaultGetOptionCollection(), - getCurrentTask(), getCurrentResult()); - return prismObject.asObjectable(); - } - - @Override - public void executeChanges( - Collection> deltas, - ModelExecuteOptions options) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, - ConfigurationException, PolicyViolationException, - SecurityViolationException { - modelService.executeChanges(deltas, options, getCurrentTask(), getCurrentResult()); - } - - @Override - public void executeChanges( - Collection> deltas) - throws ObjectAlreadyExistsException, ObjectNotFoundException, - SchemaException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, - PolicyViolationException, SecurityViolationException { - modelService.executeChanges(deltas, null, getCurrentTask(), getCurrentResult()); - } - - @SafeVarargs - @Override - public final void executeChanges(ObjectDelta... deltas) - throws ObjectAlreadyExistsException, ObjectNotFoundException, - SchemaException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, - PolicyViolationException, SecurityViolationException { - Collection> deltaCollection = MiscSchemaUtil.createCollection(deltas); - modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult()); - } - - @Override - public String addObject(PrismObject newObject, - ModelExecuteOptions options) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, - ConfigurationException, PolicyViolationException, - SecurityViolationException { - ObjectDelta delta = DeltaFactory.Object.createAddDelta(newObject); - Collection> deltaCollection = MiscSchemaUtil.createCollection(delta); - Collection> executedChanges = modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult()); - String oid = ObjectDeltaOperation.findAddDeltaOid(executedChanges, newObject); - newObject.setOid(oid); - return oid; - } - - @Override - public String addObject(PrismObject newObject) - throws ObjectAlreadyExistsException, ObjectNotFoundException, - SchemaException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, - PolicyViolationException, SecurityViolationException { - return addObject(newObject, null); - } - - @Override - public String addObject(T newObject, - ModelExecuteOptions options) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, - ConfigurationException, PolicyViolationException, - SecurityViolationException { - return addObject(newObject.asPrismObject(), options); - } - - @Override - public String addObject(T newObject) - throws ObjectAlreadyExistsException, ObjectNotFoundException, - SchemaException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, - PolicyViolationException, SecurityViolationException { - return addObject(newObject.asPrismObject(), null); - } - - @Override - public void modifyObject(ObjectDelta modifyDelta, - ModelExecuteOptions options) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, - ConfigurationException, PolicyViolationException, - SecurityViolationException { - Collection> deltaCollection = MiscSchemaUtil.createCollection(modifyDelta); - modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult()); - } - - @Override - public void modifyObject(ObjectDelta modifyDelta) - throws ObjectAlreadyExistsException, ObjectNotFoundException, - SchemaException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, - PolicyViolationException, SecurityViolationException { - Collection> deltaCollection = MiscSchemaUtil.createCollection(modifyDelta); - modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult()); - } - - @Override - public void deleteObject(Class type, String oid, - ModelExecuteOptions options) throws ObjectAlreadyExistsException, - ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, - ConfigurationException, PolicyViolationException, - SecurityViolationException { - ObjectDelta deleteDelta = prismContext.deltaFactory().object().createDeleteDelta(type, oid); - Collection> deltaCollection = MiscSchemaUtil.createCollection(deleteDelta); - modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult()); - } - - @Override - public void deleteObject(Class type, String oid) - throws ObjectAlreadyExistsException, ObjectNotFoundException, - SchemaException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, - PolicyViolationException, SecurityViolationException { - ObjectDelta deleteDelta = prismContext.deltaFactory().object().createDeleteDelta(type, oid); - Collection> deltaCollection = MiscSchemaUtil.createCollection(deleteDelta); - modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult()); - } - - @Override - public void recompute(Class type, String oid) throws SchemaException, PolicyViolationException, - ExpressionEvaluationException, ObjectNotFoundException, - ObjectAlreadyExistsException, CommunicationException, - ConfigurationException, SecurityViolationException { - modelService.recompute(type, oid, null, getCurrentTask(), getCurrentResult()); - } - - @Override - public PrismObject findShadowOwner(String accountOid) - throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException, - ExpressionEvaluationException, CommunicationException { - return modelService.findShadowOwner(accountOid, getCurrentTask(), getCurrentResult()); - } - - @Override - public PrismObject searchShadowOwner(String accountOid) - throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException, - ExpressionEvaluationException, CommunicationException { - //noinspection unchecked - return (PrismObject) modelService.searchShadowOwner(accountOid, null, getCurrentTask(), getCurrentResult()); - } - - @Override - public List searchObjects( - Class type, ObjectQuery query, - Collection> options) - throws SchemaException, ObjectNotFoundException, - SecurityViolationException, CommunicationException, - ConfigurationException, ExpressionEvaluationException { - return MiscSchemaUtil.toObjectableList( - modelService.searchObjects(type, query, options, getCurrentTask(), getCurrentResult())); - } - - @Override - public List searchObjects( - Class type, ObjectQuery query) throws SchemaException, - ObjectNotFoundException, SecurityViolationException, - CommunicationException, ConfigurationException, ExpressionEvaluationException { - return MiscSchemaUtil.toObjectableList( - modelService.searchObjects(type, query, - getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult())); - } - - @Override - public void searchObjectsIterative(Class type, - ObjectQuery query, ResultHandler handler, - Collection> options) - throws SchemaException, ObjectNotFoundException, - CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - modelService.searchObjectsIterative(type, query, handler, options, getCurrentTask(), getCurrentResult()); - } - - @Override - public void searchObjectsIterative(Class type, - ObjectQuery query, ResultHandler handler) - throws SchemaException, ObjectNotFoundException, - CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - modelService.searchObjectsIterative(type, query, handler, - getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); - } - - @Override - public T searchObjectByName(Class type, String name) - throws SecurityViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SchemaException, ExpressionEvaluationException { - ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext); - List> foundObjects = modelService - .searchObjects(type, nameQuery, - getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); - if (foundObjects.isEmpty()) { - return null; - } - if (foundObjects.size() > 1) { - throw new IllegalStateException("More than one object found for type " + type + " and name '" + name + "'"); - } - return foundObjects.iterator().next().asObjectable(); - } - - @Override - public T searchObjectByName(Class type, PolyString name) - throws SecurityViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SchemaException, ExpressionEvaluationException { - ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext); - List> foundObjects = modelService - .searchObjects(type, nameQuery, - getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); - if (foundObjects.isEmpty()) { - return null; - } - if (foundObjects.size() > 1) { - throw new IllegalStateException("More than one object found for type " + type + " and name '" + name + "'"); - } - return foundObjects.iterator().next().asObjectable(); - } - - @Override - public T searchObjectByName(Class type, PolyStringType name) - throws SecurityViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SchemaException, ExpressionEvaluationException { - ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext); - List> foundObjects = modelService - .searchObjects(type, nameQuery, - getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); - if (foundObjects.isEmpty()) { - return null; - } - if (foundObjects.size() > 1) { - throw new IllegalStateException("More than one object found for type " + type + " and name '" + name + "'"); - } - return foundObjects.iterator().next().asObjectable(); - } - - @Override - public int countObjects(Class type, - ObjectQuery query, - Collection> options) - throws SchemaException, ObjectNotFoundException, - SecurityViolationException, ConfigurationException, - CommunicationException, ExpressionEvaluationException { - return modelService.countObjects(type, query, options, getCurrentTask(), getCurrentResult()); - } - - @Override - public int countObjects(Class type, - ObjectQuery query) throws SchemaException, ObjectNotFoundException, - SecurityViolationException, ConfigurationException, - CommunicationException, ExpressionEvaluationException { - return modelService.countObjects(type, query, - getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); - } - - @Override - public OperationResult testResource(String resourceOid) - throws ObjectNotFoundException { - return modelService.testResource(resourceOid, getCurrentTask()); - } - - @Override - public ObjectDeltaType getResourceDelta(ModelContext context, String resourceOid) throws SchemaException { - List> deltas = new ArrayList<>(); - for (Object modelProjectionContextObject : context.getProjectionContexts()) { - LensProjectionContext lensProjectionContext = (LensProjectionContext) modelProjectionContextObject; - if (lensProjectionContext.getResourceShadowDiscriminator() != null && - resourceOid.equals(lensProjectionContext.getResourceShadowDiscriminator().getResourceOid())) { - deltas.add(lensProjectionContext.getDelta()); // union of primary and secondary deltas - } - } - ObjectDelta sum = ObjectDeltaCollectionsUtil.summarize(deltas); - return DeltaConvertor.toObjectDeltaType(sum); - } - - @Override - public long getSequenceCounter(String sequenceOid) throws ObjectNotFoundException, SchemaException { - return SequentialValueExpressionEvaluator.getSequenceCounter(sequenceOid, repositoryService, getCurrentResult()); - } - - // orgstruct related methods - - @Override - public Collection getManagersOids(UserType user) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getManagersOids(user, false); - } - - @Override - public Collection getOrgUnits(UserType user, QName relation) { - return orgStructFunctions.getOrgUnits(user, relation, false); - } - - @Override - public OrgType getParentOrgByOrgType(ObjectType object, String orgType) throws SchemaException, SecurityViolationException { - return orgStructFunctions.getParentOrgByOrgType(object, orgType, false); - } - - @Override - public OrgType getParentOrgByArchetype(ObjectType object, String archetypeOid) throws SchemaException, SecurityViolationException { - return orgStructFunctions.getParentOrgByArchetype(object, archetypeOid, false); - } - - @Override - public OrgType getOrgByOid(String oid) throws SchemaException { - return orgStructFunctions.getOrgByOid(oid, false); - } - - @Override - public Collection getParentOrgs(ObjectType object) throws SchemaException, SecurityViolationException { - return orgStructFunctions.getParentOrgs(object, false); - } - - @Override - public Collection getOrgUnits(UserType user) { - return orgStructFunctions.getOrgUnits(user, false); - } - - @Override - public Collection getManagersOfOrg(String orgOid) throws SchemaException, SecurityViolationException { - return orgStructFunctions.getManagersOfOrg(orgOid, false); - } - - @Override - public boolean isManagerOfOrgType(UserType user, String orgType) throws SchemaException { - return orgStructFunctions.isManagerOfOrgType(user, orgType, false); - } - - @Override - public Collection getManagers(UserType user) throws SchemaException, SecurityViolationException { - return orgStructFunctions.getManagers(user, false); - } - - @Override - public Collection getManagersByOrgType(UserType user, String orgType) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getManagersByOrgType(user, orgType, false); - } - - @Override - public boolean isManagerOf(UserType user, String orgOid) { - return orgStructFunctions.isManagerOf(user, orgOid, false); - } - - @Override - public Collection getParentOrgsByRelation(ObjectType object, String relation) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getParentOrgsByRelation(object, relation, false); - } - - @Override - public Collection getManagers(UserType user, String orgType, boolean allowSelf) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getManagers(user, orgType, allowSelf, false); - } - - @Override - public Collection getParentOrgs(ObjectType object, String relation, String orgType) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getParentOrgs(object, relation, orgType, false); - } - - @Override - public Collection getManagersOidsExceptUser(UserType user) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getManagersOidsExceptUser(user, false); - } - - @Override - public Collection getManagersOidsExceptUser(@NotNull Collection userRefList) - throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, - ConfigurationException, ExpressionEvaluationException { - return orgStructFunctions.getManagersOidsExceptUser(userRefList, false); - } - - @Override - public OrgType getOrgByName(String name) throws SchemaException, SecurityViolationException { - return orgStructFunctions.getOrgByName(name, false); - } - - @Override - public Collection getParentOrgsByRelation(ObjectType object, QName relation) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getParentOrgsByRelation(object, relation, false); - } - - @Override - public Collection getParentOrgs(ObjectType object, QName relation, String orgType) - throws SchemaException, SecurityViolationException { - return orgStructFunctions.getParentOrgs(object, relation, orgType, false); - } - - @Override - public boolean isManager(UserType user) { - return orgStructFunctions.isManager(user); - } - - @Override - public Protector getProtector() { - return protector; - } - - @Override - public String getPlaintext(ProtectedStringType protectedStringType) throws EncryptionException { - if (protectedStringType != null) { - return protector.decryptString(protectedStringType); - } else { - return null; - } - } - - @Override - public Map parseXmlToMap(String xml) { - Map resultingMap = new HashMap<>(); - if (xml != null && !xml.isEmpty()) { - XMLInputFactory factory = XMLInputFactory.newInstance(); - String value = ""; - String startName = ""; - InputStream stream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); - boolean isRootElement = true; - try { - XMLEventReader eventReader = factory.createXMLEventReader(stream); - while (eventReader.hasNext()) { - - XMLEvent event = eventReader.nextEvent(); - int code = event.getEventType(); - if (code == XMLStreamConstants.START_ELEMENT) { - - StartElement startElement = event.asStartElement(); - startName = startElement.getName().getLocalPart(); - if (!isRootElement) { - resultingMap.put(startName, null); - } else { - isRootElement = false; - } - } else if (code == XMLStreamConstants.CHARACTERS) { - Characters characters = event.asCharacters(); - if (!characters.isWhiteSpace()) { - - StringBuilder valueBuilder; - if (value != null) { - valueBuilder = new StringBuilder(value).append(" ").append(characters.getData()); - } else { - valueBuilder = new StringBuilder(characters.getData()); - } - value = valueBuilder.toString(); - } - } else if (code == XMLStreamConstants.END_ELEMENT) { - - EndElement endElement = event.asEndElement(); - String endName = endElement.getName().getLocalPart(); - - if (endName.equals(startName)) { - if (value != null) { - resultingMap.put(endName, value); - value = null; - } - } else { - LOGGER.trace("No value between xml tags, tag name : {}", endName); - } - - } else if (code == XMLStreamConstants.END_DOCUMENT) { - isRootElement = true; - } - } - } catch (XMLStreamException e) { - - StringBuilder error = new StringBuilder("Xml stream exception wile parsing xml string") - .append(e.getLocalizedMessage()); - throw new SystemException(error.toString()); - } - } else { - LOGGER.trace("Input xml string null or empty."); - } - return resultingMap; - } - - @Override - public List getMembersAsReferences(String orgOid) throws SchemaException, SecurityViolationException, - CommunicationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException { - return getMembers(orgOid).stream() - .map(obj -> createObjectRef(obj, prismContext)) - .collect(Collectors.toList()); - } - - @Override - public List getMembers(String orgOid) throws SchemaException, ObjectNotFoundException, SecurityViolationException, - CommunicationException, ConfigurationException, ExpressionEvaluationException { - ObjectQuery query = prismContext.queryFor(UserType.class) - .isDirectChildOf(orgOid) - .build(); - return searchObjects(UserType.class, query, null); - } - - @Override - public String computeProjectionLifecycle(F focus, ShadowType shadow, ResourceType resource) { - if (focus == null || shadow == null) { - return null; - } - if (!(focus instanceof UserType)) { - return null; - } - if (shadow.getKind() != null && shadow.getKind() != ShadowKindType.ACCOUNT) { - return null; - } - ProtectedStringType focusPasswordPs = FocusTypeUtil.getPasswordValue((UserType) focus); - if (focusPasswordPs != null && focusPasswordPs.canGetCleartext()) { - return null; - } - CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil - .getEffectiveCapability(resource, CredentialsCapabilityType.class); - if (credentialsCapabilityType == null) { - return null; - } - PasswordCapabilityType passwordCapabilityType = credentialsCapabilityType.getPassword(); - if (passwordCapabilityType == null) { - return null; - } - if (passwordCapabilityType.isEnabled() == Boolean.FALSE) { - return null; - } - return SchemaConstants.LIFECYCLE_PROPOSED; - } - - public MidPointPrincipal getPrincipal() throws SecurityViolationException { - return securityContextManager.getPrincipal(); - } - - @Override - public String getPrincipalOid() { - return securityContextManager.getPrincipalOid(); - } - - @Override - public String getChannel() { - Task task = getCurrentTask(); - return task != null ? task.getChannel() : null; - } - - @Override - public WorkflowService getWorkflowService() { - return workflowService; - } - - @Override - public List getShadowsToActivate(Collection projectionContexts) { - List shadows = new ArrayList<>(); - - //noinspection unchecked - for (ModelElementContext projectionCtx : projectionContexts) { - - List executedShadowDeltas = projectionCtx.getExecutedDeltas(); - //noinspection unchecked - for (ObjectDeltaOperation shadowDelta : executedShadowDeltas) { - if (shadowDelta.getExecutionResult().getStatus() == OperationResultStatus.SUCCESS - && shadowDelta.getObjectDelta().getChangeType() == ChangeType.ADD) { - PrismObject shadow = shadowDelta.getObjectDelta().getObjectToAdd(); - PrismProperty pLifecycleState = shadow.findProperty(ShadowType.F_LIFECYCLE_STATE); - if (pLifecycleState != null && !pLifecycleState.isEmpty() && SchemaConstants.LIFECYCLE_PROPOSED - .equals(pLifecycleState.getRealValue())) { - shadows.add(shadow.asObjectable()); - } - - } - } - } - return shadows; - } - - @Override - public String createRegistrationConfirmationLink(UserType userType) { - SecurityPolicyType securityPolicy = resolveSecurityPolicy(userType.asPrismObject()); - if (securityPolicy != null && securityPolicy.getAuthentication() != null - && securityPolicy.getAuthentication().getSequence() != null && !securityPolicy.getAuthentication().getSequence().isEmpty()) { - if (securityPolicy.getRegistration() != null && securityPolicy.getRegistration().getSelfRegistration() != null - && securityPolicy.getRegistration().getSelfRegistration().getAdditionalAuthenticationName() != null) { - String resetPasswordSequenceName = securityPolicy.getRegistration().getSelfRegistration().getAdditionalAuthenticationName(); - String prefix = createPrefixLinkByAuthSequence(SchemaConstants.CHANNEL_GUI_SELF_REGISTRATION_URI, resetPasswordSequenceName, securityPolicy.getAuthentication().getSequence()); - if (prefix != null) { - return createTokenConfirmationLink(prefix, userType); - } - } - } - return createTokenConfirmationLink(SchemaConstants.REGISTRATION_CONFIRAMTION_PREFIX, userType); - } - - @Override - public String createPasswordResetLink(UserType userType) { - SecurityPolicyType securityPolicy = resolveSecurityPolicy(userType.asPrismObject()); - if (securityPolicy != null && securityPolicy.getAuthentication() != null - && securityPolicy.getAuthentication().getSequence() != null && !securityPolicy.getAuthentication().getSequence().isEmpty()) { - if (securityPolicy.getCredentialsReset() != null && securityPolicy.getCredentialsReset().getAuthenticationSequenceName() != null) { - String resetPasswordSequenceName = securityPolicy.getCredentialsReset().getAuthenticationSequenceName(); - String prefix = createPrefixLinkByAuthSequence(SchemaConstants.CHANNEL_GUI_RESET_PASSWORD_URI, resetPasswordSequenceName, securityPolicy.getAuthentication().getSequence()); - if (prefix != null) { - return createTokenConfirmationLink(prefix, userType); - } - } - } - return createTokenConfirmationLink(SchemaConstants.PASSWORD_RESET_CONFIRMATION_PREFIX, userType); - } - - @Override - public String createAccountActivationLink(UserType userType) { - return createBaseConfirmationLink(SchemaConstants.ACCOUNT_ACTIVATION_PREFIX, userType.getOid()); - } - - private String createBaseConfirmationLink(String prefix, UserType userType) { - return getPublicHttpUrlPattern() + prefix + "?" + SchemaConstants.USER_ID + "=" + userType.getName().getOrig(); - } - - private String createBaseConfirmationLink(String prefix, String oid) { - return getPublicHttpUrlPattern() + prefix + "?" + SchemaConstants.USER_ID + "=" + oid; - } - - private String createTokenConfirmationLink(String prefix, UserType userType) { - return createBaseConfirmationLink(prefix, userType) + "&" + SchemaConstants.TOKEN + "=" + getNonce(userType); - } - - private String createPrefixLinkByAuthSequence(String channel, String nameOfSequence, Collection sequences){ - AuthenticationSequenceType sequenceByName = null; - AuthenticationSequenceType defaultSequence = null; - for (AuthenticationSequenceType sequenceType : sequences) { - if (sequenceType.getName().equals(nameOfSequence)) { - sequenceByName = sequenceType; - break; - } else if (sequenceType.getChannel().getChannelId().equals(channel) - && Boolean.TRUE.equals(sequenceType.getChannel().isDefault())) { - defaultSequence = sequenceType; - } - } - AuthenticationSequenceType usedSequence = sequenceByName != null ? sequenceByName : defaultSequence; - if (usedSequence != null) { - String sequecnceSuffix = usedSequence.getChannel().getUrlSuffix(); - String prefix = (sequecnceSuffix.startsWith("/")) ? sequecnceSuffix : ("/" + sequecnceSuffix); - return SchemaConstants.AUTH_MODULE_PREFIX + prefix; - } - return null; - } - - private SecurityPolicyType resolveSecurityPolicy(PrismObject user) { - return securityContextManager.runPrivileged(new Producer() { - private static final long serialVersionUID = 1L; - - @Override - public SecurityPolicyType run() { - - Task task = taskManager.createTaskInstance("load security policy"); - - Task currentTask = getCurrentTask(); - task.setChannel(currentTask != null ? currentTask.getChannel() : null); - - OperationResult result = new OperationResult("load security policy"); - - try { - return modelInteractionService.getSecurityPolicy(user, task, result); - } catch (CommonException e) { - LOGGER.error("Could not retrieve security policy: {}", e.getMessage(), e); - return null; - } - - } - - }); - } - - private String getPublicHttpUrlPattern() { - SystemConfigurationType systemConfiguration; - try { - systemConfiguration = modelInteractionService.getSystemConfiguration(getCurrentResult()); - } catch (ObjectNotFoundException | SchemaException e) { - LOGGER.error("Error while getting system configuration. ", e); - return null; - } - if (systemConfiguration == null) { - LOGGER.trace("No system configuration defined. Skipping link generation."); - return null; - } - String host = null; - HttpConnectionInformation connectionInf = SecurityUtil.getCurrentConnectionInformation(); - if (connectionInf != null) { - host = connectionInf.getServerName(); - } - String publicHttpUrlPattern = SystemConfigurationTypeUtil.getPublicHttpUrlPattern(systemConfiguration, host); - if (StringUtils.isBlank(publicHttpUrlPattern)) { - LOGGER.error("No patern defined. It can break link generation."); - } - - return publicHttpUrlPattern; - - } - - private String getNonce(UserType user) { - if (user.getCredentials() == null) { - return null; - } - - if (user.getCredentials().getNonce() == null) { - return null; - } - - if (user.getCredentials().getNonce().getValue() == null) { - return null; - } - - try { - return getPlaintext(user.getCredentials().getNonce().getValue()); - } catch (EncryptionException e) { - return null; - } - } - - @Override - public String getConst(String name) { - return constantsManager.getConstantValue(name); - } - - @Override - public ShadowType resolveEntitlement(ShadowAssociationType shadowAssociationType) { - ObjectReferenceType shadowRef = shadowAssociationType.getShadowRef(); - if (shadowRef == null) { - LOGGER.trace("No shadowRef in association {}", shadowAssociationType); - return null; - } - if (shadowRef.asReferenceValue().getObject() != null){ - return (ShadowType) shadowAssociationType.getShadowRef().asReferenceValue().getObject().asObjectable(); - } - - LensProjectionContext projectionCtx = (LensProjectionContext) getProjectionContext(); - if (projectionCtx == null) { - LOGGER.trace("No projection found. Skipping resolving entitlement"); - return null; - } - - Map> entitlementMap = projectionCtx.getEntitlementMap(); - if (entitlementMap == null) { - LOGGER.trace("No entitlement map present in projection context {}", projectionCtx); - return null; - } - - PrismObject entitlement = entitlementMap.get(shadowRef.getOid()); - if (entitlement == null) { - LOGGER.trace("No entitlement with oid {} found among resolved entitlement {}.", shadowRef, entitlementMap); - return null; - } - LOGGER.trace("Returning resolved entitlement: {}", entitlement); - return entitlement.asObjectable(); - - } - - @Override - public ExtensionType collectExtensions(AssignmentPathType path, int startAt) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException { - return AssignmentPath.collectExtensions(path, startAt, modelService, getCurrentTask(), getCurrentResult()); - } - @Override - public TaskType executeChangesAsynchronously(Collection> deltas, ModelExecuteOptions options, - String templateTaskOid) throws SecurityViolationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { - return executeChangesAsynchronously(deltas, options, templateTaskOid, getCurrentTask(), getCurrentResult()); - } - - @Override - public TaskType executeChangesAsynchronously(Collection> deltas, ModelExecuteOptions options, - String templateTaskOid, Task opTask, OperationResult result) throws SecurityViolationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { - MidPointPrincipal principal = securityContextManager.getPrincipal(); - if (principal == null) { - throw new SecurityViolationException("No current user"); - } - TaskType newTask; - if (templateTaskOid != null) { - newTask = modelService.getObject(TaskType.class, templateTaskOid, - getDefaultGetOptionCollection(), opTask, result).asObjectable(); - } else { - newTask = new TaskType(prismContext); - newTask.setName(PolyStringType.fromOrig("Execute changes")); - newTask.setRecurrence(TaskRecurrenceType.SINGLE); - } - newTask.setName(PolyStringType.fromOrig(newTask.getName().getOrig() + " " + (int) (Math.random()*10000))); - newTask.setOid(null); - newTask.setTaskIdentifier(null); - newTask.setOwnerRef(createObjectRef(principal.getFocus(), prismContext)); - newTask.setExecutionStatus(RUNNABLE); - newTask.setHandlerUri(ModelPublicConstants.EXECUTE_DELTAS_TASK_HANDLER_URI); - if (deltas.isEmpty()) { - throw new IllegalArgumentException("No deltas to execute"); - } - List deltasBeans = new ArrayList<>(); - for (ObjectDelta delta : deltas) { - //noinspection unchecked - deltasBeans.add(DeltaConvertor.toObjectDeltaType((ObjectDelta) delta)); - } - //noinspection unchecked - PrismPropertyDefinition deltasDefinition = prismContext.getSchemaRegistry() - .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_OBJECT_DELTAS); - PrismProperty deltasProperty = deltasDefinition.instantiate(); - deltasProperty.setRealValues(deltasBeans.toArray(new ObjectDeltaType[0])); - newTask.asPrismObject().addExtensionItem(deltasProperty); - if (options != null) { - //noinspection unchecked - PrismPropertyDefinition optionsDefinition = prismContext.getSchemaRegistry() - .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_EXECUTE_OPTIONS); - PrismProperty optionsProperty = optionsDefinition.instantiate(); - optionsProperty.setRealValue(options.toModelExecutionOptionsType()); - newTask.asPrismObject().addExtensionItem(optionsProperty); - } - ObjectDelta taskAddDelta = DeltaFactory.Object.createAddDelta(newTask.asPrismObject()); - Collection> operations = modelService - .executeChanges(singleton(taskAddDelta), null, opTask, result); - return (TaskType) operations.iterator().next().getObjectDelta().getObjectToAdd().asObjectable(); - } - - @Override - public TaskType submitTaskFromTemplate(String templateTaskOid, List> extensionItems) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { - return modelInteractionService.submitTaskFromTemplate(templateTaskOid, extensionItems, getCurrentTask(), getCurrentResult()); - } - - @Override - public TaskType submitTaskFromTemplate(String templateTaskOid, Map extensionValues) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { - return modelInteractionService.submitTaskFromTemplate(templateTaskOid, extensionValues, getCurrentTask(), getCurrentResult()); - } - - @Override - public String translate(LocalizableMessage message) { - return localizationService.translate(message, Locale.getDefault()); - } - - @Override - public String translate(LocalizableMessageType message) { - return localizationService.translate(LocalizationUtil.toLocalizableMessage(message), Locale.getDefault()); - } - - @Override - public Object executeAdHocProvisioningScript(ResourceType resource, String language, String code) - throws SchemaException, ObjectNotFoundException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException, ObjectAlreadyExistsException { - return executeAdHocProvisioningScript(resource.getOid(), language, code); - } - - @Override - public Object executeAdHocProvisioningScript(String resourceOid, String language, String code) - throws SchemaException, ObjectNotFoundException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException, ObjectAlreadyExistsException { - OperationProvisioningScriptType script = new OperationProvisioningScriptType(); - script.setCode(code); - script.setLanguage(language); - script.setHost(ProvisioningScriptHostType.RESOURCE); - - return provisioningService.executeScript(resourceOid, script, getCurrentTask(), getCurrentResult()); - } - - @Override - public Boolean isEvaluateNew() { - ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal(); - if (scriptContext == null) { - return null; - } - return scriptContext.isEvaluateNew(); - } - - @Override - @NotNull - public Collection collectAssignedFocusMappingsResults(@NotNull ItemPath path) throws SchemaException { - LensContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); - if (lensContext == null) { - throw new IllegalStateException("No lensContext"); - } - DeltaSetTriple> evaluatedAssignmentTriple = lensContext.getEvaluatedAssignmentTriple(); - if (evaluatedAssignmentTriple == null) { - return emptySet(); - } - Collection rv = new HashSet<>(); - for (EvaluatedAssignmentImpl evaluatedAssignment : evaluatedAssignmentTriple.getNonNegativeValues()) { - if (evaluatedAssignment.isValid()) { - for (MappingImpl mapping : evaluatedAssignment.getFocusMappings()) { - if (path.equivalent(mapping.getOutputPath())) { - PrismValueDeltaSetTriple outputTriple = mapping.getOutputTriple(); - if (outputTriple != null) { - rv.addAll(outputTriple.getNonNegativeValues()); - } - } - } - } - } - // Ugly hack - MID-4452 - When having an assignment giving focusMapping, and the assignment is being deleted, the - // focus mapping is evaluated in wave 0 (results correctly being pushed to the minus set), but also in wave 1. - // The results are sent to zero set; and they are not applied only because they are already part of a priori delta. - // This causes problems here. - // - // Until MID-4452 is fixed, here we manually delete the values from the result. - LensFocusContext focusContext = lensContext.getFocusContext(); - if (focusContext != null) { - ObjectDelta delta = focusContext.getDelta(); - if (delta != null) { - ItemDelta targetItemDelta = delta.findItemDelta(path); - if (targetItemDelta != null) { - rv.removeAll(emptyIfNull(targetItemDelta.getValuesToDelete())); - } - } - } - return rv; - } - - private Collection> getDefaultGetOptionCollection() { - return SelectorOptions.createCollection(GetOperationOptions.createExecutionPhase()); - } - - @Override - public List getFocusesByCorrelationRule(Class type, String resourceOid, ShadowKindType kind, String intent, ShadowType shadow) { - ResourceType resource; - try { - resource = getObject(ResourceType.class, resourceOid, GetOperationOptions.createNoFetchCollection()); - } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException - | SecurityViolationException | ExpressionEvaluationException e) { - LOGGER.error("Cannot get resource, reason: {}", e.getMessage(), e); - return null; - } - SynchronizationType synchronization = resource.getSynchronization(); - if (synchronization == null) { - return null; - } - - ObjectSynchronizationDiscriminatorType discriminator = new ObjectSynchronizationDiscriminatorType(); - discriminator.setKind(kind); - discriminator.setIntent(intent); - - SynchronizationContext syncCtx = new SynchronizationContext<>(shadow.asPrismObject(), shadow.asPrismObject(), - null, resource.asPrismObject(), getCurrentTask().getChannel(), getPrismContext(), expressionFactory, getCurrentTask()); - - ObjectSynchronizationType applicablePolicy = null; - - OperationResult result = getCurrentResult(); - - try { - - SystemConfigurationType systemConfiguration = modelInteractionService.getSystemConfiguration(result); - syncCtx.setSystemConfiguration(systemConfiguration.asPrismObject()); - - for (ObjectSynchronizationType objectSync : synchronization.getObjectSynchronization()) { - - if (SynchronizationServiceUtils.isPolicyApplicable(objectSync, discriminator, expressionFactory, syncCtx, result)) { - applicablePolicy = objectSync; - break; - } - } - - if (applicablePolicy == null) { - return null; - } - - List> correlatedFocuses = correlationConfirmationEvaluator.findFocusesByCorrelationRule(type, shadow, applicablePolicy.getCorrelation(), resource, systemConfiguration, syncCtx.getTask(), result); - return MiscSchemaUtil.toObjectableList(correlatedFocuses); - - } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException - | ConfigurationException | SecurityViolationException e) { - LOGGER.error("Cannot find applicable policy for kind={}, intent={}. Reason: {}", kind, intent, e.getMessage(), e); - return null; - } - } - - @Override - public ModelContext previewChanges(Collection> deltas, - ModelExecuteOptions options) - throws CommunicationException, ObjectNotFoundException, ObjectAlreadyExistsException, ConfigurationException, - SchemaException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - return modelInteractionService.previewChanges(deltas, options, getCurrentTask(), getCurrentResult()); - } - - @Override - public void applyDefinition(O object) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - ExpressionEvaluationException { - if (object instanceof ShadowType || object instanceof ResourceType) { - provisioningService.applyDefinition(object.asPrismObject(), getCurrentTask(), getCurrentResult()); - } - } - - @Override - public S_ItemEntry deltaFor(Class objectClass) throws SchemaException { - return prismContext.deltaFor(objectClass); - } - - // MID-5243 - @Override - public boolean hasArchetype(O object, String archetypeOid) { - if (object == null) { - return false; - } - if (!(object instanceof AssignmentHolderType)) { - return archetypeOid == null; - } - - LensContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); - if (lensContext != null) { - LensFocusContext focusContext = lensContext.getFocusContext(); - ArchetypeType archetypeType = focusContext.getArchetype(); - if (archetypeType != null) { - return archetypeType.getOid().equals(archetypeOid); - } - } - - List archetypeRefs = ((AssignmentHolderType)object).getArchetypeRef(); - if (archetypeOid == null) { - return archetypeRefs.isEmpty(); - } - for (ObjectReferenceType archetypeRef : archetypeRefs) { - if (archetypeOid.equals(archetypeRef.getOid())) { - return true; - } - } - return false; - } - - // MID-5243 - @Override - public ArchetypeType getArchetype(O object) throws SchemaException, ConfigurationException { - if (!(object instanceof AssignmentHolderType)) { - return null; - } - //noinspection unchecked - PrismObject archetype = archetypeManager.determineArchetype((PrismObject) object.asPrismObject(), getCurrentResult()); - if (archetype == null) { - return null; - } - return archetype.asObjectable(); - } - - // MID-5243 - @Override - public String getArchetypeOid(O object) throws SchemaException, ConfigurationException { - if (!(object instanceof AssignmentHolderType)) { - return null; - } - //noinspection unchecked - ObjectReferenceType archetypeRef = archetypeManager.determineArchetypeRef((PrismObject) object.asPrismObject(), getCurrentResult()); - if (archetypeRef == null) { - return null; - } - return archetypeRef.getOid(); - } - - // temporary - public MessageWrapper wrap(AsyncUpdateMessageType message) { - return new MessageWrapper(message, prismContext); - } - - // temporary - @SuppressWarnings("unused") - public Map getMessageBodyAsMap(AsyncUpdateMessageType message) throws IOException { - return wrap(message).getBodyAsMap(); - } - - // temporary - @SuppressWarnings("unused") - public Item getMessageBodyAsPrismItem(AsyncUpdateMessageType message) throws SchemaException { - return wrap(message).getBodyAsPrismItem(PrismContext.LANG_XML); - } - - @Override - public void addRecomputeTrigger(O object, Long timestamp) throws ObjectAlreadyExistsException, - SchemaException, ObjectNotFoundException { - addRecomputeTrigger(object.asPrismObject(), timestamp); - } - - @Override - public void addRecomputeTrigger(PrismObject object, Long timestamp) - throws ObjectAlreadyExistsException, SchemaException, ObjectNotFoundException { - TriggerType trigger = new TriggerType(prismContext) - .handlerUri(RecomputeTriggerHandler.HANDLER_URI) - .timestamp(XmlTypeConverter.createXMLGregorianCalendar(timestamp != null ? timestamp : System.currentTimeMillis())); - List> itemDeltas = prismContext.deltaFor(object.asObjectable().getClass()) - .item(ObjectType.F_TRIGGER).add(trigger) - .asItemDeltas(); - repositoryService.modifyObject(object.getCompileTimeClass(), object.getOid(), itemDeltas, - getCurrentResult(CLASS_DOT + "addRecomputeTrigger")); - } - - @Override - public RepositoryService getRepositoryService() { - return repositoryService; - } - - @NotNull - @Override - public OptimizingTriggerCreator getOptimizingTriggerCreator(long fireAfter, long safetyMargin) { - return new OptimizingTriggerCreatorImpl(triggerCreatorGlobalState, this, fireAfter, safetyMargin); - } - - @NotNull - @Override - public ResourceAttributeDefinition getAttributeDefinition(PrismObject resource, QName objectClassName, - QName attributeName) throws SchemaException { - ResourceSchema resourceSchema = RefinedResourceSchema.getResourceSchema(resource, prismContext); - if (resourceSchema == null) { - throw new SchemaException("No resource schema in " + resource); - } - ObjectClassComplexTypeDefinition ocDef = resourceSchema.findObjectClassDefinition(objectClassName); - if (ocDef == null) { - throw new SchemaException("No definition of object class " + objectClassName + " in " + resource); - } - ResourceAttributeDefinition attrDef = ocDef.findAttributeDefinition(attributeName); - if (attrDef == null) { - throw new SchemaException("No definition of attribute " + attributeName + " in object class " + objectClassName - + " in " + resource); - } - return attrDef; - } - - @NotNull - @Override - public ResourceAttributeDefinition getAttributeDefinition(PrismObject resource, String objectClassName, - String attributeName) throws SchemaException { - return getAttributeDefinition(resource, new QName(objectClassName), new QName(attributeName)); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.expr; + +import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; +import com.evolveum.midpoint.model.api.*; +import com.evolveum.midpoint.model.api.context.*; +import com.evolveum.midpoint.model.api.expr.MidpointFunctions; +import com.evolveum.midpoint.model.api.expr.OptimizingTriggerCreator; +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.common.ConstantsManager; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.impl.expr.triggerSetter.OptimizingTriggerCreatorImpl; +import com.evolveum.midpoint.model.impl.expr.triggerSetter.TriggerCreatorGlobalState; +import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.messaging.MessageWrapper; +import com.evolveum.midpoint.model.impl.sync.SynchronizationExpressionsEvaluator; +import com.evolveum.midpoint.model.impl.sync.SynchronizationContext; +import com.evolveum.midpoint.model.impl.sync.SynchronizationServiceUtils; +import com.evolveum.midpoint.model.impl.trigger.RecomputeTriggerHandler; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.api.RepositoryService; +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.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.*; +import com.evolveum.midpoint.security.api.HttpConnectionInformation; +import com.evolveum.midpoint.security.api.MidPointPrincipal; +import com.evolveum.midpoint.security.api.SecurityContextManager; +import com.evolveum.midpoint.security.api.SecurityUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.util.Holder; +import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.Producer; +import com.evolveum.midpoint.util.annotation.Experimental; +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.CredentialsCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PasswordCapabilityType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; + +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.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.Context; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.EndElement; +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.*; +import java.util.stream.Collectors; + +import static com.evolveum.midpoint.schema.constants.SchemaConstants.PATH_CREDENTIALS_PASSWORD; +import static com.evolveum.midpoint.schema.constants.SchemaConstants.PATH_CREDENTIALS_PASSWORD_VALUE; +import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType.RUNNABLE; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; + +/** + * @author semancik + * + */ +@Component +public class MidpointFunctionsImpl implements MidpointFunctions { + + private static final Trace LOGGER = TraceManager.getTrace(MidpointFunctionsImpl.class); + + public static final String CLASS_DOT = MidpointFunctions.class.getName() + "."; + + @Autowired private PrismContext prismContext; + @Autowired private RelationRegistry relationRegistry; + @Autowired private ModelService modelService; + @Autowired private ModelInteractionService modelInteractionService; + @Autowired private ModelObjectResolver modelObjectResolver; + @Autowired private ProvisioningService provisioningService; + @Autowired private SecurityContextManager securityContextManager; + @Autowired private transient Protector protector; + @Autowired private OrgStructFunctionsImpl orgStructFunctions; + @Autowired private WorkflowService workflowService; + @Autowired private ConstantsManager constantsManager; + @Autowired private LocalizationService localizationService; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private SynchronizationExpressionsEvaluator correlationConfirmationEvaluator; + @Autowired private ArchetypeManager archetypeManager; + @Autowired private TriggerCreatorGlobalState triggerCreatorGlobalState; + @Autowired private TaskManager taskManager; + @Context HttpServletRequest httpServletRequest; + + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; + + public String hello(String name) { + return "Hello " + name; + } + + @Override + public PrismContext getPrismContext() { + return prismContext; + } + + @Override + public RelationRegistry getRelationRegistry() { + return relationRegistry; + } + + @Override + public List toList(String... s) { + return Arrays.asList(s); + } + + @Override + public UserType getUserByOid(String oid) throws ObjectNotFoundException, SchemaException { + return repositoryService.getObject(UserType.class, oid, null, new OperationResult("getUserByOid")).asObjectable(); + } + + @Override + public boolean isMemberOf(UserType user, String orgOid) { + for (ObjectReferenceType objectReferenceType : user.getParentOrgRef()) { + if (orgOid.equals(objectReferenceType.getOid())) { + return true; + } + } + return false; + } + + @Override + public String getPlaintextUserPassword(FocusType user) throws EncryptionException { + if (user == null || user.getCredentials() == null || user.getCredentials().getPassword() == null) { + return null; // todo log a warning here? + } + ProtectedStringType protectedStringType = user.getCredentials().getPassword().getValue(); + if (protectedStringType != null) { + return protector.decryptString(protectedStringType); + } else { + return null; + } + } + + @Override + public String getPlaintextAccountPassword(ShadowType account) throws EncryptionException { + if (account == null || account.getCredentials() == null || account.getCredentials().getPassword() == null) { + return null; // todo log a warning here? + } + ProtectedStringType protectedStringType = account.getCredentials().getPassword().getValue(); + if (protectedStringType != null) { + return protector.decryptString(protectedStringType); + } else { + return null; + } + } + + @Override + public String getPlaintextAccountPasswordFromDelta(ObjectDelta delta) throws EncryptionException { + + if (delta.isAdd()) { + ShadowType newShadow = delta.getObjectToAdd().asObjectable(); + return getPlaintextAccountPassword(newShadow); + } + if (!delta.isModify()) { + return null; + } + + List passwords = new ArrayList<>(); + for (ItemDelta itemDelta : delta.getModifications()) { + takePasswordsFromItemDelta(passwords, itemDelta); + } + LOGGER.trace("Found " + passwords.size() + " password change value(s)"); + if (!passwords.isEmpty()) { + return protector.decryptString(passwords.get(passwords.size() - 1)); + } else { + return null; + } + } + + private void takePasswordsFromItemDelta(List passwords, ItemDelta itemDelta) { + if (itemDelta.isDelete()) { + return; + } + + if (itemDelta.getPath().equivalent(PATH_CREDENTIALS_PASSWORD_VALUE)) { + LOGGER.trace("Found password value add/modify delta"); + //noinspection unchecked + Collection> values = itemDelta.isAdd() ? + itemDelta.getValuesToAdd() : + itemDelta.getValuesToReplace(); + for (PrismPropertyValue value : values) { + passwords.add(value.getValue()); + } + } else if (itemDelta.getPath().equivalent(PATH_CREDENTIALS_PASSWORD)) { + LOGGER.trace("Found password add/modify delta"); + //noinspection unchecked + Collection> values = itemDelta.isAdd() ? + itemDelta.getValuesToAdd() : + itemDelta.getValuesToReplace(); + for (PrismContainerValue value : values) { + if (value.asContainerable().getValue() != null) { + passwords.add(value.asContainerable().getValue()); + } + } + } else if (itemDelta.getPath().equivalent(ShadowType.F_CREDENTIALS)) { + LOGGER.trace("Found credentials add/modify delta"); + //noinspection unchecked + Collection> values = itemDelta.isAdd() ? + itemDelta.getValuesToAdd() : + itemDelta.getValuesToReplace(); + for (PrismContainerValue value : values) { + if (value.asContainerable().getPassword() != null && value.asContainerable().getPassword().getValue() != null) { + passwords.add(value.asContainerable().getPassword().getValue()); + } + } + } + } + + @Override + public String getPlaintextUserPasswordFromDeltas(List> objectDeltas) throws EncryptionException { + + List passwords = new ArrayList<>(); + + for (ObjectDelta delta : objectDeltas) { + + if (delta.isAdd()) { + FocusType newObject = delta.getObjectToAdd().asObjectable(); + return getPlaintextUserPassword(newObject); // for simplicity we do not look for other values + } + + if (!delta.isModify()) { + continue; + } + + for (ItemDelta itemDelta : delta.getModifications()) { + takePasswordsFromItemDelta(passwords, itemDelta); + } + } + LOGGER.trace("Found " + passwords.size() + " password change value(s)"); + if (!passwords.isEmpty()) { + return protector.decryptString(passwords.get(passwords.size() - 1)); + } else { + return null; + } + } + + @Override + public boolean hasLinkedAccount(String resourceOid) { + ModelContext ctx = ModelExpressionThreadLocalHolder.getLensContext(); + if (ctx == null) { + throw new IllegalStateException("No lens context"); + } + ModelElementContext focusContext = ctx.getFocusContext(); + if (focusContext == null) { + throw new IllegalStateException("No focus in lens context"); + } + + ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal(); + + ResourceShadowDiscriminator rat = new ResourceShadowDiscriminator(resourceOid, ShadowKindType.ACCOUNT, null, null, false); + ModelProjectionContext projectionContext = ctx.findProjectionContext(rat); + if (projectionContext == null) { + // but check if it is not among list of deleted contexts + if (scriptContext == null || scriptContext.isEvaluateNew()) { + return false; + } + // evaluating old state + for (ResourceShadowDiscriminator deletedOne : ctx.getHistoricResourceObjects()) { + if (resourceOid.equals(deletedOne.getResourceOid()) && deletedOne.getKind() == ShadowKindType.ACCOUNT + && deletedOne.getIntent() == null || "default" + .equals(deletedOne.getIntent())) { // TODO implement this seriously + LOGGER.trace("Found deleted one: {}", deletedOne); // TODO remove + return true; + } + } + return false; + } + + if (projectionContext.isTombstone()) { + return false; + } + + SynchronizationPolicyDecision synchronizationPolicyDecision = projectionContext.getSynchronizationPolicyDecision(); + SynchronizationIntent synchronizationIntent = projectionContext.getSynchronizationIntent(); + if (scriptContext == null) { + if (synchronizationPolicyDecision == null) { + return synchronizationIntent != SynchronizationIntent.DELETE && synchronizationIntent != SynchronizationIntent.UNLINK; + } else { + return synchronizationPolicyDecision != SynchronizationPolicyDecision.DELETE && synchronizationPolicyDecision != SynchronizationPolicyDecision.UNLINK; + } + } else if (scriptContext.isEvaluateNew()) { + // Evaluating new state + if (focusContext.isDelete()) { + return false; + } + if (synchronizationPolicyDecision == null) { + return synchronizationIntent != SynchronizationIntent.DELETE && synchronizationIntent != SynchronizationIntent.UNLINK; + } else { + return synchronizationPolicyDecision != SynchronizationPolicyDecision.DELETE && synchronizationPolicyDecision != SynchronizationPolicyDecision.UNLINK; + } + } else { + // Evaluating old state + if (focusContext.isAdd()) { + return false; + } + if (synchronizationPolicyDecision == null) { + return synchronizationIntent != SynchronizationIntent.ADD; + } else { + return synchronizationPolicyDecision != SynchronizationPolicyDecision.ADD; + } + } + } + + @Override + public boolean isDirectlyAssigned(F focusType, String targetOid) { + for (AssignmentType assignment : focusType.getAssignment()) { + ObjectReferenceType targetRef = assignment.getTargetRef(); + if (targetRef != null && targetRef.getOid().equals(targetOid)) { + return true; + } + } + return false; + } + + @Override + public boolean isDirectlyAssigned(F focusType, ObjectType target) { + return isDirectlyAssigned(focusType, target.getOid()); + } + + @Override + public boolean isDirectlyAssigned(String targetOid) { + ModelContext ctx = ModelExpressionThreadLocalHolder.getLensContext(); + if (ctx == null) { + throw new IllegalStateException("No lens context"); + } + ModelElementContext focusContext = ctx.getFocusContext(); + if (focusContext == null) { + throw new IllegalStateException("No focus in lens context"); + } + + PrismObject focus; + ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal(); + if (scriptContext == null) { + focus = focusContext.getObjectAny(); + } else if (scriptContext.isEvaluateNew()) { + // Evaluating new state + if (focusContext.isDelete()) { + return false; + } + focus = focusContext.getObjectNew(); + } else { + // Evaluating old state + if (focusContext.isAdd()) { + return false; + } + focus = focusContext.getObjectOld(); + } + if (focus == null) { + return false; + } + return isDirectlyAssigned(focus.asObjectable(), targetOid); + } + + @Override + public boolean isDirectlyAssigned(ObjectType target) { + return isDirectlyAssigned(target.getOid()); + } + + // EXPERIMENTAL!! + @SuppressWarnings("unused") + @Experimental + public boolean hasActiveAssignmentTargetSubtype(String roleSubtype) { + ModelContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); + if (lensContext == null) { + throw new UnsupportedOperationException("hasActiveAssignmentRoleSubtype works only with model context"); + } + DeltaSetTriple> evaluatedAssignmentTriple = lensContext.getEvaluatedAssignmentTriple(); + if (evaluatedAssignmentTriple == null) { + throw new UnsupportedOperationException("hasActiveAssignmentRoleSubtype works only with evaluatedAssignmentTriple"); + } + Collection> nonNegativeEvaluatedAssignments = evaluatedAssignmentTriple.getNonNegativeValues(); + for (EvaluatedAssignment nonNegativeEvaluatedAssignment : nonNegativeEvaluatedAssignments) { + PrismObject target = nonNegativeEvaluatedAssignment.getTarget(); + if (target == null) { + continue; + } + //noinspection unchecked + Collection targetSubtypes = FocusTypeUtil.determineSubTypes((PrismObject) target); + if (targetSubtypes.contains(roleSubtype)) { + return true; + } + } + return false; + } + + @Override + public ShadowType getLinkedShadow(FocusType focus, ResourceType resource) throws SchemaException, + SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + return getLinkedShadow(focus, resource.getOid()); + } + + @Override + public ShadowType getLinkedShadow(FocusType focus, ResourceType resource, boolean repositoryObjectOnly) + throws SchemaException, + SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + return getLinkedShadow(focus, resource.getOid(), repositoryObjectOnly); + } + + @Override + public ShadowType getLinkedShadow(FocusType focus, String resourceOid) + throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + return getLinkedShadow(focus, resourceOid, false); + } + + @Override + public ShadowType getLinkedShadow(FocusType focus, String resourceOid, boolean repositoryObjectOnly) + throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + if (focus == null) { + return null; + } + List linkRefs = focus.getLinkRef(); + for (ObjectReferenceType linkRef : linkRefs) { + ShadowType shadowType; + try { + shadowType = getObject(ShadowType.class, linkRef.getOid(), + SelectorOptions.createCollection(GetOperationOptions.createNoFetch())); + } catch (ObjectNotFoundException e) { + // Shadow is gone in the meantime. MidPoint will resolve that by itself. + // It is safe to ignore this error in this method. + LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus + + " because it no longer exists in repository"); + continue; + } + if (shadowType.getResourceRef().getOid().equals(resourceOid)) { + // We have repo shadow here. Re-read resource shadow if necessary. + if (!repositoryObjectOnly) { + try { + shadowType = getObject(ShadowType.class, shadowType.getOid()); + } catch (ObjectNotFoundException e) { + // Shadow is gone in the meantime. MidPoint will resolve that by itself. + // It is safe to ignore this error in this method. + LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus + + " because it no longer exists on resource"); + continue; + } + } + return shadowType; + } + } + return null; + } + + @Override + public ShadowType getLinkedShadow(FocusType focus, String resourceOid, ShadowKindType kind, String intent) + throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + return getLinkedShadow(focus, resourceOid, kind, intent, false); + } + + @Override + public ShadowType getLinkedShadow(FocusType focus, String resourceOid, ShadowKindType kind, String intent, + boolean repositoryObjectOnly) + throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + List linkRefs = focus.getLinkRef(); + for (ObjectReferenceType linkRef : linkRefs) { + ShadowType shadowType; + try { + shadowType = getObject(ShadowType.class, linkRef.getOid(), + SelectorOptions.createCollection(GetOperationOptions.createNoFetch())); + } catch (ObjectNotFoundException e) { + // Shadow is gone in the meantime. MidPoint will resolve that by itself. + // It is safe to ignore this error in this method. + LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus + + " because it no longer exists in repository"); + continue; + } + if (ShadowUtil.matches(shadowType, resourceOid, kind, intent)) { + // We have repo shadow here. Re-read resource shadow if necessary. + if (!repositoryObjectOnly) { + try { + shadowType = getObject(ShadowType.class, shadowType.getOid()); + } catch (ObjectNotFoundException e) { + // Shadow is gone in the meantime. MidPoint will resolve that by itself. + // It is safe to ignore this error in this method. + LOGGER.trace("Ignoring shadow " + linkRef.getOid() + " linked in " + focus + + " because it no longer exists on resource"); + continue; + } + } + return shadowType; + } + } + return null; + } + + @Override + public boolean isFullShadow() { + ModelProjectionContext projectionContext = getProjectionContext(); + if (projectionContext == null) { + LOGGER.debug("Call to isFullShadow while there is no projection context"); + return false; + } + return projectionContext.isFullShadow(); + } + + @Override + public boolean isProjectionExists() { + ModelProjectionContext projectionContext = getProjectionContext(); + if (projectionContext == null) { + return false; + } + return projectionContext.isExists(); + } + + @Override + public Integer countAccounts(String resourceOid, QName attributeName, T attributeValue) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + OperationResult result = getCurrentResult(CLASS_DOT + "countAccounts"); + ResourceType resourceType = modelObjectResolver.getObjectSimple(ResourceType.class, resourceOid, null, null, result); + return countAccounts(resourceType, attributeName, attributeValue, getCurrentTask(), result); + } + + @Override + public Integer countAccounts(ResourceType resourceType, QName attributeName, T attributeValue) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "countAccounts"); + return countAccounts(resourceType, attributeName, attributeValue, getCurrentTask(), result); + } + + @Override + public Integer countAccounts(ResourceType resourceType, String attributeName, T attributeValue) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "countAccounts"); + QName attributeQName = new QName(ResourceTypeUtil.getResourceNamespace(resourceType), attributeName); + return countAccounts(resourceType, attributeQName, attributeValue, getCurrentTask(), result); + } + + private Integer countAccounts(ResourceType resourceType, QName attributeName, T attributeValue, Task task, + OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + ObjectQuery query = createAttributeQuery(resourceType, attributeName, attributeValue); + return modelObjectResolver.countObjects(ShadowType.class, query, null, task, result); + } + + @Override + public boolean isUniquePropertyValue(ObjectType objectType, String propertyPathString, T propertyValue) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + Validate.notEmpty(propertyPathString, "Empty property path"); + OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "isUniquePropertyValue"); + ItemPath propertyPath = prismContext.itemPathParser().asItemPath(propertyPathString); + return isUniquePropertyValue(objectType, propertyPath, propertyValue, getCurrentTask(), result); + } + + private boolean isUniquePropertyValue(final ObjectType objectType, ItemPath propertyPath, T propertyValue, Task task, + OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + List conflictingObjects = getObjectsInConflictOnPropertyValue(objectType, propertyPath, + propertyValue, null, false, task, result); + return conflictingObjects.isEmpty(); + } + + @Override + public List getObjectsInConflictOnPropertyValue(O objectType, String propertyPathString, + T propertyValue, boolean getAllConflicting) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + return getObjectsInConflictOnPropertyValue(objectType, propertyPathString, propertyValue, + PrismConstants.DEFAULT_MATCHING_RULE_NAME.getLocalPart(), getAllConflicting); + } + + public List getObjectsInConflictOnPropertyValue(O objectType, String propertyPathString, + T propertyValue, String matchingRuleName, boolean getAllConflicting) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + Validate.notEmpty(propertyPathString, "Empty property path"); + OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "getObjectsInConflictOnPropertyValue"); + ItemPath propertyPath = prismContext.itemPathParser().asItemPath(propertyPathString); + QName matchingRuleQName = new QName(matchingRuleName); // no namespace for now + return getObjectsInConflictOnPropertyValue(objectType, propertyPath, propertyValue, matchingRuleQName, getAllConflicting, + getCurrentTask(), result); + } + + private List getObjectsInConflictOnPropertyValue(final O objectType, ItemPath propertyPath, + T propertyValue, QName matchingRule, final boolean getAllConflicting, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + Validate.notNull(objectType, "Null object"); + Validate.notNull(propertyPath, "Null property path"); + Validate.notNull(propertyValue, "Null property value"); + PrismPropertyDefinition propertyDefinition = objectType.asPrismObject().getDefinition() + .findPropertyDefinition(propertyPath); + if (matchingRule == null) { + if (propertyDefinition != null && PolyStringType.COMPLEX_TYPE.equals(propertyDefinition.getTypeName())) { + matchingRule = PrismConstants.POLY_STRING_ORIG_MATCHING_RULE_NAME; + } else { + matchingRule = PrismConstants.DEFAULT_MATCHING_RULE_NAME; + } + } + ObjectQuery query = prismContext.queryFor(objectType.getClass()) + .item(propertyPath, propertyDefinition).eq(propertyValue).matching(matchingRule) + .build(); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Determining uniqueness of property {} using query:\n{}", propertyPath, query.debugDump()); + } + + final List conflictingObjects = new ArrayList<>(); + ResultHandler handler = (object, parentResult) -> { + if (objectType.getOid() == null) { + // We have found a conflicting object + conflictingObjects.add(object.asObjectable()); + return getAllConflicting; + } else { + if (objectType.getOid().equals(object.getOid())) { + // We have found ourselves. No conflict (yet). Just go on. + return true; + } else { + // We have found someone else. Conflict. + conflictingObjects.add(object.asObjectable()); + return getAllConflicting; + } + } + }; + + //noinspection unchecked + modelObjectResolver.searchIterative((Class) objectType.getClass(), query, null, handler, task, result); + + return conflictingObjects; + } + + @Override + public boolean isUniqueAccountValue(ResourceType resourceType, ShadowType shadowType, String attributeName, + T attributeValue) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + Validate.notEmpty(attributeName, "Empty attribute name"); + OperationResult result = getCurrentResult(MidpointFunctions.class.getName() + "isUniqueAccountValue"); + QName attributeQName = new QName(ResourceTypeUtil.getResourceNamespace(resourceType), attributeName); + return isUniqueAccountValue(resourceType, shadowType, attributeQName, attributeValue, getCurrentTask(), result); + } + + private boolean isUniqueAccountValue(ResourceType resourceType, final ShadowType shadowType, + QName attributeName, T attributeValue, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + Validate.notNull(resourceType, "Null resource"); + Validate.notNull(shadowType, "Null shadow"); + Validate.notNull(attributeName, "Null attribute name"); + Validate.notNull(attributeValue, "Null attribute value"); + + ObjectQuery query = createAttributeQuery(resourceType, attributeName, attributeValue); + LOGGER.trace("Determining uniqueness of attribute {} using query:\n{}", attributeName, query.debugDumpLazily()); + + final Holder isUniqueHolder = new Holder<>(true); + ResultHandler handler = (object, parentResult) -> { + if (shadowType.getOid() == null) { + // We have found a conflicting object + isUniqueHolder.setValue(false); + return false; + } else { + if (shadowType.getOid().equals(object.getOid())) { + // We have found ourselves. No conflict (yet). Just go on. + return true; + } else { + // We have found someone else. Conflict. + isUniqueHolder.setValue(false); + return false; + } + } + }; + + modelObjectResolver.searchIterative(ShadowType.class, query, null, handler, task, result); + + return isUniqueHolder.getValue(); + } + + private ObjectQuery createAttributeQuery(ResourceType resourceType, QName attributeName, T attributeValue) throws SchemaException { + RefinedResourceSchema rSchema = RefinedResourceSchemaImpl.getRefinedSchema(resourceType); + RefinedObjectClassDefinition rAccountDef = rSchema.getDefaultRefinedDefinition(ShadowKindType.ACCOUNT); + RefinedAttributeDefinition attrDef = rAccountDef.findAttributeDefinition(attributeName); + if (attrDef == null) { + throw new SchemaException("No attribute '" + attributeName + "' in " + rAccountDef); + } + return prismContext.queryFor(ShadowType.class) + .itemWithDef(attrDef, ShadowType.F_ATTRIBUTES, attrDef.getItemName()).eq(attributeValue) + .and().item(ShadowType.F_OBJECT_CLASS).eq(rAccountDef.getObjectClassDefinition().getTypeName()) + .and().item(ShadowType.F_RESOURCE_REF).ref(resourceType.getOid()) + .build(); + } + + @Override + public ModelContext getModelContext() { + return ModelExpressionThreadLocalHolder.getLensContext(); + } + + @Override + public ModelElementContext getFocusContext() { + ModelContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); + if (lensContext == null) { + return null; + } + //noinspection unchecked + return (ModelElementContext) lensContext.getFocusContext(); + } + + @Override + public ModelProjectionContext getProjectionContext() { + return ModelExpressionThreadLocalHolder.getProjectionContext(); + } + + @Override + public Mapping getMapping() { + return ModelExpressionThreadLocalHolder.getMapping(); + } + + @Override + public Task getCurrentTask() { + Task rv = ModelExpressionThreadLocalHolder.getCurrentTask(); + if (rv == null) { + // fallback (MID-4130): but maybe we should instead make sure ModelExpressionThreadLocalHolder is set up correctly + ScriptExpressionEvaluationContext ctx = ScriptExpressionEvaluationContext.getThreadLocal(); + if (ctx != null) { + rv = ctx.getTask(); + } + } + return rv; + } + + @Override + public OperationResult getCurrentResult() { + // This is the most current operation result, reflecting e.g. the fact that mapping evaluation was started. + ScriptExpressionEvaluationContext ctx = ScriptExpressionEvaluationContext.getThreadLocal(); + if (ctx != null) { + return ctx.getResult(); + } else { + // This is a bit older. But better than nothing. + return ModelExpressionThreadLocalHolder.getCurrentResult(); + } + } + + @Override + public OperationResult getCurrentResult(String operationName) { + OperationResult currentResult = getCurrentResult(); + if (currentResult != null) { + return currentResult; + } else { + LOGGER.warn("No operation result for {}, creating a new one", operationName); + return new OperationResult(operationName); + } + } + + // functions working with ModelContext + + @Override + public ModelContext unwrapModelContext(LensContextType lensContextType) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + return LensContext.fromLensContextType(lensContextType, prismContext, provisioningService, getCurrentTask(), + getCurrentResult(MidpointFunctions.class.getName() + "getObject")); + } + + @Override + public LensContextType wrapModelContext(ModelContext lensContext) throws SchemaException { + return ((LensContext) lensContext).toLensContextType(); + } + + // Convenience functions + + @Override + public T createEmptyObject(Class type) throws SchemaException { + PrismObjectDefinition objectDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type); + PrismObject object = objectDefinition.instantiate(); + return object.asObjectable(); + } + + @Override + public T createEmptyObjectWithName(Class type, String name) throws SchemaException { + T objectType = createEmptyObject(type); + objectType.setName(new PolyStringType(name)); + return objectType; + } + + @Override + public T createEmptyObjectWithName(Class type, PolyString name) throws SchemaException { + T objectType = createEmptyObject(type); + objectType.setName(new PolyStringType(name)); + return objectType; + } + + @Override + public T createEmptyObjectWithName(Class type, PolyStringType name) throws SchemaException { + T objectType = createEmptyObject(type); + objectType.setName(name); + return objectType; + } + + // Functions accessing modelService + + @Override + public T resolveReference(ObjectReferenceType reference) + throws ObjectNotFoundException, SchemaException, + CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + if (reference == null) { + return null; + } + QName type = reference.getType(); // TODO what about implicitly specified types, like in resourceRef? + PrismObjectDefinition objectDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByType(reference.getType()); + if (objectDefinition == null) { + throw new SchemaException("No definition for type " + type); + } + return modelService.getObject( + objectDefinition.getCompileTimeClass(), reference.getOid(), + SelectorOptions.createCollection(GetOperationOptions.createExecutionPhase()), getCurrentTask(), getCurrentResult()) + .asObjectable(); + } + + @Override + public T resolveReferenceIfExists(ObjectReferenceType reference) + throws SchemaException, + CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + try { + return resolveReference(reference); + } catch (ObjectNotFoundException e) { + return null; + } + } + + @Override + public T getObject(Class type, String oid, + Collection> options) + throws ObjectNotFoundException, SchemaException, + CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + return modelService.getObject(type, oid, options, getCurrentTask(), getCurrentResult()).asObjectable(); + } + + @Override + public T getObject(Class type, String oid) throws ObjectNotFoundException, SchemaException, + CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + PrismObject prismObject = modelService.getObject(type, oid, + getDefaultGetOptionCollection(), + getCurrentTask(), getCurrentResult()); + return prismObject.asObjectable(); + } + + @Override + public void executeChanges( + Collection> deltas, + ModelExecuteOptions options) throws ObjectAlreadyExistsException, + ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, + ConfigurationException, PolicyViolationException, + SecurityViolationException { + modelService.executeChanges(deltas, options, getCurrentTask(), getCurrentResult()); + } + + @Override + public void executeChanges( + Collection> deltas) + throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, + PolicyViolationException, SecurityViolationException { + modelService.executeChanges(deltas, null, getCurrentTask(), getCurrentResult()); + } + + @SafeVarargs + @Override + public final void executeChanges(ObjectDelta... deltas) + throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, + PolicyViolationException, SecurityViolationException { + Collection> deltaCollection = MiscSchemaUtil.createCollection(deltas); + modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult()); + } + + @Override + public String addObject(PrismObject newObject, + ModelExecuteOptions options) throws ObjectAlreadyExistsException, + ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, + ConfigurationException, PolicyViolationException, + SecurityViolationException { + ObjectDelta delta = DeltaFactory.Object.createAddDelta(newObject); + Collection> deltaCollection = MiscSchemaUtil.createCollection(delta); + Collection> executedChanges = modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult()); + String oid = ObjectDeltaOperation.findAddDeltaOid(executedChanges, newObject); + newObject.setOid(oid); + return oid; + } + + @Override + public String addObject(PrismObject newObject) + throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, + PolicyViolationException, SecurityViolationException { + return addObject(newObject, null); + } + + @Override + public String addObject(T newObject, + ModelExecuteOptions options) throws ObjectAlreadyExistsException, + ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, + ConfigurationException, PolicyViolationException, + SecurityViolationException { + return addObject(newObject.asPrismObject(), options); + } + + @Override + public String addObject(T newObject) + throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, + PolicyViolationException, SecurityViolationException { + return addObject(newObject.asPrismObject(), null); + } + + @Override + public void modifyObject(ObjectDelta modifyDelta, + ModelExecuteOptions options) throws ObjectAlreadyExistsException, + ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, + ConfigurationException, PolicyViolationException, + SecurityViolationException { + Collection> deltaCollection = MiscSchemaUtil.createCollection(modifyDelta); + modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult()); + } + + @Override + public void modifyObject(ObjectDelta modifyDelta) + throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, + PolicyViolationException, SecurityViolationException { + Collection> deltaCollection = MiscSchemaUtil.createCollection(modifyDelta); + modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult()); + } + + @Override + public void deleteObject(Class type, String oid, + ModelExecuteOptions options) throws ObjectAlreadyExistsException, + ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, + ConfigurationException, PolicyViolationException, + SecurityViolationException { + ObjectDelta deleteDelta = prismContext.deltaFactory().object().createDeleteDelta(type, oid); + Collection> deltaCollection = MiscSchemaUtil.createCollection(deleteDelta); + modelService.executeChanges(deltaCollection, options, getCurrentTask(), getCurrentResult()); + } + + @Override + public void deleteObject(Class type, String oid) + throws ObjectAlreadyExistsException, ObjectNotFoundException, + SchemaException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, + PolicyViolationException, SecurityViolationException { + ObjectDelta deleteDelta = prismContext.deltaFactory().object().createDeleteDelta(type, oid); + Collection> deltaCollection = MiscSchemaUtil.createCollection(deleteDelta); + modelService.executeChanges(deltaCollection, null, getCurrentTask(), getCurrentResult()); + } + + @Override + public void recompute(Class type, String oid) throws SchemaException, PolicyViolationException, + ExpressionEvaluationException, ObjectNotFoundException, + ObjectAlreadyExistsException, CommunicationException, + ConfigurationException, SecurityViolationException { + modelService.recompute(type, oid, null, getCurrentTask(), getCurrentResult()); + } + + @Override + public PrismObject findShadowOwner(String accountOid) + throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException, + ExpressionEvaluationException, CommunicationException { + return modelService.findShadowOwner(accountOid, getCurrentTask(), getCurrentResult()); + } + + @Override + public PrismObject searchShadowOwner(String accountOid) + throws ObjectNotFoundException, SecurityViolationException, SchemaException, ConfigurationException, + ExpressionEvaluationException, CommunicationException { + //noinspection unchecked + return (PrismObject) modelService.searchShadowOwner(accountOid, null, getCurrentTask(), getCurrentResult()); + } + + @Override + public List searchObjects( + Class type, ObjectQuery query, + Collection> options) + throws SchemaException, ObjectNotFoundException, + SecurityViolationException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + return MiscSchemaUtil.toObjectableList( + modelService.searchObjects(type, query, options, getCurrentTask(), getCurrentResult())); + } + + @Override + public List searchObjects( + Class type, ObjectQuery query) throws SchemaException, + ObjectNotFoundException, SecurityViolationException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { + return MiscSchemaUtil.toObjectableList( + modelService.searchObjects(type, query, + getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult())); + } + + @Override + public void searchObjectsIterative(Class type, + ObjectQuery query, ResultHandler handler, + Collection> options) + throws SchemaException, ObjectNotFoundException, + CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + modelService.searchObjectsIterative(type, query, handler, options, getCurrentTask(), getCurrentResult()); + } + + @Override + public void searchObjectsIterative(Class type, + ObjectQuery query, ResultHandler handler) + throws SchemaException, ObjectNotFoundException, + CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + modelService.searchObjectsIterative(type, query, handler, + getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); + } + + @Override + public T searchObjectByName(Class type, String name) + throws SecurityViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SchemaException, ExpressionEvaluationException { + ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext); + List> foundObjects = modelService + .searchObjects(type, nameQuery, + getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); + if (foundObjects.isEmpty()) { + return null; + } + if (foundObjects.size() > 1) { + throw new IllegalStateException("More than one object found for type " + type + " and name '" + name + "'"); + } + return foundObjects.iterator().next().asObjectable(); + } + + @Override + public T searchObjectByName(Class type, PolyString name) + throws SecurityViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SchemaException, ExpressionEvaluationException { + ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext); + List> foundObjects = modelService + .searchObjects(type, nameQuery, + getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); + if (foundObjects.isEmpty()) { + return null; + } + if (foundObjects.size() > 1) { + throw new IllegalStateException("More than one object found for type " + type + " and name '" + name + "'"); + } + return foundObjects.iterator().next().asObjectable(); + } + + @Override + public T searchObjectByName(Class type, PolyStringType name) + throws SecurityViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SchemaException, ExpressionEvaluationException { + ObjectQuery nameQuery = ObjectQueryUtil.createNameQuery(name, prismContext); + List> foundObjects = modelService + .searchObjects(type, nameQuery, + getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); + if (foundObjects.isEmpty()) { + return null; + } + if (foundObjects.size() > 1) { + throw new IllegalStateException("More than one object found for type " + type + " and name '" + name + "'"); + } + return foundObjects.iterator().next().asObjectable(); + } + + @Override + public int countObjects(Class type, + ObjectQuery query, + Collection> options) + throws SchemaException, ObjectNotFoundException, + SecurityViolationException, ConfigurationException, + CommunicationException, ExpressionEvaluationException { + return modelService.countObjects(type, query, options, getCurrentTask(), getCurrentResult()); + } + + @Override + public int countObjects(Class type, + ObjectQuery query) throws SchemaException, ObjectNotFoundException, + SecurityViolationException, ConfigurationException, + CommunicationException, ExpressionEvaluationException { + return modelService.countObjects(type, query, + getDefaultGetOptionCollection(), getCurrentTask(), getCurrentResult()); + } + + @Override + public OperationResult testResource(String resourceOid) + throws ObjectNotFoundException { + return modelService.testResource(resourceOid, getCurrentTask()); + } + + @Override + public ObjectDeltaType getResourceDelta(ModelContext context, String resourceOid) throws SchemaException { + List> deltas = new ArrayList<>(); + for (Object modelProjectionContextObject : context.getProjectionContexts()) { + LensProjectionContext lensProjectionContext = (LensProjectionContext) modelProjectionContextObject; + if (lensProjectionContext.getResourceShadowDiscriminator() != null && + resourceOid.equals(lensProjectionContext.getResourceShadowDiscriminator().getResourceOid())) { + deltas.add(lensProjectionContext.getDelta()); // union of primary and secondary deltas + } + } + ObjectDelta sum = ObjectDeltaCollectionsUtil.summarize(deltas); + return DeltaConvertor.toObjectDeltaType(sum); + } + + @Override + public long getSequenceCounter(String sequenceOid) throws ObjectNotFoundException, SchemaException { + return SequentialValueExpressionEvaluator.getSequenceCounter(sequenceOid, repositoryService, getCurrentResult()); + } + + // orgstruct related methods + + @Override + public Collection getManagersOids(UserType user) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getManagersOids(user, false); + } + + @Override + public Collection getOrgUnits(UserType user, QName relation) { + return orgStructFunctions.getOrgUnits(user, relation, false); + } + + @Override + public OrgType getParentOrgByOrgType(ObjectType object, String orgType) throws SchemaException, SecurityViolationException { + return orgStructFunctions.getParentOrgByOrgType(object, orgType, false); + } + + @Override + public OrgType getParentOrgByArchetype(ObjectType object, String archetypeOid) throws SchemaException, SecurityViolationException { + return orgStructFunctions.getParentOrgByArchetype(object, archetypeOid, false); + } + + @Override + public OrgType getOrgByOid(String oid) throws SchemaException { + return orgStructFunctions.getOrgByOid(oid, false); + } + + @Override + public Collection getParentOrgs(ObjectType object) throws SchemaException, SecurityViolationException { + return orgStructFunctions.getParentOrgs(object, false); + } + + @Override + public Collection getOrgUnits(UserType user) { + return orgStructFunctions.getOrgUnits(user, false); + } + + @Override + public Collection getManagersOfOrg(String orgOid) throws SchemaException, SecurityViolationException { + return orgStructFunctions.getManagersOfOrg(orgOid, false); + } + + @Override + public boolean isManagerOfOrgType(UserType user, String orgType) throws SchemaException { + return orgStructFunctions.isManagerOfOrgType(user, orgType, false); + } + + @Override + public Collection getManagers(UserType user) throws SchemaException, SecurityViolationException { + return orgStructFunctions.getManagers(user, false); + } + + @Override + public Collection getManagersByOrgType(UserType user, String orgType) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getManagersByOrgType(user, orgType, false); + } + + @Override + public boolean isManagerOf(UserType user, String orgOid) { + return orgStructFunctions.isManagerOf(user, orgOid, false); + } + + @Override + public Collection getParentOrgsByRelation(ObjectType object, String relation) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getParentOrgsByRelation(object, relation, false); + } + + @Override + public Collection getManagers(UserType user, String orgType, boolean allowSelf) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getManagers(user, orgType, allowSelf, false); + } + + @Override + public Collection getParentOrgs(ObjectType object, String relation, String orgType) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getParentOrgs(object, relation, orgType, false); + } + + @Override + public Collection getManagersOidsExceptUser(UserType user) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getManagersOidsExceptUser(user, false); + } + + @Override + public Collection getManagersOidsExceptUser(@NotNull Collection userRefList) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + return orgStructFunctions.getManagersOidsExceptUser(userRefList, false); + } + + @Override + public OrgType getOrgByName(String name) throws SchemaException, SecurityViolationException { + return orgStructFunctions.getOrgByName(name, false); + } + + @Override + public Collection getParentOrgsByRelation(ObjectType object, QName relation) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getParentOrgsByRelation(object, relation, false); + } + + @Override + public Collection getParentOrgs(ObjectType object, QName relation, String orgType) + throws SchemaException, SecurityViolationException { + return orgStructFunctions.getParentOrgs(object, relation, orgType, false); + } + + @Override + public boolean isManager(UserType user) { + return orgStructFunctions.isManager(user); + } + + @Override + public Protector getProtector() { + return protector; + } + + @Override + public String getPlaintext(ProtectedStringType protectedStringType) throws EncryptionException { + if (protectedStringType != null) { + return protector.decryptString(protectedStringType); + } else { + return null; + } + } + + @Override + public Map parseXmlToMap(String xml) { + Map resultingMap = new HashMap<>(); + if (xml != null && !xml.isEmpty()) { + XMLInputFactory factory = XMLInputFactory.newInstance(); + String value = ""; + String startName = ""; + InputStream stream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + boolean isRootElement = true; + try { + XMLEventReader eventReader = factory.createXMLEventReader(stream); + while (eventReader.hasNext()) { + + XMLEvent event = eventReader.nextEvent(); + int code = event.getEventType(); + if (code == XMLStreamConstants.START_ELEMENT) { + + StartElement startElement = event.asStartElement(); + startName = startElement.getName().getLocalPart(); + if (!isRootElement) { + resultingMap.put(startName, null); + } else { + isRootElement = false; + } + } else if (code == XMLStreamConstants.CHARACTERS) { + Characters characters = event.asCharacters(); + if (!characters.isWhiteSpace()) { + + StringBuilder valueBuilder; + if (value != null) { + valueBuilder = new StringBuilder(value).append(" ").append(characters.getData()); + } else { + valueBuilder = new StringBuilder(characters.getData()); + } + value = valueBuilder.toString(); + } + } else if (code == XMLStreamConstants.END_ELEMENT) { + + EndElement endElement = event.asEndElement(); + String endName = endElement.getName().getLocalPart(); + + if (endName.equals(startName)) { + if (value != null) { + resultingMap.put(endName, value); + value = null; + } + } else { + LOGGER.trace("No value between xml tags, tag name : {}", endName); + } + + } else if (code == XMLStreamConstants.END_DOCUMENT) { + isRootElement = true; + } + } + } catch (XMLStreamException e) { + + StringBuilder error = new StringBuilder("Xml stream exception wile parsing xml string") + .append(e.getLocalizedMessage()); + throw new SystemException(error.toString()); + } + } else { + LOGGER.trace("Input xml string null or empty."); + } + return resultingMap; + } + + @Override + public List getMembersAsReferences(String orgOid) throws SchemaException, SecurityViolationException, + CommunicationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException { + return getMembers(orgOid).stream() + .map(obj -> createObjectRef(obj, prismContext)) + .collect(Collectors.toList()); + } + + @Override + public List getMembers(String orgOid) throws SchemaException, ObjectNotFoundException, SecurityViolationException, + CommunicationException, ConfigurationException, ExpressionEvaluationException { + ObjectQuery query = prismContext.queryFor(UserType.class) + .isDirectChildOf(orgOid) + .build(); + return searchObjects(UserType.class, query, null); + } + + @Override + public String computeProjectionLifecycle(F focus, ShadowType shadow, ResourceType resource) { + if (focus == null || shadow == null) { + return null; + } + if (!(focus instanceof UserType)) { + return null; + } + if (shadow.getKind() != null && shadow.getKind() != ShadowKindType.ACCOUNT) { + return null; + } + ProtectedStringType focusPasswordPs = FocusTypeUtil.getPasswordValue((UserType) focus); + if (focusPasswordPs != null && focusPasswordPs.canGetCleartext()) { + return null; + } + CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil + .getEffectiveCapability(resource, CredentialsCapabilityType.class); + if (credentialsCapabilityType == null) { + return null; + } + PasswordCapabilityType passwordCapabilityType = credentialsCapabilityType.getPassword(); + if (passwordCapabilityType == null) { + return null; + } + if (passwordCapabilityType.isEnabled() == Boolean.FALSE) { + return null; + } + return SchemaConstants.LIFECYCLE_PROPOSED; + } + + public MidPointPrincipal getPrincipal() throws SecurityViolationException { + return securityContextManager.getPrincipal(); + } + + @Override + public String getPrincipalOid() { + return securityContextManager.getPrincipalOid(); + } + + @Override + public String getChannel() { + Task task = getCurrentTask(); + return task != null ? task.getChannel() : null; + } + + @Override + public WorkflowService getWorkflowService() { + return workflowService; + } + + @Override + public List getShadowsToActivate(Collection projectionContexts) { + List shadows = new ArrayList<>(); + + //noinspection unchecked + for (ModelElementContext projectionCtx : projectionContexts) { + + List executedShadowDeltas = projectionCtx.getExecutedDeltas(); + //noinspection unchecked + for (ObjectDeltaOperation shadowDelta : executedShadowDeltas) { + if (shadowDelta.getExecutionResult().getStatus() == OperationResultStatus.SUCCESS + && shadowDelta.getObjectDelta().getChangeType() == ChangeType.ADD) { + PrismObject shadow = shadowDelta.getObjectDelta().getObjectToAdd(); + PrismProperty pLifecycleState = shadow.findProperty(ShadowType.F_LIFECYCLE_STATE); + if (pLifecycleState != null && !pLifecycleState.isEmpty() && SchemaConstants.LIFECYCLE_PROPOSED + .equals(pLifecycleState.getRealValue())) { + shadows.add(shadow.asObjectable()); + } + + } + } + } + return shadows; + } + + @Override + public String createRegistrationConfirmationLink(UserType userType) { + SecurityPolicyType securityPolicy = resolveSecurityPolicy(userType.asPrismObject()); + if (securityPolicy != null && securityPolicy.getAuthentication() != null + && securityPolicy.getAuthentication().getSequence() != null && !securityPolicy.getAuthentication().getSequence().isEmpty()) { + if (securityPolicy.getRegistration() != null && securityPolicy.getRegistration().getSelfRegistration() != null + && securityPolicy.getRegistration().getSelfRegistration().getAdditionalAuthenticationName() != null) { + String resetPasswordSequenceName = securityPolicy.getRegistration().getSelfRegistration().getAdditionalAuthenticationName(); + String prefix = createPrefixLinkByAuthSequence(SchemaConstants.CHANNEL_GUI_SELF_REGISTRATION_URI, resetPasswordSequenceName, securityPolicy.getAuthentication().getSequence()); + if (prefix != null) { + return createTokenConfirmationLink(prefix, userType); + } + } + } + return createTokenConfirmationLink(SchemaConstants.REGISTRATION_CONFIRAMTION_PREFIX, userType); + } + + @Override + public String createPasswordResetLink(UserType userType) { + SecurityPolicyType securityPolicy = resolveSecurityPolicy(userType.asPrismObject()); + if (securityPolicy != null && securityPolicy.getAuthentication() != null + && securityPolicy.getAuthentication().getSequence() != null && !securityPolicy.getAuthentication().getSequence().isEmpty()) { + if (securityPolicy.getCredentialsReset() != null && securityPolicy.getCredentialsReset().getAuthenticationSequenceName() != null) { + String resetPasswordSequenceName = securityPolicy.getCredentialsReset().getAuthenticationSequenceName(); + String prefix = createPrefixLinkByAuthSequence(SchemaConstants.CHANNEL_GUI_RESET_PASSWORD_URI, resetPasswordSequenceName, securityPolicy.getAuthentication().getSequence()); + if (prefix != null) { + return createTokenConfirmationLink(prefix, userType); + } + } + } + return createTokenConfirmationLink(SchemaConstants.PASSWORD_RESET_CONFIRMATION_PREFIX, userType); + } + + @Override + public String createAccountActivationLink(UserType userType) { + return createBaseConfirmationLink(SchemaConstants.ACCOUNT_ACTIVATION_PREFIX, userType.getOid()); + } + + private String createBaseConfirmationLink(String prefix, UserType userType) { + return getPublicHttpUrlPattern() + prefix + "?" + SchemaConstants.USER_ID + "=" + userType.getName().getOrig(); + } + + private String createBaseConfirmationLink(String prefix, String oid) { + return getPublicHttpUrlPattern() + prefix + "?" + SchemaConstants.USER_ID + "=" + oid; + } + + private String createTokenConfirmationLink(String prefix, UserType userType) { + return createBaseConfirmationLink(prefix, userType) + "&" + SchemaConstants.TOKEN + "=" + getNonce(userType); + } + + private String createPrefixLinkByAuthSequence(String channel, String nameOfSequence, Collection sequences){ + AuthenticationSequenceType sequenceByName = null; + AuthenticationSequenceType defaultSequence = null; + for (AuthenticationSequenceType sequenceType : sequences) { + if (sequenceType.getName().equals(nameOfSequence)) { + sequenceByName = sequenceType; + break; + } else if (sequenceType.getChannel().getChannelId().equals(channel) + && Boolean.TRUE.equals(sequenceType.getChannel().isDefault())) { + defaultSequence = sequenceType; + } + } + AuthenticationSequenceType usedSequence = sequenceByName != null ? sequenceByName : defaultSequence; + if (usedSequence != null) { + String sequecnceSuffix = usedSequence.getChannel().getUrlSuffix(); + String prefix = (sequecnceSuffix.startsWith("/")) ? sequecnceSuffix : ("/" + sequecnceSuffix); + return SchemaConstants.AUTH_MODULE_PREFIX + prefix; + } + return null; + } + + private SecurityPolicyType resolveSecurityPolicy(PrismObject user) { + return securityContextManager.runPrivileged(new Producer() { + private static final long serialVersionUID = 1L; + + @Override + public SecurityPolicyType run() { + + Task task = taskManager.createTaskInstance("load security policy"); + + Task currentTask = getCurrentTask(); + task.setChannel(currentTask != null ? currentTask.getChannel() : null); + + OperationResult result = new OperationResult("load security policy"); + + try { + return modelInteractionService.getSecurityPolicy(user, task, result); + } catch (CommonException e) { + LOGGER.error("Could not retrieve security policy: {}", e.getMessage(), e); + return null; + } + + } + + }); + } + + private String getPublicHttpUrlPattern() { + SystemConfigurationType systemConfiguration; + try { + systemConfiguration = modelInteractionService.getSystemConfiguration(getCurrentResult()); + } catch (ObjectNotFoundException | SchemaException e) { + LOGGER.error("Error while getting system configuration. ", e); + return null; + } + if (systemConfiguration == null) { + LOGGER.trace("No system configuration defined. Skipping link generation."); + return null; + } + String host = null; + HttpConnectionInformation connectionInf = SecurityUtil.getCurrentConnectionInformation(); + if (connectionInf != null) { + host = connectionInf.getServerName(); + } + String publicHttpUrlPattern = SystemConfigurationTypeUtil.getPublicHttpUrlPattern(systemConfiguration, host); + if (StringUtils.isBlank(publicHttpUrlPattern)) { + LOGGER.error("No patern defined. It can break link generation."); + } + + return publicHttpUrlPattern; + + } + + private String getNonce(UserType user) { + if (user.getCredentials() == null) { + return null; + } + + if (user.getCredentials().getNonce() == null) { + return null; + } + + if (user.getCredentials().getNonce().getValue() == null) { + return null; + } + + try { + return getPlaintext(user.getCredentials().getNonce().getValue()); + } catch (EncryptionException e) { + return null; + } + } + + @Override + public String getConst(String name) { + return constantsManager.getConstantValue(name); + } + + @Override + public ShadowType resolveEntitlement(ShadowAssociationType shadowAssociationType) { + ObjectReferenceType shadowRef = shadowAssociationType.getShadowRef(); + if (shadowRef == null) { + LOGGER.trace("No shadowRef in association {}", shadowAssociationType); + return null; + } + if (shadowRef.asReferenceValue().getObject() != null){ + return (ShadowType) shadowAssociationType.getShadowRef().asReferenceValue().getObject().asObjectable(); + } + + LensProjectionContext projectionCtx = (LensProjectionContext) getProjectionContext(); + if (projectionCtx == null) { + LOGGER.trace("No projection found. Skipping resolving entitlement"); + return null; + } + + Map> entitlementMap = projectionCtx.getEntitlementMap(); + if (entitlementMap == null) { + LOGGER.trace("No entitlement map present in projection context {}", projectionCtx); + return null; + } + + PrismObject entitlement = entitlementMap.get(shadowRef.getOid()); + if (entitlement == null) { + LOGGER.trace("No entitlement with oid {} found among resolved entitlement {}.", shadowRef, entitlementMap); + return null; + } + LOGGER.trace("Returning resolved entitlement: {}", entitlement); + return entitlement.asObjectable(); + + } + + @Override + public ExtensionType collectExtensions(AssignmentPathType path, int startAt) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + return AssignmentPath.collectExtensions(path, startAt, modelService, getCurrentTask(), getCurrentResult()); + } + @Override + public TaskType executeChangesAsynchronously(Collection> deltas, ModelExecuteOptions options, + String templateTaskOid) throws SecurityViolationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { + return executeChangesAsynchronously(deltas, options, templateTaskOid, getCurrentTask(), getCurrentResult()); + } + + @Override + public TaskType executeChangesAsynchronously(Collection> deltas, ModelExecuteOptions options, + String templateTaskOid, Task opTask, OperationResult result) throws SecurityViolationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { + MidPointPrincipal principal = securityContextManager.getPrincipal(); + if (principal == null) { + throw new SecurityViolationException("No current user"); + } + TaskType newTask; + if (templateTaskOid != null) { + newTask = modelService.getObject(TaskType.class, templateTaskOid, + getDefaultGetOptionCollection(), opTask, result).asObjectable(); + } else { + newTask = new TaskType(prismContext); + newTask.setName(PolyStringType.fromOrig("Execute changes")); + newTask.setRecurrence(TaskRecurrenceType.SINGLE); + } + newTask.setName(PolyStringType.fromOrig(newTask.getName().getOrig() + " " + (int) (Math.random()*10000))); + newTask.setOid(null); + newTask.setTaskIdentifier(null); + newTask.setOwnerRef(createObjectRef(principal.getFocus(), prismContext)); + newTask.setExecutionStatus(RUNNABLE); + newTask.setHandlerUri(ModelPublicConstants.EXECUTE_DELTAS_TASK_HANDLER_URI); + if (deltas.isEmpty()) { + throw new IllegalArgumentException("No deltas to execute"); + } + List deltasBeans = new ArrayList<>(); + for (ObjectDelta delta : deltas) { + //noinspection unchecked + deltasBeans.add(DeltaConvertor.toObjectDeltaType((ObjectDelta) delta)); + } + //noinspection unchecked + PrismPropertyDefinition deltasDefinition = prismContext.getSchemaRegistry() + .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_OBJECT_DELTAS); + PrismProperty deltasProperty = deltasDefinition.instantiate(); + deltasProperty.setRealValues(deltasBeans.toArray(new ObjectDeltaType[0])); + newTask.asPrismObject().addExtensionItem(deltasProperty); + if (options != null) { + //noinspection unchecked + PrismPropertyDefinition optionsDefinition = prismContext.getSchemaRegistry() + .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_EXECUTE_OPTIONS); + PrismProperty optionsProperty = optionsDefinition.instantiate(); + optionsProperty.setRealValue(options.toModelExecutionOptionsType()); + newTask.asPrismObject().addExtensionItem(optionsProperty); + } + ObjectDelta taskAddDelta = DeltaFactory.Object.createAddDelta(newTask.asPrismObject()); + Collection> operations = modelService + .executeChanges(singleton(taskAddDelta), null, opTask, result); + return (TaskType) operations.iterator().next().getObjectDelta().getObjectToAdd().asObjectable(); + } + + @Override + public TaskType submitTaskFromTemplate(String templateTaskOid, List> extensionItems) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { + return modelInteractionService.submitTaskFromTemplate(templateTaskOid, extensionItems, getCurrentTask(), getCurrentResult()); + } + + @Override + public TaskType submitTaskFromTemplate(String templateTaskOid, Map extensionValues) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PolicyViolationException { + return modelInteractionService.submitTaskFromTemplate(templateTaskOid, extensionValues, getCurrentTask(), getCurrentResult()); + } + + @Override + public String translate(LocalizableMessage message) { + return localizationService.translate(message, Locale.getDefault()); + } + + @Override + public String translate(LocalizableMessageType message) { + return localizationService.translate(LocalizationUtil.toLocalizableMessage(message), Locale.getDefault()); + } + + @Override + public Object executeAdHocProvisioningScript(ResourceType resource, String language, String code) + throws SchemaException, ObjectNotFoundException, + ExpressionEvaluationException, CommunicationException, ConfigurationException, + SecurityViolationException, ObjectAlreadyExistsException { + return executeAdHocProvisioningScript(resource.getOid(), language, code); + } + + @Override + public Object executeAdHocProvisioningScript(String resourceOid, String language, String code) + throws SchemaException, ObjectNotFoundException, + ExpressionEvaluationException, CommunicationException, ConfigurationException, + SecurityViolationException, ObjectAlreadyExistsException { + OperationProvisioningScriptType script = new OperationProvisioningScriptType(); + script.setCode(code); + script.setLanguage(language); + script.setHost(ProvisioningScriptHostType.RESOURCE); + + return provisioningService.executeScript(resourceOid, script, getCurrentTask(), getCurrentResult()); + } + + @Override + public Boolean isEvaluateNew() { + ScriptExpressionEvaluationContext scriptContext = ScriptExpressionEvaluationContext.getThreadLocal(); + if (scriptContext == null) { + return null; + } + return scriptContext.isEvaluateNew(); + } + + @Override + @NotNull + public Collection collectAssignedFocusMappingsResults(@NotNull ItemPath path) throws SchemaException { + ModelContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); + if (lensContext == null) { + throw new IllegalStateException("No lensContext"); + } + DeltaSetTriple> evaluatedAssignmentTriple = lensContext.getEvaluatedAssignmentTriple(); + if (evaluatedAssignmentTriple == null) { + return emptySet(); + } + Collection rv = new HashSet<>(); + for (EvaluatedAssignment evaluatedAssignment : evaluatedAssignmentTriple.getNonNegativeValues()) { + if (evaluatedAssignment.isValid()) { + for (Mapping mapping : evaluatedAssignment.getFocusMappings()) { + if (path.equivalent(mapping.getOutputPath())) { + PrismValueDeltaSetTriple outputTriple = mapping.getOutputTriple(); + if (outputTriple != null) { + rv.addAll(outputTriple.getNonNegativeValues()); + } + } + } + } + } + // Ugly hack - MID-4452 - When having an assignment giving focusMapping, and the assignment is being deleted, the + // focus mapping is evaluated in wave 0 (results correctly being pushed to the minus set), but also in wave 1. + // The results are sent to zero set; and they are not applied only because they are already part of a priori delta. + // This causes problems here. + // + // Until MID-4452 is fixed, here we manually delete the values from the result. + ModelElementContext focusContext = lensContext.getFocusContext(); + if (focusContext != null) { + ObjectDelta delta = focusContext.getDelta(); + if (delta != null) { + ItemDelta targetItemDelta = delta.findItemDelta(path); + if (targetItemDelta != null) { + rv.removeAll(emptyIfNull(targetItemDelta.getValuesToDelete())); + } + } + } + return rv; + } + + private Collection> getDefaultGetOptionCollection() { + return SelectorOptions.createCollection(GetOperationOptions.createExecutionPhase()); + } + + @Override + public List getFocusesByCorrelationRule(Class type, String resourceOid, ShadowKindType kind, String intent, ShadowType shadow) { + ResourceType resource; + try { + resource = getObject(ResourceType.class, resourceOid, GetOperationOptions.createNoFetchCollection()); + } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException + | SecurityViolationException | ExpressionEvaluationException e) { + LOGGER.error("Cannot get resource, reason: {}", e.getMessage(), e); + return null; + } + SynchronizationType synchronization = resource.getSynchronization(); + if (synchronization == null) { + return null; + } + + ObjectSynchronizationDiscriminatorType discriminator = new ObjectSynchronizationDiscriminatorType(); + discriminator.setKind(kind); + discriminator.setIntent(intent); + + SynchronizationContext syncCtx = new SynchronizationContext<>(shadow.asPrismObject(), shadow.asPrismObject(), + null, resource.asPrismObject(), getCurrentTask().getChannel(), getPrismContext(), expressionFactory, getCurrentTask()); + + ObjectSynchronizationType applicablePolicy = null; + + OperationResult result = getCurrentResult(); + + try { + + SystemConfigurationType systemConfiguration = modelInteractionService.getSystemConfiguration(result); + syncCtx.setSystemConfiguration(systemConfiguration.asPrismObject()); + + for (ObjectSynchronizationType objectSync : synchronization.getObjectSynchronization()) { + + if (SynchronizationServiceUtils.isPolicyApplicable(objectSync, discriminator, expressionFactory, syncCtx, result)) { + applicablePolicy = objectSync; + break; + } + } + + if (applicablePolicy == null) { + return null; + } + + List> correlatedFocuses = correlationConfirmationEvaluator.findFocusesByCorrelationRule(type, shadow, applicablePolicy.getCorrelation(), resource, systemConfiguration, syncCtx.getTask(), result); + return MiscSchemaUtil.toObjectableList(correlatedFocuses); + + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException + | ConfigurationException | SecurityViolationException e) { + LOGGER.error("Cannot find applicable policy for kind={}, intent={}. Reason: {}", kind, intent, e.getMessage(), e); + return null; + } + } + + @Override + public ModelContext previewChanges(Collection> deltas, + ModelExecuteOptions options) + throws CommunicationException, ObjectNotFoundException, ObjectAlreadyExistsException, ConfigurationException, + SchemaException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + return modelInteractionService.previewChanges(deltas, options, getCurrentTask(), getCurrentResult()); + } + + @Override + public void applyDefinition(O object) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + if (object instanceof ShadowType || object instanceof ResourceType) { + provisioningService.applyDefinition(object.asPrismObject(), getCurrentTask(), getCurrentResult()); + } + } + + @Override + public S_ItemEntry deltaFor(Class objectClass) throws SchemaException { + return prismContext.deltaFor(objectClass); + } + + // MID-5243 + @Override + public boolean hasArchetype(O object, String archetypeOid) { + if (object == null) { + return false; + } + if (!(object instanceof AssignmentHolderType)) { + return archetypeOid == null; + } + + ModelContext lensContext = ModelExpressionThreadLocalHolder.getLensContext(); + if (lensContext != null) { + ModelElementContext focusContext = lensContext.getFocusContext(); + ArchetypeType archetypeType = focusContext.getArchetype(); + if (archetypeType != null) { + return archetypeType.getOid().equals(archetypeOid); + } + } + + List archetypeRefs = ((AssignmentHolderType)object).getArchetypeRef(); + if (archetypeOid == null) { + return archetypeRefs.isEmpty(); + } + for (ObjectReferenceType archetypeRef : archetypeRefs) { + if (archetypeOid.equals(archetypeRef.getOid())) { + return true; + } + } + return false; + } + + // MID-5243 + @Override + public ArchetypeType getArchetype(O object) throws SchemaException, ConfigurationException { + if (!(object instanceof AssignmentHolderType)) { + return null; + } + //noinspection unchecked + PrismObject archetype = archetypeManager.determineArchetype((PrismObject) object.asPrismObject(), getCurrentResult()); + if (archetype == null) { + return null; + } + return archetype.asObjectable(); + } + + // MID-5243 + @Override + public String getArchetypeOid(O object) throws SchemaException, ConfigurationException { + if (!(object instanceof AssignmentHolderType)) { + return null; + } + //noinspection unchecked + ObjectReferenceType archetypeRef = archetypeManager.determineArchetypeRef((PrismObject) object.asPrismObject(), getCurrentResult()); + if (archetypeRef == null) { + return null; + } + return archetypeRef.getOid(); + } + + // temporary + public MessageWrapper wrap(AsyncUpdateMessageType message) { + return new MessageWrapper(message, prismContext); + } + + // temporary + @SuppressWarnings("unused") + public Map getMessageBodyAsMap(AsyncUpdateMessageType message) throws IOException { + return wrap(message).getBodyAsMap(); + } + + // temporary + @SuppressWarnings("unused") + public Item getMessageBodyAsPrismItem(AsyncUpdateMessageType message) throws SchemaException { + return wrap(message).getBodyAsPrismItem(PrismContext.LANG_XML); + } + + @Override + public void addRecomputeTrigger(O object, Long timestamp) throws ObjectAlreadyExistsException, + SchemaException, ObjectNotFoundException { + addRecomputeTrigger(object.asPrismObject(), timestamp); + } + + @Override + public void addRecomputeTrigger(PrismObject object, Long timestamp) + throws ObjectAlreadyExistsException, SchemaException, ObjectNotFoundException { + TriggerType trigger = new TriggerType(prismContext) + .handlerUri(RecomputeTriggerHandler.HANDLER_URI) + .timestamp(XmlTypeConverter.createXMLGregorianCalendar(timestamp != null ? timestamp : System.currentTimeMillis())); + List> itemDeltas = prismContext.deltaFor(object.asObjectable().getClass()) + .item(ObjectType.F_TRIGGER).add(trigger) + .asItemDeltas(); + repositoryService.modifyObject(object.getCompileTimeClass(), object.getOid(), itemDeltas, + getCurrentResult(CLASS_DOT + "addRecomputeTrigger")); + } + + @Override + public RepositoryService getRepositoryService() { + return repositoryService; + } + + @NotNull + @Override + public OptimizingTriggerCreator getOptimizingTriggerCreator(long fireAfter, long safetyMargin) { + return new OptimizingTriggerCreatorImpl(triggerCreatorGlobalState, this, fireAfter, safetyMargin); + } + + @NotNull + @Override + public ResourceAttributeDefinition getAttributeDefinition(PrismObject resource, QName objectClassName, + QName attributeName) throws SchemaException { + ResourceSchema resourceSchema = RefinedResourceSchema.getResourceSchema(resource, prismContext); + if (resourceSchema == null) { + throw new SchemaException("No resource schema in " + resource); + } + ObjectClassComplexTypeDefinition ocDef = resourceSchema.findObjectClassDefinition(objectClassName); + if (ocDef == null) { + throw new SchemaException("No definition of object class " + objectClassName + " in " + resource); + } + ResourceAttributeDefinition attrDef = ocDef.findAttributeDefinition(attributeName); + if (attrDef == null) { + throw new SchemaException("No definition of attribute " + attributeName + " in object class " + objectClassName + + " in " + resource); + } + return attrDef; + } + + @NotNull + @Override + public ResourceAttributeDefinition getAttributeDefinition(PrismObject resource, String objectClassName, + String attributeName) throws SchemaException { + return getAttributeDefinition(resource, new QName(objectClassName), new QName(attributeName)); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/SequentialValueExpressionEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/SequentialValueExpressionEvaluator.java index 4450ba02e10..fe9c735be29 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/SequentialValueExpressionEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/SequentialValueExpressionEvaluator.java @@ -1,86 +1,88 @@ -/* - * Copyright (c) 2015-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.expr; - -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDeltaUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.evaluator.AbstractExpressionEvaluator; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.prism.crypto.Protector; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.result.OperationResult; -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.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SequentialValueExpressionEvaluatorType; - -/** - * @author semancik - * - */ -public class SequentialValueExpressionEvaluator extends AbstractExpressionEvaluator { - - RepositoryService repositoryService; - - SequentialValueExpressionEvaluator(QName elementName, SequentialValueExpressionEvaluatorType sequentialValueEvaluatorType, - D outputDefinition, Protector protector, RepositoryService repositoryService, PrismContext prismContext) { - super(elementName, sequentialValueEvaluatorType, outputDefinition, protector, prismContext); - this.repositoryService = repositoryService; - } - - @Override - public PrismValueDeltaSetTriple evaluate(ExpressionEvaluationContext context, - OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException { - checkEvaluatorProfile(context); - - long counter = getSequenceCounter(getExpressionEvaluatorType().getSequenceRef().getOid(), repositoryService, result); - - Object value = ExpressionUtil.convertToOutputValue(counter, outputDefinition, protector); - - Item output = outputDefinition.instantiate(); - if (output instanceof PrismProperty) { - ((PrismProperty)output).addRealValue(value); - } else { - throw new UnsupportedOperationException("Can only generate values of property, not "+output.getClass()); - } - - return ItemDeltaUtil.toDeltaSetTriple(output, null, prismContext); - } - - public static long getSequenceCounter(String sequenceOid, RepositoryService repositoryService, OperationResult result) throws ObjectNotFoundException, SchemaException { - LensContext ctx = ModelExpressionThreadLocalHolder.getLensContext(); - if (ctx == null) { - throw new IllegalStateException("No lens context"); - } - - Long counter = ctx.getSequenceCounter(sequenceOid); - if (counter == null) { - counter = repositoryService.advanceSequence(sequenceOid, result); - ctx.setSequenceCounter(sequenceOid, counter); - } - - return counter; - } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#shortDebugDump() - */ - @Override - public String shortDebugDump() { - return "squentialValue: "+getExpressionEvaluatorType().getSequenceRef().getOid(); - } - -} +/* + * Copyright (c) 2015-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.expr; + +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDeltaUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.evaluator.AbstractExpressionEvaluator; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.result.OperationResult; +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.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SequentialValueExpressionEvaluatorType; + +/** + * @author semancik + * + */ +public class SequentialValueExpressionEvaluator extends AbstractExpressionEvaluator { + + RepositoryService repositoryService; + + SequentialValueExpressionEvaluator(QName elementName, SequentialValueExpressionEvaluatorType sequentialValueEvaluatorType, + D outputDefinition, Protector protector, RepositoryService repositoryService, PrismContext prismContext) { + super(elementName, sequentialValueEvaluatorType, outputDefinition, protector, prismContext); + this.repositoryService = repositoryService; + } + + @Override + public PrismValueDeltaSetTriple evaluate(ExpressionEvaluationContext context, + OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException { + checkEvaluatorProfile(context); + + long counter = getSequenceCounter(getExpressionEvaluatorType().getSequenceRef().getOid(), repositoryService, result); + + Object value = ExpressionUtil.convertToOutputValue(counter, outputDefinition, protector); + + Item output = outputDefinition.instantiate(); + if (output instanceof PrismProperty) { + ((PrismProperty)output).addRealValue(value); + } else { + throw new UnsupportedOperationException("Can only generate values of property, not "+output.getClass()); + } + + return ItemDeltaUtil.toDeltaSetTriple(output, null, prismContext); + } + + public static long getSequenceCounter(String sequenceOid, RepositoryService repositoryService, OperationResult result) throws ObjectNotFoundException, SchemaException { + ModelContext ctx = ModelExpressionThreadLocalHolder.getLensContext(); + if (ctx == null) { + throw new IllegalStateException("No lens context"); + } + + Long counter = ctx.getSequenceCounter(sequenceOid); + if (counter == null) { + counter = repositoryService.advanceSequence(sequenceOid, result); + ctx.setSequenceCounter(sequenceOid, counter); + } + + return counter; + } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.common.expression.ExpressionEvaluator#shortDebugDump() + */ + @Override + public String shortDebugDump() { + return "squentialValue: "+getExpressionEvaluatorType().getSequenceRef().getOid(); + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java index 8489852b6c0..1f1f4ee2c15 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/AssignmentEvaluator.java @@ -1,1546 +1,1546 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.*; -import java.util.stream.Collectors; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.ActivationComputer; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.util.CloneUtil; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; -import com.evolveum.midpoint.model.api.context.EvaluationOrder; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.PlusMinusZero; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.internals.InternalMonitor; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.FocusTypeUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.security.api.Authorization; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.Holder; -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.PolicyViolationException; -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.*; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; - -import com.evolveum.prism.xml.ns._public.types_3.PlusMinusZeroType; -import org.apache.commons.lang.BooleanUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * An engine that creates EvaluatedAssignment from an assignment IDI. It collects induced roles, constructions, - * authorizations, policy rules, and so on. - * - * @author semancik - */ -public class AssignmentEvaluator { - - private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); - - private static final String OP_EVALUATE = AssignmentEvaluator.class.getName()+".evaluate"; - private static final String OP_EVALUATE_FROM_SEGMENT = AssignmentEvaluator.class.getName()+".evaluateFromSegment"; - - private static final Trace LOGGER = TraceManager.getTrace(AssignmentEvaluator.class); - - // "Configuration parameters" - private final RepositoryService repository; - private final ObjectDeltaObject focusOdo; - private final LensContext lensContext; - private final String channel; - private final ObjectResolver objectResolver; - private final SystemObjectCache systemObjectCache; - private final RelationRegistry relationRegistry; - private final PrismContext prismContext; - private final MappingFactory mappingFactory; - private final ActivationComputer activationComputer; - private final XMLGregorianCalendar now; - private final boolean loginMode; // restricted mode, evaluating only authorizations and gui config (TODO name) - private final PrismObject systemConfiguration; - private final MappingEvaluator mappingEvaluator; - private final EvaluatedAssignmentTargetCache evaluatedAssignmentTargetCache; - private final LifecycleStateModelType focusStateModel; - - // Evaluation state - private final List memberOfInvocations = new ArrayList<>(); // experimental - - private AssignmentEvaluator(Builder builder) { - repository = builder.repository; - focusOdo = builder.focusOdo; - lensContext = builder.lensContext; - channel = builder.channel; - objectResolver = builder.objectResolver; - systemObjectCache = builder.systemObjectCache; - relationRegistry = builder.relationRegistry; - prismContext = builder.prismContext; - mappingFactory = builder.mappingFactory; - activationComputer = builder.activationComputer; - now = builder.now; - loginMode = builder.loginMode; - systemConfiguration = builder.systemConfiguration; - mappingEvaluator = builder.mappingEvaluator; - evaluatedAssignmentTargetCache = new EvaluatedAssignmentTargetCache(); - - LensFocusContext focusContext = lensContext.getFocusContext(); - if (focusContext != null) { - focusStateModel = focusContext.getLifecycleModel(); - } else { - focusStateModel = null; - } - } - - public RepositoryService getRepository() { - return repository; - } - - @SuppressWarnings("unused") - public ObjectDeltaObject getFocusOdo() { - return focusOdo; - } - - public LensContext getLensContext() { - return lensContext; - } - - public String getChannel() { - return channel; - } - - public ObjectResolver getObjectResolver() { - return objectResolver; - } - - public SystemObjectCache getSystemObjectCache() { - return systemObjectCache; - } - - public PrismContext getPrismContext() { - return prismContext; - } - - public MappingFactory getMappingFactory() { - return mappingFactory; - } - - public ActivationComputer getActivationComputer() { - return activationComputer; - } - - public XMLGregorianCalendar getNow() { - return now; - } - - @SuppressWarnings("unused") - public boolean isLoginMode() { - return loginMode; - } - - public PrismObject getSystemConfiguration() { - return systemConfiguration; - } - - @SuppressWarnings("unused") - public MappingEvaluator getMappingEvaluator() { - return mappingEvaluator; - } - - public void reset(boolean alsoMemberOfInvocations) { - evaluatedAssignmentTargetCache.reset(); - if (alsoMemberOfInvocations) { - memberOfInvocations.clear(); - } - } - - // This is to reduce the number of parameters passed between methods in this class. - // Moreover, it highlights the fact that identity of objects referenced here is fixed for any invocation of the evaluate() method. - // (There is single EvaluationContext instance for any call to evaluate().) - private class EvaluationContext { - @NotNull private final EvaluatedAssignmentImpl evalAssignment; - @NotNull private final AssignmentPathImpl assignmentPath; - // The primary assignment mode tells whether the primary assignment was added, removed or it is unchanged. - // The primary assignment is the first assignment in the assignment path, the assignment that is located in the - // focal object. - private final PlusMinusZero primaryAssignmentMode; - private final boolean evaluateOld; - private final Task task; - private EvaluationContext(@NotNull EvaluatedAssignmentImpl evalAssignment, - @NotNull AssignmentPathImpl assignmentPath, - PlusMinusZero primaryAssignmentMode, boolean evaluateOld, Task task) { - this.evalAssignment = evalAssignment; - this.assignmentPath = assignmentPath; - this.primaryAssignmentMode = primaryAssignmentMode; - this.evaluateOld = evaluateOld; - this.task = task; - } - } - - /** - * evaluateOld: If true, we take the 'old' value from assignmentIdi. If false, we take the 'new' one. - */ - public EvaluatedAssignmentImpl evaluate( - ItemDeltaItem,PrismContainerDefinition> assignmentIdi, - PlusMinusZero primaryAssignmentMode, boolean evaluateOld, AssignmentHolderType source, String sourceDescription, - AssignmentOrigin origin, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - OperationResult result = parentResult.subresult(OP_EVALUATE) - .setMinor() - .addArbitraryObjectAsParam("primaryAssignmentMode", primaryAssignmentMode) - .addParam("evaluateOld", evaluateOld) - .addArbitraryObjectAsParam("source", source) - .addParam("sourceDescription", sourceDescription) - .addArbitraryObjectAsParam("origin", origin) - .build(); - AssignmentEvaluationTraceType trace; - if (result.isTracingNormal(AssignmentEvaluationTraceType.class)) { - trace = new AssignmentEvaluationTraceType(prismContext) - .assignmentOld(CloneUtil.clone(getAssignmentBean(assignmentIdi, true))) - .assignmentNew(CloneUtil.clone(getAssignmentBean(assignmentIdi, false))) - .primaryAssignmentMode(PlusMinusZeroType.fromValue(primaryAssignmentMode)) - .evaluateOld(evaluateOld) - .textSource(source != null ? source.asPrismObject().debugDump() : "null") - .sourceDescription(sourceDescription); - result.addTrace(trace); - } else { - trace = null; - } - try { - assertSourceNotNull(source, assignmentIdi); - - EvaluatedAssignmentImpl evalAssignmentImpl = new EvaluatedAssignmentImpl<>(assignmentIdi, evaluateOld, origin, prismContext); - - EvaluationContext ctx = new EvaluationContext( - evalAssignmentImpl, - new AssignmentPathImpl(prismContext), - primaryAssignmentMode, evaluateOld, task); - - evaluatedAssignmentTargetCache.resetForNextAssignment(); - - AssignmentPathSegmentImpl segment = new AssignmentPathSegmentImpl(source, sourceDescription, assignmentIdi, true, - evaluateOld, relationRegistry, prismContext); - segment.setEvaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)); - segment.setEvaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)); - segment.setValidityOverride(true); - segment.setPathToSourceValid(true); - segment.setProcessMembership(true); - segment.setRelation(getRelation(getAssignmentType(segment, ctx))); - - evaluateFromSegment(segment, PlusMinusZero.ZERO, ctx, result); - - if (segment.getTarget() != null) { - result.addContext("assignmentTargetName", PolyString.getOrig(segment.getTarget().getName())); - } - - LOGGER.trace("Assignment evaluation finished:\n{}", ctx.evalAssignment.debugDumpLazily()); - if (trace != null) { - trace.setTextResult(ctx.evalAssignment.debugDump()); - } - result.computeStatusIfUnknown(); - return ctx.evalAssignment; - } catch (Throwable t) { - result.recordFatalError(t.getMessage(), t); - throw t; - } - } - - private AssignmentType getAssignmentBean( - ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - boolean old) { - PrismContainerValue pcv = assignmentIdi.getSingleValue(old); - return pcv != null ? pcv.asContainerable() : null; - } - - private EvaluationOrder getInitialEvaluationOrder( - ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - EvaluationContext ctx) { - AssignmentType assignmentType = LensUtil.getAssignmentType(assignmentIdi, ctx.evaluateOld); - return EvaluationOrderImpl.zero(relationRegistry).advance(getRelation(assignmentType)); - } - - /** - * @param relativeMode - * - * Where to put constructions and target roles/orgs/services (PLUS/MINUS/ZERO/null; null means "nowhere"). - * This is a mode relative to the primary assignment. It does NOT tell whether the assignment as a whole - * is added or removed. It tells whether the part of the assignment that we are processing is to be - * added or removed. This may happen, e.g. if a condition in an existing assignment turns from false to true. - * In that case the primary assignment mode is ZERO, but the relative mode is PLUS. - * The relative mode always starts at ZERO, even for added or removed assignments. - * - * This depends on the status of conditions. E.g. if condition evaluates 'false -> true' (i.e. in old - * state the value is false, and in new state the value is true), then the mode is PLUS. - * - * This "triples algebra" is based on the following two methods: - * - * @see ExpressionUtil#computeConditionResultMode(boolean, boolean) - Based on condition values "old+new" determines - * into what set (PLUS/MINUS/ZERO/none) should the result be placed. Irrespective of what is the current mode. So, - * in order to determine "real" place where to put it (i.e. the new mode) the following method is used. - * - * @see PlusMinusZero#compute(PlusMinusZero, PlusMinusZero) - Takes original mode and the mode from recent condition - * and determines the new mode (commutatively): - * - * PLUS + PLUS/ZERO = PLUS - * MINUS + MINUS/ZERO = MINUS - * ZERO + ZERO = ZERO - * PLUS + MINUS = none - * - * This is quite straightforward, although the last rule deserves a note. If we have an assignment that was originally - * disabled and becomes enabled by the current delta (i.e. PLUS), and that assignment contains an inducement that was originally - * enabled and becomes disabled (i.e. MINUS), the result is that the (e.g.) constructions within the inducement were not - * present in the old state (because assignment was disabled) and are not present in the new state (because inducement is disabled). - * - * Note: this parameter could be perhaps renamed to "tripleMode" or "destination" or something like that. - */ - private void evaluateFromSegment(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, EvaluationContext ctx, - OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - OperationResult result = parentResult.subresult(OP_EVALUATE_FROM_SEGMENT) - .setMinor() - .addParam("segment", segment.shortDump()) - .addArbitraryObjectAsParam("relativeMode", relativeMode) - .build(); - AssignmentSegmentEvaluationTraceType trace; - if (result.isTracingNormal(AssignmentSegmentEvaluationTraceType.class)) { - trace = new AssignmentSegmentEvaluationTraceType(prismContext) - .segment(segment.toAssignmentPathSegmentType(true)) - .mode(PlusMinusZeroType.fromValue(relativeMode)); - result.addTrace(trace); - } else { - trace = null; - } - try { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("*** Evaluate from segment: {}", segment); - LOGGER.trace("*** Evaluation order - standard: {}, matching: {}", segment.getEvaluationOrder(), segment.isMatchingOrder()); - LOGGER.trace("*** Evaluation order - for target: {}, matching: {}", segment.getEvaluationOrderForTarget(), segment.isMatchingOrderForTarget()); - LOGGER.trace("*** mode: {}, process membership: {}", relativeMode, segment.isProcessMembership()); - LOGGER.trace("*** path to source valid: {}, validity override: {}", segment.isPathToSourceValid(), segment.isValidityOverride()); - } - - assertSourceNotNull(segment.source, ctx.evalAssignment); - checkSchema(segment, ctx); - - ctx.assignmentPath.add(segment); - LOGGER.trace("*** Path (with current segment already added):\n{}", ctx.assignmentPath.debugDumpLazily()); - - boolean evaluateContent; - AssignmentType assignmentType = getAssignmentType(segment, ctx); - MappingType assignmentCondition = assignmentType.getCondition(); - if (assignmentCondition != null) { - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(assignmentCondition, - segment.source, assignmentPathVariables, - "condition in assignment in " + segment.getSourceDescription(), ctx, result); - boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); - boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); - PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); - if (modeFromCondition == null) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: {})", - FocusTypeUtil.dumpAssignment(assignmentType), condOld, condNew, null); - } - evaluateContent = false; - } else { - PlusMinusZero origMode = relativeMode; - relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); - LOGGER.trace("Evaluated condition in assignment {} -> {}: {} + {} = {}", condOld, condNew, origMode, - modeFromCondition, relativeMode); - evaluateContent = true; - } - } else { - evaluateContent = true; - } - - if (ctx.assignmentPath.isEmpty() && ctx.evalAssignment.isVirtual()) { - segment.setValidityOverride(ctx.evalAssignment.isVirtual()); - } - - boolean isValid = - (evaluateContent && evaluateSegmentContent(segment, relativeMode, ctx, result)) || ctx.evalAssignment.isVirtual(); - - ctx.assignmentPath.removeLast(segment); - if (ctx.assignmentPath.isEmpty()) { // direct assignment - ctx.evalAssignment.setValid(isValid); - } - - LOGGER.trace("evalAssignment isVirtual {} ", ctx.evalAssignment.isVirtual()); - if (segment.getSource() != null) { - result.addContext("segmentSourceName", PolyString.getOrig(segment.getSource().getName())); - } - if (segment.getTarget() != null) { - result.addContext("segmentTargetName", PolyString.getOrig(segment.getTarget().getName())); - } - result.addArbitraryObjectAsReturn("relativeMode", relativeMode); - result.addReturn("evaluateContent", evaluateContent); - result.addReturn("isValid", isValid); - if (trace != null) { - trace.setTextResult(segment.debugDump()); - } - } catch (Throwable t) { - result.recordFatalError(t.getMessage(), t); - throw t; - } finally { - result.computeStatusIfUnknown(); - } - } - - // "content" means "payload + targets" here - private boolean evaluateSegmentContent(AssignmentPathSegmentImpl segment, - PlusMinusZero relativeMode, EvaluationContext ctx, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - assert ctx.assignmentPath.last() == segment; - - final boolean isDirectAssignment = ctx.assignmentPath.size() == 1; - - AssignmentType assignment = getAssignmentType(segment, ctx); - - // Assignment validity is checked with respect to the assignment source, not to the focus object. - // So, if (e.g.) focus is in "draft" state, only validity of direct assignments should be influenced by this fact. - // Other assignments (e.g. from roles to metaroles) should be considered valid, provided these roles are - // in active lifecycle states. See also MID-6114. - AssignmentHolderType source = segment.isMatchingOrder() ? focusOdo.getNewObject().asObjectable() : segment.getSource(); - boolean isAssignmentValid = LensUtil.isAssignmentValid(source, assignment, now, activationComputer, focusStateModel); - if (isAssignmentValid || segment.isValidityOverride()) { - // Note: validityOverride is currently the same as "isDirectAssignment" - which is very probably OK. - // Direct assignments are visited even if they are not valid (i.e. effectively disabled). - // It is because we need to collect e.g. assignment policy rules for them. - // Also because we could have deltas that disable/enable these assignments. - boolean reallyValid = segment.isPathToSourceValid() && isAssignmentValid; - if (!loginMode && segment.isMatchingOrder()) { - if (assignment.getConstruction() != null) { - collectConstruction(segment, relativeMode, reallyValid, ctx); - } - if (assignment.getPersonaConstruction() != null) { - collectPersonaConstruction(segment, relativeMode, reallyValid, ctx); - } - if (assignment.getFocusMappings() != null) { - if (reallyValid && relativeMode != null) { // null relative mode means both PLUS and MINUS - collectFocusMappings(segment, relativeMode, ctx); - } - } - } - if (!loginMode && assignment.getPolicyRule() != null) { - // Here we ignore "reallyValid". It is OK, because reallyValid can be false here only when - // evaluating direct assignments; and invalid ones are marked as such via EvaluatedAssignment.isValid. - // TODO is it ok? - if (isNonNegative(relativeMode)) { - if (segment.isMatchingOrder()) { - collectPolicyRule(true, segment, ctx); - } - if (segment.isMatchingOrderForTarget()) { - collectPolicyRule(false, segment, ctx); - } - } - } - if (assignment.getTargetRef() != null) { - QName relation = getRelation(assignment); - if (loginMode && !relationRegistry.isProcessedOnLogin(relation)) { - LOGGER.trace("Skipping processing of assignment target {} because relation {} is configured for login skip", assignment.getTargetRef().getOid(), relation); - // Skip - to optimize logging-in, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) - // We want to make this configurable in the future MID-3581 - } else if (!loginMode && !isChanged(ctx.primaryAssignmentMode) && - !relationRegistry.isProcessedOnRecompute(relation) && !shouldEvaluateAllAssignmentRelationsOnRecompute()) { - LOGGER.debug("Skipping processing of assignment target {} because relation {} is configured for recompute skip (mode={})", assignment.getTargetRef().getOid(), relation, relativeMode); - // Skip - to optimize recompute, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) - // never skip this if assignment has changed. We want to process this, e.g. to enforce min/max assignee rules - // We want to make this configurable in the future MID-3581 - - // Important: but we still want this to be reflected in roleMembershipRef - if ((isNonNegative(relativeMode)) && segment.isProcessMembership()) { - if (assignment.getTargetRef().getOid() != null) { - addToMembershipLists(assignment.getTargetRef(), relation, ctx); - } else { - // no OID, so we have to resolve the filter - for (PrismObject targetObject : getTargets(segment, ctx, result)) { - ObjectType target = targetObject.asObjectable(); - if (target instanceof FocusType) { - addToMembershipLists((FocusType) target, relation, ctx); - } - } - } - } - } else { - List> targets = getTargets(segment, ctx, result); - LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, assignment.getId(), targets); - if (isDirectAssignment) { - setEvaluatedAssignmentTarget(segment, targets, ctx); - } - for (PrismObject target : targets) { - if (hasCycle(segment, target, ctx)) { - continue; - } - if (isDelegationToNonDelegableTarget(assignment, target, ctx)) { - continue; - } - evaluateSegmentTarget(segment, relativeMode, reallyValid, (AssignmentHolderType) target.asObjectable(), relation, ctx, result); - } - } - } - } else { - LOGGER.trace("Skipping evaluation of assignment {} because it is not valid", assignment); - } - return isAssignmentValid; - } - - private boolean shouldEvaluateAllAssignmentRelationsOnRecompute() { - return ModelExecuteOptions.isEvaluateAllAssignmentRelationsOnRecompute(lensContext.getOptions()); - } - - private boolean isDelegationToNonDelegableTarget(AssignmentType assignmentType, @NotNull PrismObject target, - EvaluationContext ctx) { - AssignmentPathSegment previousSegment = ctx.assignmentPath.beforeLast(1); - if (previousSegment == null || !previousSegment.isDelegation() || !target.canRepresent(AbstractRoleType.class)) { - return false; - } - if (!Boolean.TRUE.equals(((AbstractRoleType)target.asObjectable()).isDelegable())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping evaluation of {} because it delegates to a non-delegable target {}", - FocusTypeUtil.dumpAssignment(assignmentType), target); - } - return true; - } else { - return false; - } - } - - // number of times any given target is allowed to occur in the assignment path - private static final int MAX_TARGET_OCCURRENCES = 2; - - private boolean hasCycle(AssignmentPathSegmentImpl segment, @NotNull PrismObject target, - EvaluationContext ctx) throws PolicyViolationException { - // TODO reconsider this - if (target.getOid().equals(segment.source.getOid())) { - throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); - } - // removed condition "&& segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())" - // as currently it is always true - // TODO reconsider this - int count = ctx.assignmentPath.countTargetOccurrences(target.asObjectable()); - if (count >= MAX_TARGET_OCCURRENCES) { - LOGGER.debug("Max # of target occurrences ({}) detected for target {} in {} - stopping evaluation here", - MAX_TARGET_OCCURRENCES, ObjectTypeUtil.toShortString(target), ctx.assignmentPath); - return true; - } else { - return false; - } - } - - private void collectConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - ConstructionType constructionType = assignmentType.getConstruction(); - - LOGGER.trace("Preparing construction '{}' in {}", constructionType.getDescription(), segment.source); - - Construction construction = new Construction<>(constructionType, segment.source); - // We have to clone here as the path is constantly changing during evaluation - construction.setAssignmentPath(ctx.assignmentPath.clone()); - construction.setFocusOdo(focusOdo); - construction.setLensContext(lensContext); - construction.setObjectResolver(objectResolver); - construction.setPrismContext(prismContext); - construction.setMappingFactory(mappingFactory); - construction.setMappingEvaluator(mappingEvaluator); - construction.setOriginType(OriginType.ASSIGNMENTS); - construction.setChannel(channel); - construction.setOrderOneObject(segment.getOrderOneObject()); - construction.setValid(isValid); - construction.setRelativityMode(mode); - - // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. - if (mode == null) { - return; // null mode (i.e. plus + minus) means 'ignore the payload' - } - ctx.evalAssignment.addConstruction(construction, mode); - } - - private void collectPersonaConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - if (mode == null) { - return; // null mode (i.e. plus + minus) means 'ignore the payload' - } - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - PersonaConstructionType constructionType = assignmentType.getPersonaConstruction(); - - LOGGER.trace("Preparing persona construction '{}' in {}", constructionType.getDescription(), segment.source); - - PersonaConstruction construction = new PersonaConstruction<>(constructionType, segment.source); - // We have to clone here as the path is constantly changing during evaluation - construction.setAssignmentPath(ctx.assignmentPath.clone()); - construction.setFocusOdo(focusOdo); - construction.setLensContext(lensContext); - construction.setObjectResolver(objectResolver); - construction.setPrismContext(prismContext); - construction.setOriginType(OriginType.ASSIGNMENTS); - construction.setChannel(channel); - construction.setValid(isValid); - construction.setRelativityMode(mode); - - ctx.evalAssignment.addPersonaConstruction(construction, mode); - } - - private void collectFocusMappings(AssignmentPathSegmentImpl segment, @NotNull PlusMinusZero relativeMode, EvaluationContext ctx) - throws SchemaException { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentBean = getAssignmentType(segment, ctx); - MappingsType mappingsBean = assignmentBean.getFocusMappings(); - - LOGGER.trace("Request evaluation of focus mappings '{}' in {} ({} mappings)", - mappingsBean.getDescription(), segment.source, mappingsBean.getMapping().size()); - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - - for (MappingType mappingBean: mappingsBean.getMapping()) { - AssignedFocusMappingEvaluationRequest request = new AssignedFocusMappingEvaluationRequest(mappingBean, segment.source, - ctx.evalAssignment, relativeMode, assignmentPathVariables, segment.sourceDescription); - ctx.evalAssignment.addFocusMappingEvaluationRequest(request); - } - } - - private void collectPolicyRule(boolean focusRule, AssignmentPathSegmentImpl segment, EvaluationContext ctx) { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - AssignmentType assignmentType = getAssignmentType(segment, ctx); - PolicyRuleType policyRuleType = assignmentType.getPolicyRule(); - - LOGGER.trace("Collecting {} policy rule '{}' in {}", focusRule ? "focus" : "target", policyRuleType.getName(), segment.source); - - EvaluatedPolicyRuleImpl policyRule = new EvaluatedPolicyRuleImpl(policyRuleType.clone(), ctx.assignmentPath.clone(), prismContext); - - if (focusRule) { - ctx.evalAssignment.addFocusPolicyRule(policyRule); - } else { - if (appliesDirectly(ctx.assignmentPath)) { - ctx.evalAssignment.addThisTargetPolicyRule(policyRule); - } else { - ctx.evalAssignment.addOtherTargetPolicyRule(policyRule); - } - } - } - - private boolean appliesDirectly(AssignmentPathImpl assignmentPath) { - assert !assignmentPath.isEmpty(); - // TODO what about deputy relation which does not increase summaryOrder? - long zeroOrderCount = assignmentPath.getSegments().stream() - .filter(seg -> seg.getEvaluationOrderForTarget().getSummaryOrder() == 0) - .count(); - return zeroOrderCount == 1; - } - - @NotNull - private List> getTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, - OperationResult result) throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - if (assignmentType.getTargetRef() != null) { - try { - return resolveTargets(segment, ctx, result); - } catch (ObjectNotFoundException ex) { - // Do not throw an exception. We don't have referential integrity. Therefore if a role is deleted then throwing - // an exception would prohibit any operations with the users that have the role, including removal of the reference. - // The failure is recorded in the result and we will log it. It should be enough. - LOGGER.error(ex.getMessage()+" in assignment target reference in "+segment.sourceDescription,ex); - // For OrgType references we trigger the reconciliation (see MID-2242) - ctx.evalAssignment.setForceRecon(true); - return Collections.emptyList(); - } - } else { - throw new IllegalStateException("Both target and targetRef are null. We should not be here. Assignment: " + assignmentType); - } - } - - @NotNull - private List> resolveTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, - OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - ObjectReferenceType targetRef = assignmentType.getTargetRef(); - String oid = targetRef.getOid(); - - // Target is referenced, need to fetch it - Class targetClass; - if (targetRef.getType() != null) { - targetClass = prismContext.getSchemaRegistry().determineCompileTimeClass(targetRef.getType()); - if (targetClass == null) { - throw new SchemaException("Cannot determine type from " + targetRef.getType() + " in target reference in " + assignmentType + " in " + segment.sourceDescription); - } - } else { - throw new SchemaException("Missing type in target reference in " + assignmentType + " in " + segment.sourceDescription); - } - - if (oid == null) { - LOGGER.trace("Resolving dynamic target ref"); - if (targetRef.getFilter() == null){ - throw new SchemaException("The OID and filter are both null in assignment targetRef in "+segment.source); - } - return resolveTargetsFromFilter(targetClass, targetRef.getFilter(), segment, ctx, result); - } else { - LOGGER.trace("Resolving target {}:{} from repository", targetClass.getSimpleName(), oid); - PrismObject target; - try { - target = repository.getObject(targetClass, oid, null, result); - } catch (SchemaException e) { - throw new SchemaException(e.getMessage() + " in " + segment.sourceDescription, e); - } - // Not handling object not found exception here. Caller will handle that. - if (target == null) { - throw new IllegalArgumentException("Got null target from repository, oid:"+oid+", class:"+targetClass+" (should not happen, probably a bug) in "+segment.sourceDescription); - } - return Collections.singletonList(target); - } - } - - @NotNull - private List> resolveTargetsFromFilter(Class targetClass, - SearchFilterType filter, AssignmentPathSegmentImpl segment, - EvaluationContext ctx, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(lensContext, null, ctx.task, result)); - try { - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(segment.source, null, null, systemConfiguration.asObjectable(), prismContext); - variables.put(ExpressionConstants.VAR_SOURCE, segment.getOrderOneObject(), ObjectType.class); - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - if (assignmentPathVariables != null) { - ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, variables, getPrismContext()); - } - variables.addVariableDefinitions(getAssignmentEvaluationVariables()); - ObjectFilter origFilter = prismContext.getQueryConverter().parseFilter(filter, targetClass); - // TODO: expression profile should be determined from the holding object archetype - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - ObjectFilter evaluatedFilter = ExpressionUtil.evaluateFilterExpressions(origFilter, variables, expressionProfile, getMappingFactory().getExpressionFactory(), prismContext, " evaluating resource filter expression ", ctx.task, result); - if (evaluatedFilter == null) { - throw new SchemaException("The OID is null and filter could not be evaluated in assignment targetRef in "+segment.source); - } - - return repository.searchObjects(targetClass, prismContext.queryFactory().createQuery(evaluatedFilter), null, result); - // we don't check for no targets here; as we don't care for referential integrity - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - private ExpressionVariables getAssignmentEvaluationVariables() { - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_LOGIN_MODE, loginMode, Boolean.class); - // e.g. AssignmentEvaluator itself, model context, etc (when needed) - return variables; - } - - // Note: This method must be single-return after targetEvaluationInformation is established. - private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, boolean isAssignmentPathValid, - AssignmentHolderType target, QName relation, EvaluationContext ctx, - OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - assertSourceNotNull(segment.source, ctx.evalAssignment); - - assert ctx.assignmentPath.last() == segment; - - segment.setTarget(target); - segment.setRelation(relation); // probably not needed - - if (evaluatedAssignmentTargetCache.canSkip(segment, ctx.primaryAssignmentMode)) { - LOGGER.trace("Skipping evaluation of segment {} because it is idempotent and we have seen the target before", segment); - InternalMonitor.recordRoleEvaluationSkip(target, true); - return; - } - - LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDumpLazily(1)); - - checkRelationWithTarget(segment, target, relation); - - LifecycleStateModelType targetStateModel = ArchetypeManager.determineLifecycleModel(target.asPrismObject(), systemConfiguration); - boolean isTargetValid = LensUtil.isFocusValid(target, now, activationComputer, targetStateModel); - boolean isPathAndTargetValid = isAssignmentPathValid && isTargetValid; - - LOGGER.debug("Evaluating RBAC [{}]", ctx.assignmentPath.shortDumpLazily()); - InternalMonitor.recordRoleEvaluation(target, true); - - AssignmentTargetEvaluationInformation targetEvaluationInformation; - if (isPathAndTargetValid) { - // Cache it immediately, even before evaluation. So if there is a cycle in the role path - // then we can detect it and skip re-evaluation of aggressively idempotent roles. - // - // !!! Ensure we will not return without updating this object (except for exceptions). So please keep this - // method a single-return one after this point. We did not want to complicate things using try...finally. - // - targetEvaluationInformation = evaluatedAssignmentTargetCache.recordProcessing(segment, ctx.primaryAssignmentMode); - } else { - targetEvaluationInformation = null; - } - int targetPolicyRulesOnEntry = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); - - boolean skipOnConditionResult = false; - - if (isTargetValid && target instanceof AbstractRoleType) { - MappingType roleCondition = ((AbstractRoleType)target).getCondition(); - if (roleCondition != null) { - AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); - PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(roleCondition, - segment.source, assignmentPathVariables, - "condition in " + segment.getTargetDescription(), ctx, result); - boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); - boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); - PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); - if (modeFromCondition == null) { - skipOnConditionResult = true; - LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: null)", - target, condOld, condNew); - } else { - PlusMinusZero origMode = relativeMode; - relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); - LOGGER.trace("Evaluated condition in {}: {} -> {}: {} + {} = {}", target, condOld, condNew, - origMode, modeFromCondition, relativeMode); - } - } - } - - if (!skipOnConditionResult) { - EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( - target.asPrismObject(), - segment.isMatchingOrder(), // evaluateConstructions: exact meaning of this is to be revised - ctx.assignmentPath.clone(), - getAssignmentType(segment, ctx), - isPathAndTargetValid); - ctx.evalAssignment.addRole(evalAssignmentTarget, relativeMode); - - // we need to evaluate assignments also for disabled targets, because of target policy rules - // ... but only for direct ones! - if (isTargetValid || ctx.assignmentPath.size() == 1) { - for (AssignmentType nextAssignment : target.getAssignment()) { - evaluateAssignment(segment, relativeMode, isPathAndTargetValid, ctx, target, relation, nextAssignment, result); - } - } - - // we need to collect membership also for disabled targets (provided the assignment itself is enabled): MID-4127 - if (isNonNegative(relativeMode) && segment.isProcessMembership()) { - addToMembershipLists(target, relation, ctx); - } - - if (isTargetValid) { - if (isNonNegative(relativeMode)) { - setAsTenantRef(target, ctx); - } - - // We continue evaluation even if the relation is non-membership and non-delegation. - // Computation of isMatchingOrder will ensure that we won't collect any unwanted content. - - if (target instanceof AbstractRoleType) { - for (AssignmentType roleInducement : ((AbstractRoleType) target).getInducement()) { - evaluateInducement(segment, relativeMode, isPathAndTargetValid, ctx, target, roleInducement, result); - } - } - - if (segment.isMatchingOrder() && target instanceof AbstractRoleType && isNonNegative(relativeMode)) { - for (AuthorizationType authorizationType : ((AbstractRoleType) target).getAuthorization()) { - Authorization authorization = createAuthorization(authorizationType, target.toString()); - if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { - ctx.evalAssignment.addAuthorization(authorization); - } - } - AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) target).getAdminGuiConfiguration(); - if (adminGuiConfiguration != null && !ctx.evalAssignment.getAdminGuiConfigurations() - .contains(adminGuiConfiguration)) { - ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); - } - } - } - } - int targetPolicyRulesOnExit = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); - - LOGGER.trace("Evaluating segment target DONE for {}; target policy rules: {} -> {}", segment, targetPolicyRulesOnEntry, - targetPolicyRulesOnExit); - if (targetEvaluationInformation != null) { - targetEvaluationInformation.setBringsTargetPolicyRules(targetPolicyRulesOnExit > targetPolicyRulesOnEntry); - } - } - - // TODO revisit this - private ObjectType getOrderOneObject(AssignmentPathSegmentImpl segment) { - EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); - if (evaluationOrder.getSummaryOrder() == 1) { - return segment.getTarget(); - } else { - if (segment.getSource() != null) { // should be always the case... - return segment.getSource(); - } else { - return segment.getTarget(); - } - } - } - - private void evaluateAssignment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, - AssignmentHolderType target, QName relation, AssignmentType nextAssignment, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - ObjectType orderOneObject = getOrderOneObject(segment); - - if (relationRegistry.isDelegation(relation)) { - // We have to handle assignments as though they were inducements here. - if (!isAllowedByLimitations(segment, nextAssignment, ctx)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", - FocusTypeUtil.dumpAssignment(nextAssignment)); - } - return; - } - } - QName nextRelation = getRelation(nextAssignment); - EvaluationOrder nextEvaluationOrder = segment.getEvaluationOrder().advance(nextRelation); - EvaluationOrder nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().advance(nextRelation); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", - segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpAssignment(nextAssignment), nextEvaluationOrder); - } - String nextSourceDescription = target+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, nextSourceDescription, nextAssignment, true, relationRegistry, prismContext); - nextSegment.setRelation(nextRelation); - nextSegment.setEvaluationOrder(nextEvaluationOrder); - nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget); - nextSegment.setOrderOneObject(orderOneObject); - nextSegment.setPathToSourceValid(isValid); - /* - * We obviously want to process membership from the segment if it's of matching order. - * - * But we want to do that also for targets obtained via delegations. The current (approximate) approach is to - * collect membership from all assignments of any user that we find on the assignment path. - * - * TODO: does this work for invalid (effectiveStatus = disabled) assignments? - */ - boolean isUser = target instanceof UserType; - nextSegment.setProcessMembership(nextSegment.isMatchingOrder() || isUser); - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(nextSegment, mode, ctx, result); - } - - private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, - AssignmentHolderType target, AssignmentType inducement, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { - - ObjectType orderOneObject = getOrderOneObject(segment); - - if (!isInducementApplicableToFocusType(inducement.getFocusType())) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", - FocusTypeUtil.dumpAssignment(inducement), inducement.getFocusType(), target.getClass().getSimpleName()); - } - return; - } - if (!isAllowedByLimitations(segment, inducement, ctx)) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignment(inducement)); - } - return; - } - String subSourceDescription = target+" in "+segment.sourceDescription; - AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, subSourceDescription, inducement, false, relationRegistry, prismContext); - // note that 'old' and 'new' values for assignment in nextSegment are the same - boolean nextIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( - segment.getEvaluationOrder(), nextSegment.getAssignmentNew()); - boolean nextIsMatchingOrderForTarget = AssignmentPathSegmentImpl.computeMatchingOrder( - segment.getEvaluationOrderForTarget(), nextSegment.getAssignmentNew()); - - Holder nextEvaluationOrderHolder = new Holder<>(segment.getEvaluationOrder().clone()); - Holder nextEvaluationOrderForTargetHolder = new Holder<>(segment.getEvaluationOrderForTarget().clone()); - adjustOrder(nextEvaluationOrderHolder, nextEvaluationOrderForTargetHolder, inducement.getOrderConstraint(), inducement.getOrder(), ctx.assignmentPath, nextSegment, ctx); - nextSegment.setEvaluationOrder(nextEvaluationOrderHolder.getValue(), nextIsMatchingOrder); - nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTargetHolder.getValue(), nextIsMatchingOrderForTarget); - - nextSegment.setOrderOneObject(orderOneObject); - nextSegment.setPathToSourceValid(isValid); - nextSegment.setProcessMembership(nextIsMatchingOrder); - nextSegment.setRelation(getRelation(inducement)); - - // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into - // inducements with non-matching order: for example because we need to extract target-related policy rules - // (these are stored with order of one less than orders for focus-related policy rules). - // - // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. - // processMembership attribute to false for these inducements. - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", - segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpInducementConstraints(inducement), - FocusTypeUtil.dumpAssignment(inducement), nextEvaluationOrderHolder.getValue().shortDump()); - } - assert !ctx.assignmentPath.isEmpty(); - evaluateFromSegment(nextSegment, mode, ctx, result); - } - - private void adjustOrder(Holder evaluationOrderHolder, Holder targetEvaluationOrderHolder, - List constraints, Integer order, AssignmentPathImpl assignmentPath, - AssignmentPathSegmentImpl nextSegment, EvaluationContext ctx) { - - if (constraints.isEmpty()) { - if (order == null || order == 1) { - return; - } else if (order <= 0) { - throw new IllegalStateException("Wrong inducement order: it must be positive but it is " + order + " instead"); - } - // converting legacy -> new specification - int currentOrder = evaluationOrderHolder.getValue().getSummaryOrder(); - if (order > currentOrder) { - LOGGER.trace("order of the inducement ({}) is greater than the current evaluation order ({}), marking as undefined", - order, currentOrder); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - // i.e. currentOrder >= order, i.e. currentOrder > order-1 - int newOrder = currentOrder - (order - 1); - assert newOrder > 0; - constraints = Collections.singletonList(new OrderConstraintsType(prismContext) - .order(order) - .resetOrder(newOrder)); - } - - OrderConstraintsType summaryConstraints = ObjectTypeUtil.getConstraintFor(constraints, null); - Integer resetSummaryTo = summaryConstraints != null && summaryConstraints.getResetOrder() != null ? - summaryConstraints.getResetOrder() : null; - - if (resetSummaryTo != null) { - int summaryBackwards = evaluationOrderHolder.getValue().getSummaryOrder() - resetSummaryTo; - if (summaryBackwards < 0) { - // or should we throw an exception? - LOGGER.warn("Cannot move summary order backwards to a negative value ({}). Current order: {}, requested order: {}", - summaryBackwards, evaluationOrderHolder.getValue().getSummaryOrder(), resetSummaryTo); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } else if (summaryBackwards > 0) { -// MultiSet backRelations = new HashMultiSet<>(); - int assignmentsSeen = 0; - int i = assignmentPath.size()-1; - while (assignmentsSeen < summaryBackwards) { - if (i < 0) { - LOGGER.trace("Cannot move summary order backwards by {}; only {} assignments segment seen: {}", - summaryBackwards, assignmentsSeen, assignmentPath); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - AssignmentPathSegmentImpl segment = assignmentPath.getSegments().get(i); - if (segment.isAssignment()) { - if (!relationRegistry.isDelegation(segment.getRelation())) { - // backRelations.add(segment.getRelation()); - assignmentsSeen++; - LOGGER.trace("Going back {}: relation at assignment -{} (position -{}): {}", summaryBackwards, - assignmentsSeen, assignmentPath.size() - i, segment.getRelation()); - } - } else { - AssignmentType inducement = segment.getAssignment(ctx.evaluateOld); // for i>0 returns value regardless of evaluateOld - for (OrderConstraintsType constraint : inducement.getOrderConstraint()) { - if (constraint.getResetOrder() != null && constraint.getRelation() != null) { - LOGGER.debug("Going back {}: an inducement with non-summary resetting constraint found" - + " in the chain (at position -{}): {} in {}", summaryBackwards, assignmentPath.size()-i, - constraint, segment); - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - return; - } - } - if (segment.getLastEqualOrderSegmentIndex() != null) { - i = segment.getLastEqualOrderSegmentIndex(); - continue; - } - } - i--; - } - nextSegment.setLastEqualOrderSegmentIndex(i); - evaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrder()); - targetEvaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrderForTarget()); - } else { - // summaryBackwards is 0 - nothing to change - } - for (OrderConstraintsType constraint : constraints) { - if (constraint.getRelation() != null && constraint.getResetOrder() != null) { - LOGGER.warn("Ignoring resetOrder (with a value of {} for {}) because summary order was already moved backwards by {} to {}: {}", - constraint.getResetOrder(), constraint.getRelation(), summaryBackwards, - evaluationOrderHolder.getValue().getSummaryOrder(), constraint); - } - } - } else { - EvaluationOrder beforeChange = evaluationOrderHolder.getValue().clone(); - for (OrderConstraintsType constraint : constraints) { - if (constraint.getResetOrder() != null) { - assert constraint.getRelation() != null; // already processed above - int currentOrder = evaluationOrderHolder.getValue().getMatchingRelationOrder(constraint.getRelation()); - int newOrder = constraint.getResetOrder(); - if (newOrder > currentOrder) { - LOGGER.warn("Cannot increase evaluation order for {} from {} to {}: {}", constraint.getRelation(), - currentOrder, newOrder, constraint); - } else if (newOrder < currentOrder) { - evaluationOrderHolder.setValue(evaluationOrderHolder.getValue().resetOrder(constraint.getRelation(), newOrder)); - LOGGER.trace("Reset order for {} from {} to {} -> {}", constraint.getRelation(), currentOrder, newOrder, evaluationOrderHolder.getValue()); - } else { - LOGGER.trace("Keeping order for {} at {} -> {}", constraint.getRelation(), currentOrder, evaluationOrderHolder.getValue()); - } - } - } - Map difference = beforeChange.diff(evaluationOrderHolder.getValue()); - targetEvaluationOrderHolder.setValue(targetEvaluationOrderHolder.getValue().applyDifference(difference)); - } - - if (evaluationOrderHolder.getValue().getSummaryOrder() <= 0) { - makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); - } - if (!targetEvaluationOrderHolder.getValue().isValid()) { - // some extreme cases like the one described in TestAssignmentProcessor2.test520 - makeUndefined(targetEvaluationOrderHolder); - } - if (!evaluationOrderHolder.getValue().isValid()) { - throw new AssertionError("Resulting evaluation order path is invalid: " + evaluationOrderHolder.getValue()); - } - } - - @SafeVarargs - private final void makeUndefined(Holder... holders) { // final because of SafeVarargs (on java8) - for (Holder holder : holders) { - holder.setValue(EvaluationOrderImpl.UNDEFINED); - } - } - - private void addToMembershipLists(AssignmentHolderType targetToAdd, QName relation, EvaluationContext ctx) { - PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); - valueToAdd.setObject(targetToAdd.asPrismObject()); - valueToAdd.setTargetType(ObjectTypes.getObjectType(targetToAdd.getClass()).getTypeQName()); - valueToAdd.setRelation(relation); - valueToAdd.setTargetName(targetToAdd.getName().toPolyString()); - - addToMembershipLists(valueToAdd, targetToAdd.getClass(), relation, targetToAdd, ctx); - } - - private void setAsTenantRef(AssignmentHolderType targetToSet, EvaluationContext ctx) { - if (targetToSet instanceof OrgType) { - if (BooleanUtils.isTrue(((OrgType)targetToSet).isTenant()) && ctx.evalAssignment.getTenantOid() == null) { - if (ctx.assignmentPath.hasOnlyOrgs()) { - ctx.evalAssignment.setTenantOid(targetToSet.getOid()); - } - } - } - } - - private void addToMembershipLists(ObjectReferenceType referenceToAdd, QName relation, EvaluationContext ctx) { - PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); - valueToAdd.setOid(referenceToAdd.getOid()); - valueToAdd.setTargetType(referenceToAdd.getType()); - valueToAdd.setRelation(relation); - valueToAdd.setTargetName(referenceToAdd.getTargetName()); - - Class targetClass = ObjectTypes.getObjectTypeFromTypeQName(referenceToAdd.getType()).getClassDefinition(); - addToMembershipLists(valueToAdd, targetClass, relation, referenceToAdd, ctx); - } - - private void addToMembershipLists(PrismReferenceValue valueToAdd, Class targetClass, QName relation, - Object targetDesc, EvaluationContext ctx) { - if (ctx.assignmentPath.containsDelegation(ctx.evaluateOld, relationRegistry)) { - addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), valueToAdd, "delegationRef", targetDesc); - } else { - if (AbstractRoleType.class.isAssignableFrom(targetClass)) { - addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), valueToAdd, "membershipRef", targetDesc); - } - } - if (OrgType.class.isAssignableFrom(targetClass) && relationRegistry.isStoredIntoParentOrgRef(relation)) { - addIfNotThere(ctx.evalAssignment.getOrgRefVals(), valueToAdd, "orgRef", targetDesc); - } - if (ArchetypeType.class.isAssignableFrom(targetClass)) { - addIfNotThere(ctx.evalAssignment.getArchetypeRefVals(), valueToAdd, "archetypeRef", targetDesc); - } - } - - private void addIfNotThere(Collection collection, PrismReferenceValue valueToAdd, String collectionName, - Object targetDesc) { - if (!collection.contains(valueToAdd)) { - LOGGER.trace("Adding target {} to {}", targetDesc, collectionName); - collection.add(valueToAdd); - } else { - LOGGER.trace("Would add target {} to {}, but it's already there", targetDesc, collectionName); - } - } - - private boolean isNonNegative(PlusMinusZero mode) { - // mode == null is also considered negative, because it is a combination of PLUS and MINUS; - // so the net result is that for both old and new state there exists an unsatisfied condition on the path. - return mode == PlusMinusZero.ZERO || mode == PlusMinusZero.PLUS; - } - - private boolean isChanged(PlusMinusZero mode) { - // mode == null is also considered negative, because it is a combination of PLUS and MINUS; - // so the net result is that for both old and new state there exists an unsatisfied condition on the path. - return mode == PlusMinusZero.PLUS || mode == PlusMinusZero.MINUS; - } - - private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, AssignmentHolderType targetType, QName relation) - throws SchemaException { - if (targetType instanceof AbstractRoleType || targetType instanceof TaskType) { //TODO: - // OK, just go on - } else if (targetType instanceof UserType) { - if (!relationRegistry.isDelegation(relation)) { - throw new SchemaException("Unsupported relation " + relation + " for assignment of target type " + targetType + " in " + segment.sourceDescription); - } - } else { - throw new SchemaException("Unknown assignment target type " + targetType + " in " + segment.sourceDescription); - } - } - - private boolean isInducementApplicableToFocusType(QName inducementFocusType) throws SchemaException { - if (inducementFocusType == null) { - return true; - } - Class inducementFocusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); - if (inducementFocusClass == null) { - throw new SchemaException("Could not determine class for " + inducementFocusType); - } - if (lensContext.getFocusClass() == null) { - // should not occur; it would be probably safe to throw an exception here - LOGGER.error("No focus class in lens context; inducement targeted at focus type {} will not be applied:\n{}", - inducementFocusType, lensContext.debugDump()); - return false; - } - return inducementFocusClass.isAssignableFrom(lensContext.getFocusClass()); - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean isAllowedByLimitations(AssignmentPathSegment segment, AssignmentType nextAssignment, EvaluationContext ctx) { - AssignmentType currentAssignment = segment.getAssignment(ctx.evaluateOld); - AssignmentSelectorType targetLimitation = currentAssignment.getLimitTargetContent(); - if (isDeputyDelegation(nextAssignment)) { // delegation of delegation - return targetLimitation != null && BooleanUtils.isTrue(targetLimitation.isAllowTransitive()); - } else { - // As for the case of targetRef==null: we want to pass target-less assignments (focus mappings, policy rules etc) - // from the delegator to delegatee. To block them we should use order constraints (but also for assignments?). - return targetLimitation == null || nextAssignment.getTargetRef() == null || - FocusTypeUtil.selectorMatches(targetLimitation, nextAssignment, prismContext); - } - } - - private boolean isDeputyDelegation(AssignmentType assignmentType) { - ObjectReferenceType targetRef = assignmentType.getTargetRef(); - return targetRef != null && relationRegistry.isDelegation(targetRef.getRelation()); - } - - private Authorization createAuthorization(AuthorizationType authorizationType, String sourceDesc) { - Authorization authorization = new Authorization(authorizationType); - authorization.setSourceDescription(sourceDesc); - return authorization; - } - - private void assertSourceNotNull(ObjectType source, EvaluatedAssignment assignment) { - if (source == null) { - throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignment+")"); - } - } - - private void assertSourceNotNull(ObjectType source, ItemDeltaItem,PrismContainerDefinition> assignmentIdi) { - if (source == null) { - throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignmentIdi.getAnyItem()+")"); - } - } - - private AssignmentType getAssignmentType(AssignmentPathSegmentImpl segment, EvaluationContext ctx) { - return segment.getAssignment(ctx.evaluateOld); - } - - private void checkSchema(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException { - AssignmentType assignmentType = getAssignmentType(segment, ctx); - //noinspection unchecked - PrismContainerValue assignmentContainerValue = assignmentType.asPrismContainerValue(); - PrismContainerable assignmentContainer = assignmentContainerValue.getParent(); - if (assignmentContainer == null) { - throw new SchemaException("The assignment "+assignmentType+" does not have a parent in "+segment.sourceDescription); - } - if (assignmentContainer.getDefinition() == null) { - throw new SchemaException("The assignment "+assignmentType+" does not have definition in "+segment.sourceDescription); - } - PrismContainer extensionContainer = assignmentContainerValue.findContainer(AssignmentType.F_EXTENSION); - if (extensionContainer != null) { - if (extensionContainer.getDefinition() == null) { - throw new SchemaException("Extension does not have a definition in assignment "+assignmentType+" in "+segment.sourceDescription); - } - - if (extensionContainer.getValue().getItems() == null) { - throw new SchemaException("Extension without items in assignment " + assignmentType + " in " + segment.sourceDescription + ", empty extension tag?"); - } - - for (Item item: extensionContainer.getValue().getItems()) { - if (item == null) { - throw new SchemaException("Null item in extension in assignment "+assignmentType+" in "+segment.sourceDescription); - } - if (item.getDefinition() == null) { - throw new SchemaException("Item "+item+" has no definition in extension in assignment "+assignmentType+" in "+segment.sourceDescription); - } - } - } - } - - private void setEvaluatedAssignmentTarget(AssignmentPathSegmentImpl segment, - @NotNull List> targets, EvaluationContext ctx) { - assert ctx.evalAssignment.getTarget() == null; - if (targets.size() > 1) { - throw new UnsupportedOperationException("Multiple targets for direct focus assignment are not supported: " + segment.getAssignment(ctx.evaluateOld)); - } else if (!targets.isEmpty()) { - ctx.evalAssignment.setTarget(targets.get(0)); - } - } - - private PrismValueDeltaSetTriple> evaluateCondition(MappingType condition, - ObjectType source, AssignmentPathVariables assignmentPathVariables, String contextDescription, EvaluationContext ctx, - OperationResult result) throws ExpressionEvaluationException, - ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { - MappingImpl.Builder,PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder(); - builder = builder.mappingType(condition) - .contextDescription(contextDescription) - .sourceContext(focusOdo) - .originType(OriginType.ASSIGNMENTS) - .originObject(source) - .defaultTargetDefinition(prismContext.definitionFactory().createPropertyDefinition(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN)) - .addVariableDefinitions(getAssignmentEvaluationVariables()) - .rootNode(focusOdo) - .addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo) - .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo) - .addAliasRegistration(ExpressionConstants.VAR_USER, null) - .addAliasRegistration(ExpressionConstants.VAR_FOCUS, null) - .addVariableDefinition(ExpressionConstants.VAR_SOURCE, source, ObjectType.class) - .addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT_EVALUATOR, this, AssignmentEvaluator.class); - builder = LensUtil.addAssignmentPathVariables(builder, assignmentPathVariables, prismContext); - - MappingImpl, PrismPropertyDefinition> mapping = builder.build(); - - mappingEvaluator.evaluateMapping(mapping, lensContext, ctx.task, result); - - return mapping.getOutputTriple(); - } - - @Nullable - private QName getRelation(AssignmentType assignmentType) { - return assignmentType.getTargetRef() != null ? - relationRegistry.normalizeRelation(assignmentType.getTargetRef().getRelation()) : null; - } - - /* - * This "isMemberOf iteration" section is an experimental implementation of MID-5366. - * - * The main idea: In role/assignment/inducement conditions we test the membership not by querying roleMembershipRef - * on focus object but instead we call assignmentEvaluator.isMemberOf() method. This method - by default - inspects - * roleMembershipRef but also records the check result. Later, when assignment evaluation is complete, AssignmentProcessor - * will ask if all of these check results are still valid. If they are not, it requests re-evaluation of all the assignments, - * using updated check results. - * - * This should work unless there are some cyclic dependencies (like "this sentence is a lie" paradox). - */ - public boolean isMemberOf(String targetOid) { - if (targetOid == null) { - throw new IllegalArgumentException("Null targetOid in isMemberOf call"); - } - MemberOfInvocation existingInvocation = findInvocation(targetOid); - if (existingInvocation != null) { - return existingInvocation.result; - } else { - boolean result = computeIsMemberOfDuringEvaluation(targetOid); - memberOfInvocations.add(new MemberOfInvocation(targetOid, result)); - return result; - } - } - - private MemberOfInvocation findInvocation(String targetOid) { - List matching = memberOfInvocations.stream() - .filter(invocation -> targetOid.equals(invocation.targetOid)) - .collect(Collectors.toList()); - if (matching.isEmpty()) { - return null; - } else if (matching.size() == 1) { - return matching.get(0); - } else { - throw new IllegalStateException("More than one matching MemberOfInvocation for targetOid='" + targetOid + "': " + matching); - } - } - - private boolean computeIsMemberOfDuringEvaluation(String targetOid) { - // TODO Or should we consider evaluateOld? - PrismObject focus = focusOdo.getNewObject(); - return focus != null && containsMember(focus.asObjectable().getRoleMembershipRef(), targetOid); - } - - public boolean isMemberOfInvocationResultChanged(DeltaSetTriple> evaluatedAssignmentTriple) { - if (!memberOfInvocations.isEmpty()) { - // Similar code is in AssignmentProcessor.processMembershipAndDelegatedRefs -- check that if changing the business logic - List membership = evaluatedAssignmentTriple.getNonNegativeValues().stream() - .filter(EvaluatedAssignmentImpl::isValid) - .flatMap(evaluatedAssignment -> evaluatedAssignment.getMembershipRefVals().stream()) - .map(ref -> ObjectTypeUtil.createObjectRef(ref, false)) - .collect(Collectors.toList()); - LOGGER.trace("Computed new membership: {}", membership); - return updateMemberOfInvocations(membership); - } else { - return false; - } - } - - private boolean updateMemberOfInvocations(List newMembership) { - boolean changed = false; - for (MemberOfInvocation invocation : memberOfInvocations) { - boolean newResult = containsMember(newMembership, invocation.targetOid); - if (newResult != invocation.result) { - LOGGER.trace("Invocation result changed for {} - new one is '{}'", invocation, newResult); - invocation.result = newResult; - changed = true; - } - } - return changed; - } - - // todo generalize a bit (e.g. by including relation) - private boolean containsMember(List membership, String targetOid) { - return membership.stream().anyMatch(ref -> targetOid.equals(ref.getOid())); - } - - public static final class Builder { - private RepositoryService repository; - private ObjectDeltaObject focusOdo; - private LensContext lensContext; - private String channel; - private ObjectResolver objectResolver; - private SystemObjectCache systemObjectCache; - private RelationRegistry relationRegistry; - private PrismContext prismContext; - private MappingFactory mappingFactory; - private ActivationComputer activationComputer; - private XMLGregorianCalendar now; - private boolean loginMode = false; - private PrismObject systemConfiguration; - private MappingEvaluator mappingEvaluator; - - public Builder() { - } - - public Builder repository(RepositoryService val) { - repository = val; - return this; - } - - public Builder focusOdo(ObjectDeltaObject val) { - focusOdo = val; - return this; - } - - public Builder lensContext(LensContext val) { - lensContext = val; - return this; - } - - public Builder channel(String val) { - channel = val; - return this; - } - - public Builder objectResolver(ObjectResolver val) { - objectResolver = val; - return this; - } - - public Builder systemObjectCache(SystemObjectCache val) { - systemObjectCache = val; - return this; - } - - public Builder relationRegistry(RelationRegistry val) { - relationRegistry = val; - return this; - } - - public Builder prismContext(PrismContext val) { - prismContext = val; - return this; - } - - public Builder mappingFactory(MappingFactory val) { - mappingFactory = val; - return this; - } - - public Builder activationComputer(ActivationComputer val) { - activationComputer = val; - return this; - } - - public Builder now(XMLGregorianCalendar val) { - now = val; - return this; - } - - public Builder loginMode(boolean val) { - loginMode = val; - return this; - } - - public Builder systemConfiguration(PrismObject val) { - systemConfiguration = val; - return this; - } - - public Builder mappingEvaluator(MappingEvaluator val) { - mappingEvaluator = val; - return this; - } - - public AssignmentEvaluator build() { - return new AssignmentEvaluator<>(this); - } - } - - private static class MemberOfInvocation { - private final String targetOid; - private boolean result; - - private MemberOfInvocation(String targetOid, boolean result) { - this.targetOid = targetOid; - this.result = result; - } - - @Override - public String toString() { - return "MemberOfInvocation{" + - "targetOid='" + targetOid + '\'' + - ", result=" + result + - '}'; - } - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import java.util.*; +import java.util.stream.Collectors; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.ActivationComputer; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.api.context.AssignmentPathSegment; +import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; +import com.evolveum.midpoint.model.api.context.EvaluationOrder; +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.internals.InternalMonitor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.security.api.Authorization; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.Holder; +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.PolicyViolationException; +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.*; +import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; + +import com.evolveum.prism.xml.ns._public.types_3.PlusMinusZeroType; +import org.apache.commons.lang.BooleanUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An engine that creates EvaluatedAssignment from an assignment IDI. It collects induced roles, constructions, + * authorizations, policy rules, and so on. + * + * @author semancik + */ +public class AssignmentEvaluator { + + private static final QName CONDITION_OUTPUT_NAME = new QName(SchemaConstants.NS_C, "condition"); + + private static final String OP_EVALUATE = AssignmentEvaluator.class.getName()+".evaluate"; + private static final String OP_EVALUATE_FROM_SEGMENT = AssignmentEvaluator.class.getName()+".evaluateFromSegment"; + + private static final Trace LOGGER = TraceManager.getTrace(AssignmentEvaluator.class); + + // "Configuration parameters" + private final RepositoryService repository; + private final ObjectDeltaObject focusOdo; + private final LensContext lensContext; + private final String channel; + private final ObjectResolver objectResolver; + private final SystemObjectCache systemObjectCache; + private final RelationRegistry relationRegistry; + private final PrismContext prismContext; + private final MappingFactory mappingFactory; + private final ActivationComputer activationComputer; + private final XMLGregorianCalendar now; + private final boolean loginMode; // restricted mode, evaluating only authorizations and gui config (TODO name) + private final PrismObject systemConfiguration; + private final MappingEvaluator mappingEvaluator; + private final EvaluatedAssignmentTargetCache evaluatedAssignmentTargetCache; + private final LifecycleStateModelType focusStateModel; + + // Evaluation state + private final List memberOfInvocations = new ArrayList<>(); // experimental + + private AssignmentEvaluator(Builder builder) { + repository = builder.repository; + focusOdo = builder.focusOdo; + lensContext = builder.lensContext; + channel = builder.channel; + objectResolver = builder.objectResolver; + systemObjectCache = builder.systemObjectCache; + relationRegistry = builder.relationRegistry; + prismContext = builder.prismContext; + mappingFactory = builder.mappingFactory; + activationComputer = builder.activationComputer; + now = builder.now; + loginMode = builder.loginMode; + systemConfiguration = builder.systemConfiguration; + mappingEvaluator = builder.mappingEvaluator; + evaluatedAssignmentTargetCache = new EvaluatedAssignmentTargetCache(); + + LensFocusContext focusContext = lensContext.getFocusContext(); + if (focusContext != null) { + focusStateModel = focusContext.getLifecycleModel(); + } else { + focusStateModel = null; + } + } + + public RepositoryService getRepository() { + return repository; + } + + @SuppressWarnings("unused") + public ObjectDeltaObject getFocusOdo() { + return focusOdo; + } + + public LensContext getLensContext() { + return lensContext; + } + + public String getChannel() { + return channel; + } + + public ObjectResolver getObjectResolver() { + return objectResolver; + } + + public SystemObjectCache getSystemObjectCache() { + return systemObjectCache; + } + + public PrismContext getPrismContext() { + return prismContext; + } + + public MappingFactory getMappingFactory() { + return mappingFactory; + } + + public ActivationComputer getActivationComputer() { + return activationComputer; + } + + public XMLGregorianCalendar getNow() { + return now; + } + + @SuppressWarnings("unused") + public boolean isLoginMode() { + return loginMode; + } + + public PrismObject getSystemConfiguration() { + return systemConfiguration; + } + + @SuppressWarnings("unused") + public MappingEvaluator getMappingEvaluator() { + return mappingEvaluator; + } + + public void reset(boolean alsoMemberOfInvocations) { + evaluatedAssignmentTargetCache.reset(); + if (alsoMemberOfInvocations) { + memberOfInvocations.clear(); + } + } + + // This is to reduce the number of parameters passed between methods in this class. + // Moreover, it highlights the fact that identity of objects referenced here is fixed for any invocation of the evaluate() method. + // (There is single EvaluationContext instance for any call to evaluate().) + private class EvaluationContext { + @NotNull private final EvaluatedAssignmentImpl evalAssignment; + @NotNull private final AssignmentPathImpl assignmentPath; + // The primary assignment mode tells whether the primary assignment was added, removed or it is unchanged. + // The primary assignment is the first assignment in the assignment path, the assignment that is located in the + // focal object. + private final PlusMinusZero primaryAssignmentMode; + private final boolean evaluateOld; + private final Task task; + private EvaluationContext(@NotNull EvaluatedAssignmentImpl evalAssignment, + @NotNull AssignmentPathImpl assignmentPath, + PlusMinusZero primaryAssignmentMode, boolean evaluateOld, Task task) { + this.evalAssignment = evalAssignment; + this.assignmentPath = assignmentPath; + this.primaryAssignmentMode = primaryAssignmentMode; + this.evaluateOld = evaluateOld; + this.task = task; + } + } + + /** + * evaluateOld: If true, we take the 'old' value from assignmentIdi. If false, we take the 'new' one. + */ + public EvaluatedAssignmentImpl evaluate( + ItemDeltaItem,PrismContainerDefinition> assignmentIdi, + PlusMinusZero primaryAssignmentMode, boolean evaluateOld, AssignmentHolderType source, String sourceDescription, + AssignmentOrigin origin, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + OperationResult result = parentResult.subresult(OP_EVALUATE) + .setMinor() + .addArbitraryObjectAsParam("primaryAssignmentMode", primaryAssignmentMode) + .addParam("evaluateOld", evaluateOld) + .addArbitraryObjectAsParam("source", source) + .addParam("sourceDescription", sourceDescription) + .addArbitraryObjectAsParam("origin", origin) + .build(); + AssignmentEvaluationTraceType trace; + if (result.isTracingNormal(AssignmentEvaluationTraceType.class)) { + trace = new AssignmentEvaluationTraceType(prismContext) + .assignmentOld(CloneUtil.clone(getAssignmentBean(assignmentIdi, true))) + .assignmentNew(CloneUtil.clone(getAssignmentBean(assignmentIdi, false))) + .primaryAssignmentMode(PlusMinusZeroType.fromValue(primaryAssignmentMode)) + .evaluateOld(evaluateOld) + .textSource(source != null ? source.asPrismObject().debugDump() : "null") + .sourceDescription(sourceDescription); + result.addTrace(trace); + } else { + trace = null; + } + try { + assertSourceNotNull(source, assignmentIdi); + + EvaluatedAssignmentImpl evalAssignmentImpl = new EvaluatedAssignmentImpl<>(assignmentIdi, evaluateOld, origin, prismContext); + + EvaluationContext ctx = new EvaluationContext( + evalAssignmentImpl, + new AssignmentPathImpl(prismContext), + primaryAssignmentMode, evaluateOld, task); + + evaluatedAssignmentTargetCache.resetForNextAssignment(); + + AssignmentPathSegmentImpl segment = new AssignmentPathSegmentImpl(source, sourceDescription, assignmentIdi, true, + evaluateOld, relationRegistry, prismContext); + segment.setEvaluationOrder(getInitialEvaluationOrder(assignmentIdi, ctx)); + segment.setEvaluationOrderForTarget(EvaluationOrderImpl.zero(relationRegistry)); + segment.setValidityOverride(true); + segment.setPathToSourceValid(true); + segment.setProcessMembership(true); + segment.setRelation(getRelation(getAssignmentType(segment, ctx))); + + evaluateFromSegment(segment, PlusMinusZero.ZERO, ctx, result); + + if (segment.getTarget() != null) { + result.addContext("assignmentTargetName", PolyString.getOrig(segment.getTarget().getName())); + } + + LOGGER.trace("Assignment evaluation finished:\n{}", ctx.evalAssignment.debugDumpLazily()); + if (trace != null) { + trace.setTextResult(ctx.evalAssignment.debugDump()); + } + result.computeStatusIfUnknown(); + return ctx.evalAssignment; + } catch (Throwable t) { + result.recordFatalError(t.getMessage(), t); + throw t; + } + } + + private AssignmentType getAssignmentBean( + ItemDeltaItem, PrismContainerDefinition> assignmentIdi, + boolean old) { + PrismContainerValue pcv = assignmentIdi.getSingleValue(old); + return pcv != null ? pcv.asContainerable() : null; + } + + private EvaluationOrder getInitialEvaluationOrder( + ItemDeltaItem, PrismContainerDefinition> assignmentIdi, + EvaluationContext ctx) { + AssignmentType assignmentType = LensUtil.getAssignmentType(assignmentIdi, ctx.evaluateOld); + return EvaluationOrderImpl.zero(relationRegistry).advance(getRelation(assignmentType)); + } + + /** + * @param relativeMode + * + * Where to put constructions and target roles/orgs/services (PLUS/MINUS/ZERO/null; null means "nowhere"). + * This is a mode relative to the primary assignment. It does NOT tell whether the assignment as a whole + * is added or removed. It tells whether the part of the assignment that we are processing is to be + * added or removed. This may happen, e.g. if a condition in an existing assignment turns from false to true. + * In that case the primary assignment mode is ZERO, but the relative mode is PLUS. + * The relative mode always starts at ZERO, even for added or removed assignments. + * + * This depends on the status of conditions. E.g. if condition evaluates 'false -> true' (i.e. in old + * state the value is false, and in new state the value is true), then the mode is PLUS. + * + * This "triples algebra" is based on the following two methods: + * + * @see ExpressionUtil#computeConditionResultMode(boolean, boolean) - Based on condition values "old+new" determines + * into what set (PLUS/MINUS/ZERO/none) should the result be placed. Irrespective of what is the current mode. So, + * in order to determine "real" place where to put it (i.e. the new mode) the following method is used. + * + * @see PlusMinusZero#compute(PlusMinusZero, PlusMinusZero) - Takes original mode and the mode from recent condition + * and determines the new mode (commutatively): + * + * PLUS + PLUS/ZERO = PLUS + * MINUS + MINUS/ZERO = MINUS + * ZERO + ZERO = ZERO + * PLUS + MINUS = none + * + * This is quite straightforward, although the last rule deserves a note. If we have an assignment that was originally + * disabled and becomes enabled by the current delta (i.e. PLUS), and that assignment contains an inducement that was originally + * enabled and becomes disabled (i.e. MINUS), the result is that the (e.g.) constructions within the inducement were not + * present in the old state (because assignment was disabled) and are not present in the new state (because inducement is disabled). + * + * Note: this parameter could be perhaps renamed to "tripleMode" or "destination" or something like that. + */ + private void evaluateFromSegment(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, EvaluationContext ctx, + OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + OperationResult result = parentResult.subresult(OP_EVALUATE_FROM_SEGMENT) + .setMinor() + .addParam("segment", segment.shortDump()) + .addArbitraryObjectAsParam("relativeMode", relativeMode) + .build(); + AssignmentSegmentEvaluationTraceType trace; + if (result.isTracingNormal(AssignmentSegmentEvaluationTraceType.class)) { + trace = new AssignmentSegmentEvaluationTraceType(prismContext) + .segment(segment.toAssignmentPathSegmentType(true)) + .mode(PlusMinusZeroType.fromValue(relativeMode)); + result.addTrace(trace); + } else { + trace = null; + } + try { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("*** Evaluate from segment: {}", segment); + LOGGER.trace("*** Evaluation order - standard: {}, matching: {}", segment.getEvaluationOrder(), segment.isMatchingOrder()); + LOGGER.trace("*** Evaluation order - for target: {}, matching: {}", segment.getEvaluationOrderForTarget(), segment.isMatchingOrderForTarget()); + LOGGER.trace("*** mode: {}, process membership: {}", relativeMode, segment.isProcessMembership()); + LOGGER.trace("*** path to source valid: {}, validity override: {}", segment.isPathToSourceValid(), segment.isValidityOverride()); + } + + assertSourceNotNull(segment.source, ctx.evalAssignment); + checkSchema(segment, ctx); + + ctx.assignmentPath.add(segment); + LOGGER.trace("*** Path (with current segment already added):\n{}", ctx.assignmentPath.debugDumpLazily()); + + boolean evaluateContent; + AssignmentType assignmentType = getAssignmentType(segment, ctx); + MappingType assignmentCondition = assignmentType.getCondition(); + if (assignmentCondition != null) { + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(assignmentCondition, + segment.source, assignmentPathVariables, + "condition in assignment in " + segment.getSourceDescription(), ctx, result); + boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); + boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); + PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); + if (modeFromCondition == null) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: {})", + FocusTypeUtil.dumpAssignment(assignmentType), condOld, condNew, null); + } + evaluateContent = false; + } else { + PlusMinusZero origMode = relativeMode; + relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); + LOGGER.trace("Evaluated condition in assignment {} -> {}: {} + {} = {}", condOld, condNew, origMode, + modeFromCondition, relativeMode); + evaluateContent = true; + } + } else { + evaluateContent = true; + } + + if (ctx.assignmentPath.isEmpty() && ctx.evalAssignment.isVirtual()) { + segment.setValidityOverride(ctx.evalAssignment.isVirtual()); + } + + boolean isValid = + (evaluateContent && evaluateSegmentContent(segment, relativeMode, ctx, result)) || ctx.evalAssignment.isVirtual(); + + ctx.assignmentPath.removeLast(segment); + if (ctx.assignmentPath.isEmpty()) { // direct assignment + ctx.evalAssignment.setValid(isValid); + } + + LOGGER.trace("evalAssignment isVirtual {} ", ctx.evalAssignment.isVirtual()); + if (segment.getSource() != null) { + result.addContext("segmentSourceName", PolyString.getOrig(segment.getSource().getName())); + } + if (segment.getTarget() != null) { + result.addContext("segmentTargetName", PolyString.getOrig(segment.getTarget().getName())); + } + result.addArbitraryObjectAsReturn("relativeMode", relativeMode); + result.addReturn("evaluateContent", evaluateContent); + result.addReturn("isValid", isValid); + if (trace != null) { + trace.setTextResult(segment.debugDump()); + } + } catch (Throwable t) { + result.recordFatalError(t.getMessage(), t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + // "content" means "payload + targets" here + private boolean evaluateSegmentContent(AssignmentPathSegmentImpl segment, + PlusMinusZero relativeMode, EvaluationContext ctx, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + + assert ctx.assignmentPath.last() == segment; + + final boolean isDirectAssignment = ctx.assignmentPath.size() == 1; + + AssignmentType assignment = getAssignmentType(segment, ctx); + + // Assignment validity is checked with respect to the assignment source, not to the focus object. + // So, if (e.g.) focus is in "draft" state, only validity of direct assignments should be influenced by this fact. + // Other assignments (e.g. from roles to metaroles) should be considered valid, provided these roles are + // in active lifecycle states. See also MID-6114. + AssignmentHolderType source = segment.isMatchingOrder() ? focusOdo.getNewObject().asObjectable() : segment.getSource(); + boolean isAssignmentValid = LensUtil.isAssignmentValid(source, assignment, now, activationComputer, focusStateModel); + if (isAssignmentValid || segment.isValidityOverride()) { + // Note: validityOverride is currently the same as "isDirectAssignment" - which is very probably OK. + // Direct assignments are visited even if they are not valid (i.e. effectively disabled). + // It is because we need to collect e.g. assignment policy rules for them. + // Also because we could have deltas that disable/enable these assignments. + boolean reallyValid = segment.isPathToSourceValid() && isAssignmentValid; + if (!loginMode && segment.isMatchingOrder()) { + if (assignment.getConstruction() != null) { + collectConstruction(segment, relativeMode, reallyValid, ctx); + } + if (assignment.getPersonaConstruction() != null) { + collectPersonaConstruction(segment, relativeMode, reallyValid, ctx); + } + if (assignment.getFocusMappings() != null) { + if (reallyValid && relativeMode != null) { // null relative mode means both PLUS and MINUS + collectFocusMappings(segment, relativeMode, ctx); + } + } + } + if (!loginMode && assignment.getPolicyRule() != null) { + // Here we ignore "reallyValid". It is OK, because reallyValid can be false here only when + // evaluating direct assignments; and invalid ones are marked as such via EvaluatedAssignment.isValid. + // TODO is it ok? + if (isNonNegative(relativeMode)) { + if (segment.isMatchingOrder()) { + collectPolicyRule(true, segment, ctx); + } + if (segment.isMatchingOrderForTarget()) { + collectPolicyRule(false, segment, ctx); + } + } + } + if (assignment.getTargetRef() != null) { + QName relation = getRelation(assignment); + if (loginMode && !relationRegistry.isProcessedOnLogin(relation)) { + LOGGER.trace("Skipping processing of assignment target {} because relation {} is configured for login skip", assignment.getTargetRef().getOid(), relation); + // Skip - to optimize logging-in, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) + // We want to make this configurable in the future MID-3581 + } else if (!loginMode && !isChanged(ctx.primaryAssignmentMode) && + !relationRegistry.isProcessedOnRecompute(relation) && !shouldEvaluateAllAssignmentRelationsOnRecompute()) { + LOGGER.debug("Skipping processing of assignment target {} because relation {} is configured for recompute skip (mode={})", assignment.getTargetRef().getOid(), relation, relativeMode); + // Skip - to optimize recompute, we skip all assignments with non-membership/non-delegation relations (e.g. approver, owner, etc) + // never skip this if assignment has changed. We want to process this, e.g. to enforce min/max assignee rules + // We want to make this configurable in the future MID-3581 + + // Important: but we still want this to be reflected in roleMembershipRef + if ((isNonNegative(relativeMode)) && segment.isProcessMembership()) { + if (assignment.getTargetRef().getOid() != null) { + addToMembershipLists(assignment.getTargetRef(), relation, ctx); + } else { + // no OID, so we have to resolve the filter + for (PrismObject targetObject : getTargets(segment, ctx, result)) { + ObjectType target = targetObject.asObjectable(); + if (target instanceof FocusType) { + addToMembershipLists((FocusType) target, relation, ctx); + } + } + } + } + } else { + List> targets = getTargets(segment, ctx, result); + LOGGER.trace("Targets in {}, assignment ID {}: {}", segment.source, assignment.getId(), targets); + if (isDirectAssignment) { + setEvaluatedAssignmentTarget(segment, targets, ctx); + } + for (PrismObject target : targets) { + if (hasCycle(segment, target, ctx)) { + continue; + } + if (isDelegationToNonDelegableTarget(assignment, target, ctx)) { + continue; + } + evaluateSegmentTarget(segment, relativeMode, reallyValid, (AssignmentHolderType) target.asObjectable(), relation, ctx, result); + } + } + } + } else { + LOGGER.trace("Skipping evaluation of assignment {} because it is not valid", assignment); + } + return isAssignmentValid; + } + + private boolean shouldEvaluateAllAssignmentRelationsOnRecompute() { + return ModelExecuteOptions.isEvaluateAllAssignmentRelationsOnRecompute(lensContext.getOptions()); + } + + private boolean isDelegationToNonDelegableTarget(AssignmentType assignmentType, @NotNull PrismObject target, + EvaluationContext ctx) { + AssignmentPathSegment previousSegment = ctx.assignmentPath.beforeLast(1); + if (previousSegment == null || !previousSegment.isDelegation() || !target.canRepresent(AbstractRoleType.class)) { + return false; + } + if (!Boolean.TRUE.equals(((AbstractRoleType)target.asObjectable()).isDelegable())) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping evaluation of {} because it delegates to a non-delegable target {}", + FocusTypeUtil.dumpAssignment(assignmentType), target); + } + return true; + } else { + return false; + } + } + + // number of times any given target is allowed to occur in the assignment path + private static final int MAX_TARGET_OCCURRENCES = 2; + + private boolean hasCycle(AssignmentPathSegmentImpl segment, @NotNull PrismObject target, + EvaluationContext ctx) throws PolicyViolationException { + // TODO reconsider this + if (target.getOid().equals(segment.source.getOid())) { + throw new PolicyViolationException("The "+segment.source+" refers to itself in assignment/inducement"); + } + // removed condition "&& segment.getEvaluationOrder().equals(ctx.assignmentPath.getEvaluationOrder())" + // as currently it is always true + // TODO reconsider this + int count = ctx.assignmentPath.countTargetOccurrences(target.asObjectable()); + if (count >= MAX_TARGET_OCCURRENCES) { + LOGGER.debug("Max # of target occurrences ({}) detected for target {} in {} - stopping evaluation here", + MAX_TARGET_OCCURRENCES, ObjectTypeUtil.toShortString(target), ctx.assignmentPath); + return true; + } else { + return false; + } + } + + private void collectConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + AssignmentType assignmentType = getAssignmentType(segment, ctx); + ConstructionType constructionType = assignmentType.getConstruction(); + + LOGGER.trace("Preparing construction '{}' in {}", constructionType.getDescription(), segment.source); + + Construction construction = new Construction<>(constructionType, segment.source); + // We have to clone here as the path is constantly changing during evaluation + construction.setAssignmentPath(ctx.assignmentPath.clone()); + construction.setFocusOdo(focusOdo); + construction.setLensContext(lensContext); + construction.setObjectResolver(objectResolver); + construction.setPrismContext(prismContext); + construction.setMappingFactory(mappingFactory); + construction.setMappingEvaluator(mappingEvaluator); + construction.setOriginType(OriginType.ASSIGNMENTS); + construction.setChannel(channel); + construction.setOrderOneObject(segment.getOrderOneObject()); + construction.setValid(isValid); + construction.setRelativityMode(mode); + + // Do not evaluate the construction here. We will do it in the second pass. Just prepare everything to be evaluated. + if (mode == null) { + return; // null mode (i.e. plus + minus) means 'ignore the payload' + } + ctx.evalAssignment.addConstruction(construction, mode); + } + + private void collectPersonaConstruction(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx) { + assertSourceNotNull(segment.source, ctx.evalAssignment); + if (mode == null) { + return; // null mode (i.e. plus + minus) means 'ignore the payload' + } + + AssignmentType assignmentType = getAssignmentType(segment, ctx); + PersonaConstructionType constructionType = assignmentType.getPersonaConstruction(); + + LOGGER.trace("Preparing persona construction '{}' in {}", constructionType.getDescription(), segment.source); + + PersonaConstruction construction = new PersonaConstruction<>(constructionType, segment.source); + // We have to clone here as the path is constantly changing during evaluation + construction.setAssignmentPath(ctx.assignmentPath.clone()); + construction.setFocusOdo(focusOdo); + construction.setLensContext(lensContext); + construction.setObjectResolver(objectResolver); + construction.setPrismContext(prismContext); + construction.setOriginType(OriginType.ASSIGNMENTS); + construction.setChannel(channel); + construction.setValid(isValid); + construction.setRelativityMode(mode); + + ctx.evalAssignment.addPersonaConstruction(construction, mode); + } + + private void collectFocusMappings(AssignmentPathSegmentImpl segment, @NotNull PlusMinusZero relativeMode, EvaluationContext ctx) + throws SchemaException { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + AssignmentType assignmentBean = getAssignmentType(segment, ctx); + MappingsType mappingsBean = assignmentBean.getFocusMappings(); + + LOGGER.trace("Request evaluation of focus mappings '{}' in {} ({} mappings)", + mappingsBean.getDescription(), segment.source, mappingsBean.getMapping().size()); + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + + for (MappingType mappingBean: mappingsBean.getMapping()) { + AssignedFocusMappingEvaluationRequest request = new AssignedFocusMappingEvaluationRequest(mappingBean, segment.source, + ctx.evalAssignment, relativeMode, assignmentPathVariables, segment.sourceDescription); + ctx.evalAssignment.addFocusMappingEvaluationRequest(request); + } + } + + private void collectPolicyRule(boolean focusRule, AssignmentPathSegmentImpl segment, EvaluationContext ctx) { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + AssignmentType assignmentType = getAssignmentType(segment, ctx); + PolicyRuleType policyRuleType = assignmentType.getPolicyRule(); + + LOGGER.trace("Collecting {} policy rule '{}' in {}", focusRule ? "focus" : "target", policyRuleType.getName(), segment.source); + + EvaluatedPolicyRuleImpl policyRule = new EvaluatedPolicyRuleImpl(policyRuleType.clone(), ctx.assignmentPath.clone(), prismContext); + + if (focusRule) { + ctx.evalAssignment.addFocusPolicyRule(policyRule); + } else { + if (appliesDirectly(ctx.assignmentPath)) { + ctx.evalAssignment.addThisTargetPolicyRule(policyRule); + } else { + ctx.evalAssignment.addOtherTargetPolicyRule(policyRule); + } + } + } + + private boolean appliesDirectly(AssignmentPathImpl assignmentPath) { + assert !assignmentPath.isEmpty(); + // TODO what about deputy relation which does not increase summaryOrder? + long zeroOrderCount = assignmentPath.getSegments().stream() + .filter(seg -> seg.getEvaluationOrderForTarget().getSummaryOrder() == 0) + .count(); + return zeroOrderCount == 1; + } + + @NotNull + private List> getTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, + OperationResult result) throws SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + AssignmentType assignmentType = getAssignmentType(segment, ctx); + if (assignmentType.getTargetRef() != null) { + try { + return resolveTargets(segment, ctx, result); + } catch (ObjectNotFoundException ex) { + // Do not throw an exception. We don't have referential integrity. Therefore if a role is deleted then throwing + // an exception would prohibit any operations with the users that have the role, including removal of the reference. + // The failure is recorded in the result and we will log it. It should be enough. + LOGGER.error(ex.getMessage()+" in assignment target reference in "+segment.sourceDescription,ex); + // For OrgType references we trigger the reconciliation (see MID-2242) + ctx.evalAssignment.setForceRecon(true); + return Collections.emptyList(); + } + } else { + throw new IllegalStateException("Both target and targetRef are null. We should not be here. Assignment: " + assignmentType); + } + } + + @NotNull + private List> resolveTargets(AssignmentPathSegmentImpl segment, EvaluationContext ctx, + OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { + AssignmentType assignmentType = getAssignmentType(segment, ctx); + ObjectReferenceType targetRef = assignmentType.getTargetRef(); + String oid = targetRef.getOid(); + + // Target is referenced, need to fetch it + Class targetClass; + if (targetRef.getType() != null) { + targetClass = prismContext.getSchemaRegistry().determineCompileTimeClass(targetRef.getType()); + if (targetClass == null) { + throw new SchemaException("Cannot determine type from " + targetRef.getType() + " in target reference in " + assignmentType + " in " + segment.sourceDescription); + } + } else { + throw new SchemaException("Missing type in target reference in " + assignmentType + " in " + segment.sourceDescription); + } + + if (oid == null) { + LOGGER.trace("Resolving dynamic target ref"); + if (targetRef.getFilter() == null){ + throw new SchemaException("The OID and filter are both null in assignment targetRef in "+segment.source); + } + return resolveTargetsFromFilter(targetClass, targetRef.getFilter(), segment, ctx, result); + } else { + LOGGER.trace("Resolving target {}:{} from repository", targetClass.getSimpleName(), oid); + PrismObject target; + try { + target = repository.getObject(targetClass, oid, null, result); + } catch (SchemaException e) { + throw new SchemaException(e.getMessage() + " in " + segment.sourceDescription, e); + } + // Not handling object not found exception here. Caller will handle that. + if (target == null) { + throw new IllegalArgumentException("Got null target from repository, oid:"+oid+", class:"+targetClass+" (should not happen, probably a bug) in "+segment.sourceDescription); + } + return Collections.singletonList(target); + } + } + + @NotNull + private List> resolveTargetsFromFilter(Class targetClass, + SearchFilterType filter, AssignmentPathSegmentImpl segment, + EvaluationContext ctx, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(lensContext, null, ctx.task, result)); + try { + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(segment.source, null, null, systemConfiguration.asObjectable(), prismContext); + variables.put(ExpressionConstants.VAR_SOURCE, segment.getOrderOneObject(), ObjectType.class); + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + if (assignmentPathVariables != null) { + ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, variables, getPrismContext()); + } + variables.addVariableDefinitions(getAssignmentEvaluationVariables()); + ObjectFilter origFilter = prismContext.getQueryConverter().parseFilter(filter, targetClass); + // TODO: expression profile should be determined from the holding object archetype + ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); + ObjectFilter evaluatedFilter = ExpressionUtil.evaluateFilterExpressions(origFilter, variables, expressionProfile, getMappingFactory().getExpressionFactory(), prismContext, " evaluating resource filter expression ", ctx.task, result); + if (evaluatedFilter == null) { + throw new SchemaException("The OID is null and filter could not be evaluated in assignment targetRef in "+segment.source); + } + + return repository.searchObjects(targetClass, prismContext.queryFactory().createQuery(evaluatedFilter), null, result); + // we don't check for no targets here; as we don't care for referential integrity + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + private ExpressionVariables getAssignmentEvaluationVariables() { + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_LOGIN_MODE, loginMode, Boolean.class); + // e.g. AssignmentEvaluator itself, model context, etc (when needed) + return variables; + } + + // Note: This method must be single-return after targetEvaluationInformation is established. + private void evaluateSegmentTarget(AssignmentPathSegmentImpl segment, PlusMinusZero relativeMode, boolean isAssignmentPathValid, + AssignmentHolderType target, QName relation, EvaluationContext ctx, + OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + assertSourceNotNull(segment.source, ctx.evalAssignment); + + assert ctx.assignmentPath.last() == segment; + + segment.setTarget(target); + segment.setRelation(relation); // probably not needed + + if (evaluatedAssignmentTargetCache.canSkip(segment, ctx.primaryAssignmentMode)) { + LOGGER.trace("Skipping evaluation of segment {} because it is idempotent and we have seen the target before", segment); + InternalMonitor.recordRoleEvaluationSkip(target, true); + return; + } + + LOGGER.trace("Evaluating segment TARGET:\n{}", segment.debugDumpLazily(1)); + + checkRelationWithTarget(segment, target, relation); + + LifecycleStateModelType targetStateModel = ArchetypeManager.determineLifecycleModel(target.asPrismObject(), systemConfiguration); + boolean isTargetValid = LensUtil.isFocusValid(target, now, activationComputer, targetStateModel); + boolean isPathAndTargetValid = isAssignmentPathValid && isTargetValid; + + LOGGER.debug("Evaluating RBAC [{}]", ctx.assignmentPath.shortDumpLazily()); + InternalMonitor.recordRoleEvaluation(target, true); + + AssignmentTargetEvaluationInformation targetEvaluationInformation; + if (isPathAndTargetValid) { + // Cache it immediately, even before evaluation. So if there is a cycle in the role path + // then we can detect it and skip re-evaluation of aggressively idempotent roles. + // + // !!! Ensure we will not return without updating this object (except for exceptions). So please keep this + // method a single-return one after this point. We did not want to complicate things using try...finally. + // + targetEvaluationInformation = evaluatedAssignmentTargetCache.recordProcessing(segment, ctx.primaryAssignmentMode); + } else { + targetEvaluationInformation = null; + } + int targetPolicyRulesOnEntry = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); + + boolean skipOnConditionResult = false; + + if (isTargetValid && target instanceof AbstractRoleType) { + MappingType roleCondition = ((AbstractRoleType)target).getCondition(); + if (roleCondition != null) { + AssignmentPathVariables assignmentPathVariables = LensUtil.computeAssignmentPathVariables(ctx.assignmentPath); + PrismValueDeltaSetTriple> conditionTriple = evaluateCondition(roleCondition, + segment.source, assignmentPathVariables, + "condition in " + segment.getTargetDescription(), ctx, result); + boolean condOld = ExpressionUtil.computeConditionResult(conditionTriple.getNonPositiveValues()); + boolean condNew = ExpressionUtil.computeConditionResult(conditionTriple.getNonNegativeValues()); + PlusMinusZero modeFromCondition = ExpressionUtil.computeConditionResultMode(condOld, condNew); + if (modeFromCondition == null) { + skipOnConditionResult = true; + LOGGER.trace("Skipping evaluation of {} because of condition result ({} -> {}: null)", + target, condOld, condNew); + } else { + PlusMinusZero origMode = relativeMode; + relativeMode = PlusMinusZero.compute(relativeMode, modeFromCondition); + LOGGER.trace("Evaluated condition in {}: {} -> {}: {} + {} = {}", target, condOld, condNew, + origMode, modeFromCondition, relativeMode); + } + } + } + + if (!skipOnConditionResult) { + EvaluatedAssignmentTargetImpl evalAssignmentTarget = new EvaluatedAssignmentTargetImpl( + target.asPrismObject(), + segment.isMatchingOrder(), // evaluateConstructions: exact meaning of this is to be revised + ctx.assignmentPath.clone(), + getAssignmentType(segment, ctx), + isPathAndTargetValid); + ctx.evalAssignment.addRole(evalAssignmentTarget, relativeMode); + + // we need to evaluate assignments also for disabled targets, because of target policy rules + // ... but only for direct ones! + if (isTargetValid || ctx.assignmentPath.size() == 1) { + for (AssignmentType nextAssignment : target.getAssignment()) { + evaluateAssignment(segment, relativeMode, isPathAndTargetValid, ctx, target, relation, nextAssignment, result); + } + } + + // we need to collect membership also for disabled targets (provided the assignment itself is enabled): MID-4127 + if (isNonNegative(relativeMode) && segment.isProcessMembership()) { + addToMembershipLists(target, relation, ctx); + } + + if (isTargetValid) { + if (isNonNegative(relativeMode)) { + setAsTenantRef(target, ctx); + } + + // We continue evaluation even if the relation is non-membership and non-delegation. + // Computation of isMatchingOrder will ensure that we won't collect any unwanted content. + + if (target instanceof AbstractRoleType) { + for (AssignmentType roleInducement : ((AbstractRoleType) target).getInducement()) { + evaluateInducement(segment, relativeMode, isPathAndTargetValid, ctx, target, roleInducement, result); + } + } + + if (segment.isMatchingOrder() && target instanceof AbstractRoleType && isNonNegative(relativeMode)) { + for (AuthorizationType authorizationType : ((AbstractRoleType) target).getAuthorization()) { + Authorization authorization = createAuthorization(authorizationType, target.toString()); + if (!ctx.evalAssignment.getAuthorizations().contains(authorization)) { + ctx.evalAssignment.addAuthorization(authorization); + } + } + AdminGuiConfigurationType adminGuiConfiguration = ((AbstractRoleType) target).getAdminGuiConfiguration(); + if (adminGuiConfiguration != null && !ctx.evalAssignment.getAdminGuiConfigurations() + .contains(adminGuiConfiguration)) { + ctx.evalAssignment.addAdminGuiConfiguration(adminGuiConfiguration); + } + } + } + } + int targetPolicyRulesOnExit = ctx.evalAssignment.getAllTargetsPolicyRulesCount(); + + LOGGER.trace("Evaluating segment target DONE for {}; target policy rules: {} -> {}", segment, targetPolicyRulesOnEntry, + targetPolicyRulesOnExit); + if (targetEvaluationInformation != null) { + targetEvaluationInformation.setBringsTargetPolicyRules(targetPolicyRulesOnExit > targetPolicyRulesOnEntry); + } + } + + // TODO revisit this + private ObjectType getOrderOneObject(AssignmentPathSegmentImpl segment) { + EvaluationOrder evaluationOrder = segment.getEvaluationOrder(); + if (evaluationOrder.getSummaryOrder() == 1) { + return segment.getTarget(); + } else { + if (segment.getSource() != null) { // should be always the case... + return segment.getSource(); + } else { + return segment.getTarget(); + } + } + } + + private void evaluateAssignment(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, + AssignmentHolderType target, QName relation, AssignmentType nextAssignment, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + + ObjectType orderOneObject = getOrderOneObject(segment); + + if (relationRegistry.isDelegation(relation)) { + // We have to handle assignments as though they were inducements here. + if (!isAllowedByLimitations(segment, nextAssignment, ctx)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of delegated assignment {} because it is limited in the delegation", + FocusTypeUtil.dumpAssignment(nextAssignment)); + } + return; + } + } + QName nextRelation = getRelation(nextAssignment); + EvaluationOrder nextEvaluationOrder = segment.getEvaluationOrder().advance(nextRelation); + EvaluationOrder nextEvaluationOrderForTarget = segment.getEvaluationOrderForTarget().advance(nextRelation); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("orig EO({}): follow assignment {} {}; new EO({})", + segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpAssignment(nextAssignment), nextEvaluationOrder); + } + String nextSourceDescription = target+" in "+segment.sourceDescription; + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, nextSourceDescription, nextAssignment, true, relationRegistry, prismContext); + nextSegment.setRelation(nextRelation); + nextSegment.setEvaluationOrder(nextEvaluationOrder); + nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTarget); + nextSegment.setOrderOneObject(orderOneObject); + nextSegment.setPathToSourceValid(isValid); + /* + * We obviously want to process membership from the segment if it's of matching order. + * + * But we want to do that also for targets obtained via delegations. The current (approximate) approach is to + * collect membership from all assignments of any user that we find on the assignment path. + * + * TODO: does this work for invalid (effectiveStatus = disabled) assignments? + */ + boolean isUser = target instanceof UserType; + nextSegment.setProcessMembership(nextSegment.isMatchingOrder() || isUser); + assert !ctx.assignmentPath.isEmpty(); + evaluateFromSegment(nextSegment, mode, ctx, result); + } + + private void evaluateInducement(AssignmentPathSegmentImpl segment, PlusMinusZero mode, boolean isValid, EvaluationContext ctx, + AssignmentHolderType target, AssignmentType inducement, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { + + ObjectType orderOneObject = getOrderOneObject(segment); + + if (!isInducementApplicableToFocusType(inducement.getFocusType())) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of inducement {} because the focusType does not match (specified: {}, actual: {})", + FocusTypeUtil.dumpAssignment(inducement), inducement.getFocusType(), target.getClass().getSimpleName()); + } + return; + } + if (!isAllowedByLimitations(segment, inducement, ctx)) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Skipping application of inducement {} because it is limited", FocusTypeUtil.dumpAssignment(inducement)); + } + return; + } + String subSourceDescription = target+" in "+segment.sourceDescription; + AssignmentPathSegmentImpl nextSegment = new AssignmentPathSegmentImpl(target, subSourceDescription, inducement, false, relationRegistry, prismContext); + // note that 'old' and 'new' values for assignment in nextSegment are the same + boolean nextIsMatchingOrder = AssignmentPathSegmentImpl.computeMatchingOrder( + segment.getEvaluationOrder(), nextSegment.getAssignmentNew()); + boolean nextIsMatchingOrderForTarget = AssignmentPathSegmentImpl.computeMatchingOrder( + segment.getEvaluationOrderForTarget(), nextSegment.getAssignmentNew()); + + Holder nextEvaluationOrderHolder = new Holder<>(segment.getEvaluationOrder().clone()); + Holder nextEvaluationOrderForTargetHolder = new Holder<>(segment.getEvaluationOrderForTarget().clone()); + adjustOrder(nextEvaluationOrderHolder, nextEvaluationOrderForTargetHolder, inducement.getOrderConstraint(), inducement.getOrder(), ctx.assignmentPath, nextSegment, ctx); + nextSegment.setEvaluationOrder(nextEvaluationOrderHolder.getValue(), nextIsMatchingOrder); + nextSegment.setEvaluationOrderForTarget(nextEvaluationOrderForTargetHolder.getValue(), nextIsMatchingOrderForTarget); + + nextSegment.setOrderOneObject(orderOneObject); + nextSegment.setPathToSourceValid(isValid); + nextSegment.setProcessMembership(nextIsMatchingOrder); + nextSegment.setRelation(getRelation(inducement)); + + // Originally we executed the following only if isMatchingOrder. However, sometimes we have to look even into + // inducements with non-matching order: for example because we need to extract target-related policy rules + // (these are stored with order of one less than orders for focus-related policy rules). + // + // We need to make sure NOT to extract anything other from such inducements. That's why we set e.g. + // processMembership attribute to false for these inducements. + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("orig EO({}): evaluate {} inducement({}) {}; new EO({})", + segment.getEvaluationOrder().shortDump(), target, FocusTypeUtil.dumpInducementConstraints(inducement), + FocusTypeUtil.dumpAssignment(inducement), nextEvaluationOrderHolder.getValue().shortDump()); + } + assert !ctx.assignmentPath.isEmpty(); + evaluateFromSegment(nextSegment, mode, ctx, result); + } + + private void adjustOrder(Holder evaluationOrderHolder, Holder targetEvaluationOrderHolder, + List constraints, Integer order, AssignmentPathImpl assignmentPath, + AssignmentPathSegmentImpl nextSegment, EvaluationContext ctx) { + + if (constraints.isEmpty()) { + if (order == null || order == 1) { + return; + } else if (order <= 0) { + throw new IllegalStateException("Wrong inducement order: it must be positive but it is " + order + " instead"); + } + // converting legacy -> new specification + int currentOrder = evaluationOrderHolder.getValue().getSummaryOrder(); + if (order > currentOrder) { + LOGGER.trace("order of the inducement ({}) is greater than the current evaluation order ({}), marking as undefined", + order, currentOrder); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } + // i.e. currentOrder >= order, i.e. currentOrder > order-1 + int newOrder = currentOrder - (order - 1); + assert newOrder > 0; + constraints = Collections.singletonList(new OrderConstraintsType(prismContext) + .order(order) + .resetOrder(newOrder)); + } + + OrderConstraintsType summaryConstraints = ObjectTypeUtil.getConstraintFor(constraints, null); + Integer resetSummaryTo = summaryConstraints != null && summaryConstraints.getResetOrder() != null ? + summaryConstraints.getResetOrder() : null; + + if (resetSummaryTo != null) { + int summaryBackwards = evaluationOrderHolder.getValue().getSummaryOrder() - resetSummaryTo; + if (summaryBackwards < 0) { + // or should we throw an exception? + LOGGER.warn("Cannot move summary order backwards to a negative value ({}). Current order: {}, requested order: {}", + summaryBackwards, evaluationOrderHolder.getValue().getSummaryOrder(), resetSummaryTo); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } else if (summaryBackwards > 0) { +// MultiSet backRelations = new HashMultiSet<>(); + int assignmentsSeen = 0; + int i = assignmentPath.size()-1; + while (assignmentsSeen < summaryBackwards) { + if (i < 0) { + LOGGER.trace("Cannot move summary order backwards by {}; only {} assignments segment seen: {}", + summaryBackwards, assignmentsSeen, assignmentPath); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } + AssignmentPathSegmentImpl segment = assignmentPath.getSegments().get(i); + if (segment.isAssignment()) { + if (!relationRegistry.isDelegation(segment.getRelation())) { + // backRelations.add(segment.getRelation()); + assignmentsSeen++; + LOGGER.trace("Going back {}: relation at assignment -{} (position -{}): {}", summaryBackwards, + assignmentsSeen, assignmentPath.size() - i, segment.getRelation()); + } + } else { + AssignmentType inducement = segment.getAssignment(ctx.evaluateOld); // for i>0 returns value regardless of evaluateOld + for (OrderConstraintsType constraint : inducement.getOrderConstraint()) { + if (constraint.getResetOrder() != null && constraint.getRelation() != null) { + LOGGER.debug("Going back {}: an inducement with non-summary resetting constraint found" + + " in the chain (at position -{}): {} in {}", summaryBackwards, assignmentPath.size()-i, + constraint, segment); + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + return; + } + } + if (segment.getLastEqualOrderSegmentIndex() != null) { + i = segment.getLastEqualOrderSegmentIndex(); + continue; + } + } + i--; + } + nextSegment.setLastEqualOrderSegmentIndex(i); + evaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrder()); + targetEvaluationOrderHolder.setValue(assignmentPath.getSegments().get(i).getEvaluationOrderForTarget()); + } else { + // summaryBackwards is 0 - nothing to change + } + for (OrderConstraintsType constraint : constraints) { + if (constraint.getRelation() != null && constraint.getResetOrder() != null) { + LOGGER.warn("Ignoring resetOrder (with a value of {} for {}) because summary order was already moved backwards by {} to {}: {}", + constraint.getResetOrder(), constraint.getRelation(), summaryBackwards, + evaluationOrderHolder.getValue().getSummaryOrder(), constraint); + } + } + } else { + EvaluationOrder beforeChange = evaluationOrderHolder.getValue().clone(); + for (OrderConstraintsType constraint : constraints) { + if (constraint.getResetOrder() != null) { + assert constraint.getRelation() != null; // already processed above + int currentOrder = evaluationOrderHolder.getValue().getMatchingRelationOrder(constraint.getRelation()); + int newOrder = constraint.getResetOrder(); + if (newOrder > currentOrder) { + LOGGER.warn("Cannot increase evaluation order for {} from {} to {}: {}", constraint.getRelation(), + currentOrder, newOrder, constraint); + } else if (newOrder < currentOrder) { + evaluationOrderHolder.setValue(evaluationOrderHolder.getValue().resetOrder(constraint.getRelation(), newOrder)); + LOGGER.trace("Reset order for {} from {} to {} -> {}", constraint.getRelation(), currentOrder, newOrder, evaluationOrderHolder.getValue()); + } else { + LOGGER.trace("Keeping order for {} at {} -> {}", constraint.getRelation(), currentOrder, evaluationOrderHolder.getValue()); + } + } + } + Map difference = beforeChange.diff(evaluationOrderHolder.getValue()); + targetEvaluationOrderHolder.setValue(targetEvaluationOrderHolder.getValue().applyDifference(difference)); + } + + if (evaluationOrderHolder.getValue().getSummaryOrder() <= 0) { + makeUndefined(evaluationOrderHolder, targetEvaluationOrderHolder); + } + if (!targetEvaluationOrderHolder.getValue().isValid()) { + // some extreme cases like the one described in TestAssignmentProcessor2.test520 + makeUndefined(targetEvaluationOrderHolder); + } + if (!evaluationOrderHolder.getValue().isValid()) { + throw new AssertionError("Resulting evaluation order path is invalid: " + evaluationOrderHolder.getValue()); + } + } + + @SafeVarargs + private final void makeUndefined(Holder... holders) { // final because of SafeVarargs (on java8) + for (Holder holder : holders) { + holder.setValue(EvaluationOrderImpl.UNDEFINED); + } + } + + private void addToMembershipLists(AssignmentHolderType targetToAdd, QName relation, EvaluationContext ctx) { + PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); + valueToAdd.setObject(targetToAdd.asPrismObject()); + valueToAdd.setTargetType(ObjectTypes.getObjectType(targetToAdd.getClass()).getTypeQName()); + valueToAdd.setRelation(relation); + valueToAdd.setTargetName(targetToAdd.getName().toPolyString()); + + addToMembershipLists(valueToAdd, targetToAdd.getClass(), relation, targetToAdd, ctx); + } + + private void setAsTenantRef(AssignmentHolderType targetToSet, EvaluationContext ctx) { + if (targetToSet instanceof OrgType) { + if (BooleanUtils.isTrue(((OrgType)targetToSet).isTenant()) && ctx.evalAssignment.getTenantOid() == null) { + if (ctx.assignmentPath.hasOnlyOrgs()) { + ctx.evalAssignment.setTenantOid(targetToSet.getOid()); + } + } + } + } + + private void addToMembershipLists(ObjectReferenceType referenceToAdd, QName relation, EvaluationContext ctx) { + PrismReferenceValue valueToAdd = prismContext.itemFactory().createReferenceValue(); + valueToAdd.setOid(referenceToAdd.getOid()); + valueToAdd.setTargetType(referenceToAdd.getType()); + valueToAdd.setRelation(relation); + valueToAdd.setTargetName(referenceToAdd.getTargetName()); + + Class targetClass = ObjectTypes.getObjectTypeFromTypeQName(referenceToAdd.getType()).getClassDefinition(); + addToMembershipLists(valueToAdd, targetClass, relation, referenceToAdd, ctx); + } + + private void addToMembershipLists(PrismReferenceValue valueToAdd, Class targetClass, QName relation, + Object targetDesc, EvaluationContext ctx) { + if (ctx.assignmentPath.containsDelegation(ctx.evaluateOld, relationRegistry)) { + addIfNotThere(ctx.evalAssignment.getDelegationRefVals(), valueToAdd, "delegationRef", targetDesc); + } else { + if (AbstractRoleType.class.isAssignableFrom(targetClass)) { + addIfNotThere(ctx.evalAssignment.getMembershipRefVals(), valueToAdd, "membershipRef", targetDesc); + } + } + if (OrgType.class.isAssignableFrom(targetClass) && relationRegistry.isStoredIntoParentOrgRef(relation)) { + addIfNotThere(ctx.evalAssignment.getOrgRefVals(), valueToAdd, "orgRef", targetDesc); + } + if (ArchetypeType.class.isAssignableFrom(targetClass)) { + addIfNotThere(ctx.evalAssignment.getArchetypeRefVals(), valueToAdd, "archetypeRef", targetDesc); + } + } + + private void addIfNotThere(Collection collection, PrismReferenceValue valueToAdd, String collectionName, + Object targetDesc) { + if (!collection.contains(valueToAdd)) { + LOGGER.trace("Adding target {} to {}", targetDesc, collectionName); + collection.add(valueToAdd); + } else { + LOGGER.trace("Would add target {} to {}, but it's already there", targetDesc, collectionName); + } + } + + private boolean isNonNegative(PlusMinusZero mode) { + // mode == null is also considered negative, because it is a combination of PLUS and MINUS; + // so the net result is that for both old and new state there exists an unsatisfied condition on the path. + return mode == PlusMinusZero.ZERO || mode == PlusMinusZero.PLUS; + } + + private boolean isChanged(PlusMinusZero mode) { + // mode == null is also considered negative, because it is a combination of PLUS and MINUS; + // so the net result is that for both old and new state there exists an unsatisfied condition on the path. + return mode == PlusMinusZero.PLUS || mode == PlusMinusZero.MINUS; + } + + private void checkRelationWithTarget(AssignmentPathSegmentImpl segment, AssignmentHolderType targetType, QName relation) + throws SchemaException { + if (targetType instanceof AbstractRoleType || targetType instanceof TaskType) { //TODO: + // OK, just go on + } else if (targetType instanceof UserType) { + if (!relationRegistry.isDelegation(relation)) { + throw new SchemaException("Unsupported relation " + relation + " for assignment of target type " + targetType + " in " + segment.sourceDescription); + } + } else { + throw new SchemaException("Unknown assignment target type " + targetType + " in " + segment.sourceDescription); + } + } + + private boolean isInducementApplicableToFocusType(QName inducementFocusType) throws SchemaException { + if (inducementFocusType == null) { + return true; + } + Class inducementFocusClass = prismContext.getSchemaRegistry().determineCompileTimeClass(inducementFocusType); + if (inducementFocusClass == null) { + throw new SchemaException("Could not determine class for " + inducementFocusType); + } + if (lensContext.getFocusClass() == null) { + // should not occur; it would be probably safe to throw an exception here + LOGGER.error("No focus class in lens context; inducement targeted at focus type {} will not be applied:\n{}", + inducementFocusType, lensContext.debugDump()); + return false; + } + return inducementFocusClass.isAssignableFrom(lensContext.getFocusClass()); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean isAllowedByLimitations(AssignmentPathSegment segment, AssignmentType nextAssignment, EvaluationContext ctx) { + AssignmentType currentAssignment = segment.getAssignment(ctx.evaluateOld); + AssignmentSelectorType targetLimitation = currentAssignment.getLimitTargetContent(); + if (isDeputyDelegation(nextAssignment)) { // delegation of delegation + return targetLimitation != null && BooleanUtils.isTrue(targetLimitation.isAllowTransitive()); + } else { + // As for the case of targetRef==null: we want to pass target-less assignments (focus mappings, policy rules etc) + // from the delegator to delegatee. To block them we should use order constraints (but also for assignments?). + return targetLimitation == null || nextAssignment.getTargetRef() == null || + FocusTypeUtil.selectorMatches(targetLimitation, nextAssignment, prismContext); + } + } + + private boolean isDeputyDelegation(AssignmentType assignmentType) { + ObjectReferenceType targetRef = assignmentType.getTargetRef(); + return targetRef != null && relationRegistry.isDelegation(targetRef.getRelation()); + } + + private Authorization createAuthorization(AuthorizationType authorizationType, String sourceDesc) { + Authorization authorization = new Authorization(authorizationType); + authorization.setSourceDescription(sourceDesc); + return authorization; + } + + private void assertSourceNotNull(ObjectType source, EvaluatedAssignment assignment) { + if (source == null) { + throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignment+")"); + } + } + + private void assertSourceNotNull(ObjectType source, ItemDeltaItem,PrismContainerDefinition> assignmentIdi) { + if (source == null) { + throw new IllegalArgumentException("Source cannot be null (while evaluating assignment "+assignmentIdi.getAnyItem()+")"); + } + } + + private AssignmentType getAssignmentType(AssignmentPathSegmentImpl segment, EvaluationContext ctx) { + return segment.getAssignment(ctx.evaluateOld); + } + + private void checkSchema(AssignmentPathSegmentImpl segment, EvaluationContext ctx) throws SchemaException { + AssignmentType assignmentType = getAssignmentType(segment, ctx); + //noinspection unchecked + PrismContainerValue assignmentContainerValue = assignmentType.asPrismContainerValue(); + PrismContainerable assignmentContainer = assignmentContainerValue.getParent(); + if (assignmentContainer == null) { + throw new SchemaException("The assignment "+assignmentType+" does not have a parent in "+segment.sourceDescription); + } + if (assignmentContainer.getDefinition() == null) { + throw new SchemaException("The assignment "+assignmentType+" does not have definition in "+segment.sourceDescription); + } + PrismContainer extensionContainer = assignmentContainerValue.findContainer(AssignmentType.F_EXTENSION); + if (extensionContainer != null) { + if (extensionContainer.getDefinition() == null) { + throw new SchemaException("Extension does not have a definition in assignment "+assignmentType+" in "+segment.sourceDescription); + } + + if (extensionContainer.getValue().getItems() == null) { + throw new SchemaException("Extension without items in assignment " + assignmentType + " in " + segment.sourceDescription + ", empty extension tag?"); + } + + for (Item item: extensionContainer.getValue().getItems()) { + if (item == null) { + throw new SchemaException("Null item in extension in assignment "+assignmentType+" in "+segment.sourceDescription); + } + if (item.getDefinition() == null) { + throw new SchemaException("Item "+item+" has no definition in extension in assignment "+assignmentType+" in "+segment.sourceDescription); + } + } + } + } + + private void setEvaluatedAssignmentTarget(AssignmentPathSegmentImpl segment, + @NotNull List> targets, EvaluationContext ctx) { + assert ctx.evalAssignment.getTarget() == null; + if (targets.size() > 1) { + throw new UnsupportedOperationException("Multiple targets for direct focus assignment are not supported: " + segment.getAssignment(ctx.evaluateOld)); + } else if (!targets.isEmpty()) { + ctx.evalAssignment.setTarget(targets.get(0)); + } + } + + private PrismValueDeltaSetTriple> evaluateCondition(MappingType condition, + ObjectType source, AssignmentPathVariables assignmentPathVariables, String contextDescription, EvaluationContext ctx, + OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { + MappingImpl.Builder,PrismPropertyDefinition> builder = mappingFactory.createMappingBuilder(); + builder = builder.mappingType(condition) + .contextDescription(contextDescription) + .sourceContext(focusOdo) + .originType(OriginType.ASSIGNMENTS) + .originObject(source) + .defaultTargetDefinition(prismContext.definitionFactory().createPropertyDefinition(CONDITION_OUTPUT_NAME, DOMUtil.XSD_BOOLEAN)) + .addVariableDefinitions(getAssignmentEvaluationVariables()) + .rootNode(focusOdo) + .addVariableDefinition(ExpressionConstants.VAR_USER, focusOdo) + .addVariableDefinition(ExpressionConstants.VAR_FOCUS, focusOdo) + .addAliasRegistration(ExpressionConstants.VAR_USER, null) + .addAliasRegistration(ExpressionConstants.VAR_FOCUS, null) + .addVariableDefinition(ExpressionConstants.VAR_SOURCE, source, ObjectType.class) + .addVariableDefinition(ExpressionConstants.VAR_ASSIGNMENT_EVALUATOR, this, AssignmentEvaluator.class); + builder = LensUtil.addAssignmentPathVariables(builder, assignmentPathVariables, prismContext); + + MappingImpl, PrismPropertyDefinition> mapping = builder.build(); + + mappingEvaluator.evaluateMapping(mapping, lensContext, ctx.task, result); + + return mapping.getOutputTriple(); + } + + @Nullable + private QName getRelation(AssignmentType assignmentType) { + return assignmentType.getTargetRef() != null ? + relationRegistry.normalizeRelation(assignmentType.getTargetRef().getRelation()) : null; + } + + /* + * This "isMemberOf iteration" section is an experimental implementation of MID-5366. + * + * The main idea: In role/assignment/inducement conditions we test the membership not by querying roleMembershipRef + * on focus object but instead we call assignmentEvaluator.isMemberOf() method. This method - by default - inspects + * roleMembershipRef but also records the check result. Later, when assignment evaluation is complete, AssignmentProcessor + * will ask if all of these check results are still valid. If they are not, it requests re-evaluation of all the assignments, + * using updated check results. + * + * This should work unless there are some cyclic dependencies (like "this sentence is a lie" paradox). + */ + public boolean isMemberOf(String targetOid) { + if (targetOid == null) { + throw new IllegalArgumentException("Null targetOid in isMemberOf call"); + } + MemberOfInvocation existingInvocation = findInvocation(targetOid); + if (existingInvocation != null) { + return existingInvocation.result; + } else { + boolean result = computeIsMemberOfDuringEvaluation(targetOid); + memberOfInvocations.add(new MemberOfInvocation(targetOid, result)); + return result; + } + } + + private MemberOfInvocation findInvocation(String targetOid) { + List matching = memberOfInvocations.stream() + .filter(invocation -> targetOid.equals(invocation.targetOid)) + .collect(Collectors.toList()); + if (matching.isEmpty()) { + return null; + } else if (matching.size() == 1) { + return matching.get(0); + } else { + throw new IllegalStateException("More than one matching MemberOfInvocation for targetOid='" + targetOid + "': " + matching); + } + } + + private boolean computeIsMemberOfDuringEvaluation(String targetOid) { + // TODO Or should we consider evaluateOld? + PrismObject focus = focusOdo.getNewObject(); + return focus != null && containsMember(focus.asObjectable().getRoleMembershipRef(), targetOid); + } + + public boolean isMemberOfInvocationResultChanged(DeltaSetTriple> evaluatedAssignmentTriple) { + if (!memberOfInvocations.isEmpty()) { + // Similar code is in AssignmentProcessor.processMembershipAndDelegatedRefs -- check that if changing the business logic + List membership = evaluatedAssignmentTriple.getNonNegativeValues().stream() + .filter(EvaluatedAssignmentImpl::isValid) + .flatMap(evaluatedAssignment -> evaluatedAssignment.getMembershipRefVals().stream()) + .map(ref -> ObjectTypeUtil.createObjectRef(ref, false)) + .collect(Collectors.toList()); + LOGGER.trace("Computed new membership: {}", membership); + return updateMemberOfInvocations(membership); + } else { + return false; + } + } + + private boolean updateMemberOfInvocations(List newMembership) { + boolean changed = false; + for (MemberOfInvocation invocation : memberOfInvocations) { + boolean newResult = containsMember(newMembership, invocation.targetOid); + if (newResult != invocation.result) { + LOGGER.trace("Invocation result changed for {} - new one is '{}'", invocation, newResult); + invocation.result = newResult; + changed = true; + } + } + return changed; + } + + // todo generalize a bit (e.g. by including relation) + private boolean containsMember(List membership, String targetOid) { + return membership.stream().anyMatch(ref -> targetOid.equals(ref.getOid())); + } + + public static final class Builder { + private RepositoryService repository; + private ObjectDeltaObject focusOdo; + private LensContext lensContext; + private String channel; + private ObjectResolver objectResolver; + private SystemObjectCache systemObjectCache; + private RelationRegistry relationRegistry; + private PrismContext prismContext; + private MappingFactory mappingFactory; + private ActivationComputer activationComputer; + private XMLGregorianCalendar now; + private boolean loginMode = false; + private PrismObject systemConfiguration; + private MappingEvaluator mappingEvaluator; + + public Builder() { + } + + public Builder repository(RepositoryService val) { + repository = val; + return this; + } + + public Builder focusOdo(ObjectDeltaObject val) { + focusOdo = val; + return this; + } + + public Builder lensContext(LensContext val) { + lensContext = val; + return this; + } + + public Builder channel(String val) { + channel = val; + return this; + } + + public Builder objectResolver(ObjectResolver val) { + objectResolver = val; + return this; + } + + public Builder systemObjectCache(SystemObjectCache val) { + systemObjectCache = val; + return this; + } + + public Builder relationRegistry(RelationRegistry val) { + relationRegistry = val; + return this; + } + + public Builder prismContext(PrismContext val) { + prismContext = val; + return this; + } + + public Builder mappingFactory(MappingFactory val) { + mappingFactory = val; + return this; + } + + public Builder activationComputer(ActivationComputer val) { + activationComputer = val; + return this; + } + + public Builder now(XMLGregorianCalendar val) { + now = val; + return this; + } + + public Builder loginMode(boolean val) { + loginMode = val; + return this; + } + + public Builder systemConfiguration(PrismObject val) { + systemConfiguration = val; + return this; + } + + public Builder mappingEvaluator(MappingEvaluator val) { + mappingEvaluator = val; + return this; + } + + public AssignmentEvaluator build() { + return new AssignmentEvaluator<>(this); + } + } + + private static class MemberOfInvocation { + private final String targetOid; + private boolean result; + + private MemberOfInvocation(String targetOid, boolean result) { + this.targetOid = targetOid; + this.result = result; + } + + @Override + public String toString() { + return "MemberOfInvocation{" + + "targetOid='" + targetOid + '\'' + + ", result=" + result + + '}'; + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java index af362e16357..fe731b04b67 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java @@ -22,6 +22,7 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; import org.apache.commons.lang.BooleanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -34,8 +35,8 @@ import com.evolveum.midpoint.model.api.ProgressInformation; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; import com.evolveum.midpoint.model.impl.lens.projector.focus.FocusConstraintsChecker; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java index bffec27a583..afa9c6dbf89 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/EvaluatedAssignmentImpl.java @@ -1,598 +1,594 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; -import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.model.api.context.*; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.delta.PlusMinusZero; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.security.api.Authorization; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.ShortDumpable; -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.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.jetbrains.annotations.NotNull; - -import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerable; -import static com.evolveum.midpoint.prism.delta.PlusMinusZero.MINUS; -import static com.evolveum.midpoint.prism.delta.PlusMinusZero.PLUS; -import static com.evolveum.midpoint.prism.delta.PlusMinusZero.ZERO; - -/** - * Evaluated assignment that contains all constructions and authorizations from the assignment - * itself and all the applicable inducements from all the roles referenced from the assignment. - * - * @author Radovan Semancik - */ -public class EvaluatedAssignmentImpl implements EvaluatedAssignment, ShortDumpable { - - private static final Trace LOGGER = TraceManager.getTrace(EvaluatedAssignmentImpl.class); - - @NotNull private final ItemDeltaItem,PrismContainerDefinition> assignmentIdi; - private final boolean evaluatedOld; - @NotNull private final DeltaSetTriple> constructionTriple; - @NotNull private final DeltaSetTriple> personaConstructionTriple; - @NotNull private final DeltaSetTriple roles; - @NotNull private final Collection orgRefVals = new ArrayList<>(); - @NotNull private final Collection archetypeRefVals = new ArrayList<>(); - @NotNull private final Collection membershipRefVals = new ArrayList<>(); - @NotNull private final Collection delegationRefVals = new ArrayList<>(); - @NotNull private final Collection authorizations = new ArrayList<>(); - - /** - * Requests to evaluate focus mappings. These are collected during assignment evaluation, but executed afterwards. - * This is to implement proper mapping chaining. - * - * @since 4.0.1 - */ - @NotNull private final Collection focusMappingEvaluationRequests = new ArrayList<>(); - - /** - * These are evaluated focus mappings. Since 4.0.1 the evaluation is carried out not during assignment evaluation - * but afterwards. - */ - @NotNull private final Collection> focusMappings = new ArrayList<>(); - - @NotNull private final Collection adminGuiConfigurations = new ArrayList<>(); - // rules related to the focal object (typically e.g. "forbid modifications") - @NotNull private final Collection focusPolicyRules = new ArrayList<>(); - // rules related to the target of this assignment (typically e.g. "approve the assignment") - @NotNull private final Collection thisTargetPolicyRules = new ArrayList<>(); - // rules related to other targets provided by this assignment (e.g. induced or obtained by delegation) - // usually, these rules do not cause direct action (e.g. in the case of approvals); - // however, there are situations in which they are used (e.g. for exclusion rules) - @NotNull private final Collection otherTargetsPolicyRules = new ArrayList<>(); - private String tenantOid; - - private PrismObject target; - private boolean isValid; - private boolean wasValid; - private boolean forceRecon; // used also to force recomputation of parentOrgRefs - @NotNull private final AssignmentOrigin origin; - private Collection policySituations = new HashSet<>(); - - private PrismContext prismContext; - - public EvaluatedAssignmentImpl( - @NotNull ItemDeltaItem, PrismContainerDefinition> assignmentIdi, - boolean evaluatedOld, @NotNull AssignmentOrigin origin, PrismContext prismContext) { - this.assignmentIdi = assignmentIdi; - this.evaluatedOld = evaluatedOld; - this.constructionTriple = prismContext.deltaFactory().createDeltaSetTriple(); - this.personaConstructionTriple = prismContext.deltaFactory().createDeltaSetTriple(); - this.roles = prismContext.deltaFactory().createDeltaSetTriple(); - this.prismContext = prismContext; - this.origin = origin; - } - - @NotNull - public ItemDeltaItem,PrismContainerDefinition> getAssignmentIdi() { - return assignmentIdi; - } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#getAssignmentType() - */ - @Override - public AssignmentType getAssignmentType() { - return asContainerable(assignmentIdi.getSingleValue(evaluatedOld)); - } - - @Override - public Long getAssignmentId() { - Item, PrismContainerDefinition> any = assignmentIdi.getAnyItem(); - return any != null && !any.getValues().isEmpty() ? any.getAnyValue().getId() : null; - } - - @Override - public AssignmentType getAssignmentType(boolean old) { - return asContainerable(assignmentIdi.getSingleValue(old)); - } - - private ObjectReferenceType getTargetRef() { - AssignmentType assignmentType = getAssignmentType(); - if (assignmentType == null) { - return null; - } - return assignmentType.getTargetRef(); - } - - @Override - public QName getRelation() { - ObjectReferenceType targetRef = getTargetRef(); - return targetRef != null ? targetRef.getRelation() : null; - } - - @Override - public QName getNormalizedRelation(RelationRegistry relationRegistry) { - ObjectReferenceType targetRef = getTargetRef(); - return targetRef != null ? relationRegistry.normalizeRelation(targetRef.getRelation()) : null; - } - - @NotNull - public DeltaSetTriple> getConstructionTriple() { - return constructionTriple; - } - - /** - * Construction is not a part of model-api. To avoid heavy refactoring at present time, there is not a classical - * Construction-ConstructionImpl separation, but we use artificial (simplified) EvaluatedConstruction - * API class instead. - */ - @Override - public DeltaSetTriple getEvaluatedConstructions(Task task, OperationResult result) { - DeltaSetTriple rv = prismContext.deltaFactory().createDeltaSetTriple(); - for (PlusMinusZero whichSet : PlusMinusZero.values()) { - Collection> constructionSet = constructionTriple.getSet(whichSet); - if (constructionSet != null) { - for (Construction construction : constructionSet) { - if (!construction.isIgnored()) { - rv.addToSet(whichSet, new EvaluatedConstructionImpl(construction)); - } - } - } - } - return rv; - } - - Collection> getConstructionSet(PlusMinusZero whichSet) { - switch (whichSet) { - case ZERO: return getConstructionTriple().getZeroSet(); - case PLUS: return getConstructionTriple().getPlusSet(); - case MINUS: return getConstructionTriple().getMinusSet(); - default: throw new IllegalArgumentException("whichSet: " + whichSet); - } - } - - void addConstruction(Construction construction, PlusMinusZero whichSet) { - addToTriple(construction, constructionTriple, whichSet); - } - - private void addToTriple(T construction, @NotNull DeltaSetTriple triple, PlusMinusZero whichSet) { - switch (whichSet) { - case ZERO: - triple.addToZeroSet(construction); - break; - case PLUS: - triple.addToPlusSet(construction); - break; - case MINUS: - triple.addToMinusSet(construction); - break; - default: - throw new IllegalArgumentException("whichSet: " + whichSet); - } - } - - @NotNull - DeltaSetTriple> getPersonaConstructionTriple() { - return personaConstructionTriple; - } - - void addPersonaConstruction(PersonaConstruction personaConstruction, PlusMinusZero whichSet) { - addToTriple(personaConstruction, personaConstructionTriple, whichSet); - } - - @NotNull - @Override - public DeltaSetTriple getRoles() { - return roles; - } - - void addRole(EvaluatedAssignmentTargetImpl role, PlusMinusZero mode) { - roles.addToSet(mode, role); - } - - @NotNull - public Collection getOrgRefVals() { - return orgRefVals; - } - - @NotNull - public Collection getArchetypeRefVals() { - return archetypeRefVals; - } - - @NotNull - public Collection getMembershipRefVals() { - return membershipRefVals; - } - - @NotNull - public Collection getDelegationRefVals() { - return delegationRefVals; - } - - public String getTenantOid() { - return tenantOid; - } - - void setTenantOid(String tenantOid) { - this.tenantOid = tenantOid; - } - - @NotNull - @Override - public Collection getAuthorizations() { - return authorizations; - } - - void addAuthorization(Authorization authorization) { - authorizations.add(authorization); - } - - @NotNull - public Collection getAdminGuiConfigurations() { - return adminGuiConfigurations; - } - - void addAdminGuiConfiguration(AdminGuiConfigurationType adminGuiConfiguration) { - adminGuiConfigurations.add(adminGuiConfiguration); - } - - @NotNull - public Collection> getFocusMappings() { - return focusMappings; - } - - @NotNull - public Collection getFocusMappingEvaluationRequests() { - return focusMappingEvaluationRequests; - } - - public void addFocusMapping(MappingImpl focusMapping) { - this.focusMappings.add(focusMapping); - } - - void addFocusMappingEvaluationRequest(AssignedFocusMappingEvaluationRequest request) { - this.focusMappingEvaluationRequests.add(request); - } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#getTarget() - */ - @Override - public PrismObject getTarget() { - return target; - } - - public void setTarget(PrismObject target) { - this.target = target; - } - - public boolean isVirtual() { - return origin.isVirtual(); - } - - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#isValid() - */ - @Override - public boolean isValid() { - return isValid; - } - - public void setValid(boolean isValid) { - this.isValid = isValid; - } - - public void setWasValid(boolean wasValid) { - this.wasValid = wasValid; - } - - public boolean isForceRecon() { - return forceRecon; - } - - public void setForceRecon(boolean forceRecon) { - this.forceRecon = forceRecon; - } - - // System configuration is used only to provide $configuration script variable (MID-2372) - public void evaluateConstructions(ObjectDeltaObject focusOdo, PrismObject systemConfiguration, - Consumer resourceConsumer, Task task, OperationResult result) throws SchemaException, - ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, - CommunicationException { - for (Construction construction : constructionTriple.getAllValues()) { - construction.setFocusOdo(focusOdo); - construction.setSystemConfiguration(systemConfiguration); - construction.setWasValid(wasValid); - LOGGER.trace("Evaluating construction '{}' in {}", construction, construction.getSource()); - construction.evaluate(task, result); - if (resourceConsumer != null && construction.getResource() != null) { - resourceConsumer.accept(construction.getResource()); - } - } - } - - void evaluateConstructions(ObjectDeltaObject focusOdo, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException { - evaluateConstructions(focusOdo, null, null, task, result); - } - - @NotNull - public AssignmentOrigin getOrigin() { - return origin; - } - - @Override - public boolean isPresentInCurrentObject() { - return origin.isCurrent(); - } - - @Override - public boolean isPresentInOldObject() { - return origin.isOld(); - } - - @NotNull - public Collection getFocusPolicyRules() { - return focusPolicyRules; - } - - void addFocusPolicyRule(EvaluatedPolicyRule policyRule) { - focusPolicyRules.add(policyRule); - } - - @NotNull - public Collection getThisTargetPolicyRules() { - return thisTargetPolicyRules; - } - - public void addThisTargetPolicyRule(EvaluatedPolicyRule policyRule) { - thisTargetPolicyRules.add(policyRule); - } - - @NotNull - public Collection getOtherTargetsPolicyRules() { - return otherTargetsPolicyRules; - } - - public void addOtherTargetPolicyRule(EvaluatedPolicyRule policyRule) { - otherTargetsPolicyRules.add(policyRule); - } - - @NotNull - public Collection getAllTargetsPolicyRules() { - return Stream.concat(thisTargetPolicyRules.stream(), otherTargetsPolicyRules.stream()).collect(Collectors.toList()); - } - - @Override - public int getAllTargetsPolicyRulesCount() { - return thisTargetPolicyRules.size() + otherTargetsPolicyRules.size(); - } - - @Override - public Collection getPolicySituations() { - return policySituations; - } - - @Override - public void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers) { - boolean hasException = processRuleExceptions(this, rule, triggers); - - for (EvaluatedPolicyRuleTrigger trigger : triggers) { - if (trigger instanceof EvaluatedExclusionTrigger) { - EvaluatedExclusionTrigger exclTrigger = (EvaluatedExclusionTrigger) trigger; - if (exclTrigger.getConflictingAssignment() != null) { - hasException = - hasException || processRuleExceptions((EvaluatedAssignmentImpl) exclTrigger.getConflictingAssignment(), - rule, triggers); - } - } - } - - if (!hasException) { - LensUtil.triggerRule(rule, triggers, policySituations); - } - } - - private boolean processRuleExceptions(EvaluatedAssignmentImpl evaluatedAssignment, @NotNull EvaluatedPolicyRule rule, Collection> triggers) { - boolean hasException = false; - for (PolicyExceptionType policyException: evaluatedAssignment.getAssignmentType().getPolicyException()) { - if (policyException.getRuleName().equals(rule.getName())) { - LensUtil.processRuleWithException(rule, triggers, policyException); - hasException = true; - } - } - return hasException; - } - - @Override - public String debugDump(int indent) { - StringBuilder sb = new StringBuilder(); - DebugUtil.debugDumpLabelLn(sb, "EvaluatedAssignment", indent); - DebugUtil.debugDumpWithLabelLn(sb, "assignment old", String.valueOf(assignmentIdi.getItemOld()), indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "assignment delta", String.valueOf(assignmentIdi.getDelta()), indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "assignment new", String.valueOf(assignmentIdi.getItemNew()), indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "evaluatedOld", evaluatedOld, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "target", String.valueOf(target), indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "isValid", isValid, indent + 1); - if (forceRecon) { - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "forceRecon", forceRecon, indent + 1); - } - sb.append("\n"); - if (constructionTriple.isEmpty()) { - DebugUtil.debugDumpWithLabel(sb, "Constructions", "(empty)", indent+1); - } else { - DebugUtil.debugDumpWithLabel(sb, "Constructions", constructionTriple, indent+1); - } - if (!personaConstructionTriple.isEmpty()) { - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "Persona constructions", personaConstructionTriple, indent+1); - } - if (!roles.isEmpty()) { - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "Roles", roles, indent+1); - } - dumpRefList(indent, sb, "Orgs", orgRefVals); - dumpRefList(indent, sb, "Membership", membershipRefVals); - dumpRefList(indent, sb, "Delegation", delegationRefVals); - if (!authorizations.isEmpty()) { - sb.append("\n"); - DebugUtil.debugDumpLabel(sb, "Authorizations", indent+1); - for (Authorization autz: authorizations) { - sb.append("\n"); - DebugUtil.indentDebugDump(sb, indent+2); - sb.append(autz.toString()); - } - } - if (!focusMappingEvaluationRequests.isEmpty()) { - sb.append("\n"); - DebugUtil.debugDumpLabel(sb, "Focus mappings evaluation requests", indent+1); - for (AssignedFocusMappingEvaluationRequest request : focusMappingEvaluationRequests) { - sb.append("\n"); - DebugUtil.indentDebugDump(sb, indent+2); - sb.append(request.shortDump()); - } - } - if (!focusMappings.isEmpty()) { - sb.append("\n"); - DebugUtil.debugDumpLabel(sb, "Focus mappings", indent+1); - for (PrismValueDeltaSetTripleProducer mapping: focusMappings) { - sb.append("\n"); - DebugUtil.indentDebugDump(sb, indent+2); - sb.append(mapping.toString()); - } - } - if (target != null) { - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "Target", target.toString(), indent+1); - } - sb.append("\n"); - DebugUtil.debugDumpWithLabelLn(sb, "focusPolicyRules " + ruleCountInfo(focusPolicyRules), focusPolicyRules, indent+1); - DebugUtil.debugDumpWithLabelLn(sb, "thisTargetPolicyRules " + ruleCountInfo(thisTargetPolicyRules), thisTargetPolicyRules, indent+1); - DebugUtil.debugDumpWithLabelLn(sb, "otherTargetsPolicyRules " + ruleCountInfo(otherTargetsPolicyRules), otherTargetsPolicyRules, indent+1); - DebugUtil.debugDumpWithLabelLn(sb, "origin", origin.toString(), indent+1); - return sb.toString(); - } - - private String ruleCountInfo(Collection rules) { - return "(" + rules.size() + ", triggered " + LensContext.getTriggeredRulesCount(rules) + ")"; - } - - private void dumpRefList(int indent, StringBuilder sb, String label, Collection referenceValues) { - if (!referenceValues.isEmpty()) { - sb.append("\n"); - DebugUtil.debugDumpLabel(sb, label, indent+1); - for (PrismReferenceValue refVal: referenceValues) { - sb.append("\n"); - DebugUtil.indentDebugDump(sb, indent+2); - sb.append(refVal.toString()); - } - } - } - - @Override - public String toString() { - return "EvaluatedAssignment(target=" + target - + "; constr=" + constructionTriple - + "; org=" + orgRefVals - + "; autz=" + authorizations - + "; " + focusMappingEvaluationRequests.size() + " focus mappings eval requests" - + "; " + focusMappings.size() + " focus mappings" - + "; " + focusPolicyRules.size()+" rules)"; - } - - String toHumanReadableString() { - if (target != null) { - return "EvaluatedAssignment(" + target + ")"; - } else if (!constructionTriple.isEmpty()) { - return "EvaluatedAssignment(" + constructionTriple + ")"; - } else if (!personaConstructionTriple.isEmpty()) { - return "EvaluatedAssignment(" + personaConstructionTriple + ")"; - } else { - return toString(); - } - } - - @Override - public void shortDump(StringBuilder sb) { - if (target != null) { - sb.append(target); - } else if (!constructionTriple.isEmpty()) { - sb.append("construction("); - constructionTriple.shortDump(sb); - sb.append(")"); - } else if (!personaConstructionTriple.isEmpty()) { - sb.append("personaConstruction("); - personaConstructionTriple.shortDump(sb); - sb.append(")"); - } else { - sb.append(toString()); - return; - } - if (!isValid()) { - sb.append(" invalid "); - } - } - - public List getNonNegativeTargets() { - List rv = new ArrayList<>(); - rv.addAll(roles.getZeroSet()); - rv.addAll(roles.getPlusSet()); - return rv; - } - - /** - * @return mode (adding, deleting, keeping) with respect to the *current* object (not the old one) - */ - @NotNull - public PlusMinusZero getMode() { - if (assignmentIdi.getItemNew() == null || assignmentIdi.getItemNew().isEmpty()) { - return MINUS; - } else if (origin.isCurrent()) { - return ZERO; - } else { - return PLUS; - } - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import java.util.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; +import com.evolveum.midpoint.model.impl.lens.projector.mappings.AssignedFocusMappingEvaluationRequest; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.model.api.context.*; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.PlusMinusZero; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.api.Authorization; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.ShortDumpable; +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.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.jetbrains.annotations.NotNull; + +import static com.evolveum.midpoint.prism.PrismContainerValue.asContainerable; +import static com.evolveum.midpoint.prism.delta.PlusMinusZero.MINUS; +import static com.evolveum.midpoint.prism.delta.PlusMinusZero.PLUS; +import static com.evolveum.midpoint.prism.delta.PlusMinusZero.ZERO; + +/** + * Evaluated assignment that contains all constructions and authorizations from the assignment + * itself and all the applicable inducements from all the roles referenced from the assignment. + * + * @author Radovan Semancik + */ +public class EvaluatedAssignmentImpl implements EvaluatedAssignment, ShortDumpable { + + private static final Trace LOGGER = TraceManager.getTrace(EvaluatedAssignmentImpl.class); + + @NotNull private final ItemDeltaItem,PrismContainerDefinition> assignmentIdi; + private final boolean evaluatedOld; + @NotNull private final DeltaSetTriple> constructionTriple; + @NotNull private final DeltaSetTriple> personaConstructionTriple; + @NotNull private final DeltaSetTriple roles; + @NotNull private final Collection orgRefVals = new ArrayList<>(); + @NotNull private final Collection archetypeRefVals = new ArrayList<>(); + @NotNull private final Collection membershipRefVals = new ArrayList<>(); + @NotNull private final Collection delegationRefVals = new ArrayList<>(); + @NotNull private final Collection authorizations = new ArrayList<>(); + + /** + * Requests to evaluate focus mappings. These are collected during assignment evaluation, but executed afterwards. + * This is to implement proper mapping chaining. + * + * @since 4.0.1 + */ + @NotNull private final Collection focusMappingEvaluationRequests = new ArrayList<>(); + + @NotNull private final Collection> focusMappings = new ArrayList<>(); + + @NotNull private final Collection adminGuiConfigurations = new ArrayList<>(); + // rules related to the focal object (typically e.g. "forbid modifications") + @NotNull private final Collection focusPolicyRules = new ArrayList<>(); + // rules related to the target of this assignment (typically e.g. "approve the assignment") + @NotNull private final Collection thisTargetPolicyRules = new ArrayList<>(); + // rules related to other targets provided by this assignment (e.g. induced or obtained by delegation) + // usually, these rules do not cause direct action (e.g. in the case of approvals); + // however, there are situations in which they are used (e.g. for exclusion rules) + @NotNull private final Collection otherTargetsPolicyRules = new ArrayList<>(); + private String tenantOid; + + private PrismObject target; + private boolean isValid; + private boolean wasValid; + private boolean forceRecon; // used also to force recomputation of parentOrgRefs + @NotNull private final AssignmentOrigin origin; + private Collection policySituations = new HashSet<>(); + + private PrismContext prismContext; + + public EvaluatedAssignmentImpl( + @NotNull ItemDeltaItem, PrismContainerDefinition> assignmentIdi, + boolean evaluatedOld, @NotNull AssignmentOrigin origin, PrismContext prismContext) { + this.assignmentIdi = assignmentIdi; + this.evaluatedOld = evaluatedOld; + this.constructionTriple = prismContext.deltaFactory().createDeltaSetTriple(); + this.personaConstructionTriple = prismContext.deltaFactory().createDeltaSetTriple(); + this.roles = prismContext.deltaFactory().createDeltaSetTriple(); + this.prismContext = prismContext; + this.origin = origin; + } + + @NotNull + public ItemDeltaItem,PrismContainerDefinition> getAssignmentIdi() { + return assignmentIdi; + } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#getAssignmentType() + */ + @Override + public AssignmentType getAssignmentType() { + return asContainerable(assignmentIdi.getSingleValue(evaluatedOld)); + } + + @Override + public Long getAssignmentId() { + Item, PrismContainerDefinition> any = assignmentIdi.getAnyItem(); + return any != null && !any.getValues().isEmpty() ? any.getAnyValue().getId() : null; + } + + @Override + public AssignmentType getAssignmentType(boolean old) { + return asContainerable(assignmentIdi.getSingleValue(old)); + } + + private ObjectReferenceType getTargetRef() { + AssignmentType assignmentType = getAssignmentType(); + if (assignmentType == null) { + return null; + } + return assignmentType.getTargetRef(); + } + + @Override + public QName getRelation() { + ObjectReferenceType targetRef = getTargetRef(); + return targetRef != null ? targetRef.getRelation() : null; + } + + @Override + public QName getNormalizedRelation(RelationRegistry relationRegistry) { + ObjectReferenceType targetRef = getTargetRef(); + return targetRef != null ? relationRegistry.normalizeRelation(targetRef.getRelation()) : null; + } + + @NotNull + public DeltaSetTriple> getConstructionTriple() { + return constructionTriple; + } + + /** + * Construction is not a part of model-api. To avoid heavy refactoring at present time, there is not a classical + * Construction-ConstructionImpl separation, but we use artificial (simplified) EvaluatedConstruction + * API class instead. + */ + @Override + public DeltaSetTriple getEvaluatedConstructions(Task task, OperationResult result) { + DeltaSetTriple rv = prismContext.deltaFactory().createDeltaSetTriple(); + for (PlusMinusZero whichSet : PlusMinusZero.values()) { + Collection> constructionSet = constructionTriple.getSet(whichSet); + if (constructionSet != null) { + for (Construction construction : constructionSet) { + if (!construction.isIgnored()) { + rv.addToSet(whichSet, new EvaluatedConstructionImpl(construction)); + } + } + } + } + return rv; + } + + Collection> getConstructionSet(PlusMinusZero whichSet) { + switch (whichSet) { + case ZERO: return getConstructionTriple().getZeroSet(); + case PLUS: return getConstructionTriple().getPlusSet(); + case MINUS: return getConstructionTriple().getMinusSet(); + default: throw new IllegalArgumentException("whichSet: " + whichSet); + } + } + + void addConstruction(Construction construction, PlusMinusZero whichSet) { + addToTriple(construction, constructionTriple, whichSet); + } + + private void addToTriple(T construction, @NotNull DeltaSetTriple triple, PlusMinusZero whichSet) { + switch (whichSet) { + case ZERO: + triple.addToZeroSet(construction); + break; + case PLUS: + triple.addToPlusSet(construction); + break; + case MINUS: + triple.addToMinusSet(construction); + break; + default: + throw new IllegalArgumentException("whichSet: " + whichSet); + } + } + + @NotNull + DeltaSetTriple> getPersonaConstructionTriple() { + return personaConstructionTriple; + } + + void addPersonaConstruction(PersonaConstruction personaConstruction, PlusMinusZero whichSet) { + addToTriple(personaConstruction, personaConstructionTriple, whichSet); + } + + @NotNull + @Override + public DeltaSetTriple getRoles() { + return roles; + } + + void addRole(EvaluatedAssignmentTargetImpl role, PlusMinusZero mode) { + roles.addToSet(mode, role); + } + + @NotNull + public Collection getOrgRefVals() { + return orgRefVals; + } + + @NotNull + public Collection getArchetypeRefVals() { + return archetypeRefVals; + } + + @NotNull + public Collection getMembershipRefVals() { + return membershipRefVals; + } + + @NotNull + public Collection getDelegationRefVals() { + return delegationRefVals; + } + + public String getTenantOid() { + return tenantOid; + } + + void setTenantOid(String tenantOid) { + this.tenantOid = tenantOid; + } + + @NotNull + @Override + public Collection getAuthorizations() { + return authorizations; + } + + void addAuthorization(Authorization authorization) { + authorizations.add(authorization); + } + + @NotNull + public Collection getAdminGuiConfigurations() { + return adminGuiConfigurations; + } + + void addAdminGuiConfiguration(AdminGuiConfigurationType adminGuiConfiguration) { + adminGuiConfigurations.add(adminGuiConfiguration); + } + + @NotNull + public Collection> getFocusMappings() { + return focusMappings; + } + + @NotNull + public Collection getFocusMappingEvaluationRequests() { + return focusMappingEvaluationRequests; + } + + public void addFocusMapping(MappingImpl focusMapping) { + this.focusMappings.add(focusMapping); + } + + void addFocusMappingEvaluationRequest(AssignedFocusMappingEvaluationRequest request) { + this.focusMappingEvaluationRequests.add(request); + } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#getTarget() + */ + @Override + public PrismObject getTarget() { + return target; + } + + public void setTarget(PrismObject target) { + this.target = target; + } + + public boolean isVirtual() { + return origin.isVirtual(); + } + + /* (non-Javadoc) + * @see com.evolveum.midpoint.model.impl.lens.EvaluatedAssignment#isValid() + */ + @Override + public boolean isValid() { + return isValid; + } + + public void setValid(boolean isValid) { + this.isValid = isValid; + } + + public void setWasValid(boolean wasValid) { + this.wasValid = wasValid; + } + + public boolean isForceRecon() { + return forceRecon; + } + + public void setForceRecon(boolean forceRecon) { + this.forceRecon = forceRecon; + } + + // System configuration is used only to provide $configuration script variable (MID-2372) + public void evaluateConstructions(ObjectDeltaObject focusOdo, PrismObject systemConfiguration, + Consumer resourceConsumer, Task task, OperationResult result) throws SchemaException, + ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, + CommunicationException { + for (Construction construction : constructionTriple.getAllValues()) { + construction.setFocusOdo(focusOdo); + construction.setSystemConfiguration(systemConfiguration); + construction.setWasValid(wasValid); + LOGGER.trace("Evaluating construction '{}' in {}", construction, construction.getSource()); + construction.evaluate(task, result); + if (resourceConsumer != null && construction.getResource() != null) { + resourceConsumer.accept(construction.getResource()); + } + } + } + + void evaluateConstructions(ObjectDeltaObject focusOdo, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException { + evaluateConstructions(focusOdo, null, null, task, result); + } + + @NotNull + public AssignmentOrigin getOrigin() { + return origin; + } + + @Override + public boolean isPresentInCurrentObject() { + return origin.isCurrent(); + } + + @Override + public boolean isPresentInOldObject() { + return origin.isOld(); + } + + @NotNull + public Collection getFocusPolicyRules() { + return focusPolicyRules; + } + + void addFocusPolicyRule(EvaluatedPolicyRule policyRule) { + focusPolicyRules.add(policyRule); + } + + @NotNull + public Collection getThisTargetPolicyRules() { + return thisTargetPolicyRules; + } + + public void addThisTargetPolicyRule(EvaluatedPolicyRule policyRule) { + thisTargetPolicyRules.add(policyRule); + } + + @NotNull + public Collection getOtherTargetsPolicyRules() { + return otherTargetsPolicyRules; + } + + public void addOtherTargetPolicyRule(EvaluatedPolicyRule policyRule) { + otherTargetsPolicyRules.add(policyRule); + } + + @NotNull + public Collection getAllTargetsPolicyRules() { + return Stream.concat(thisTargetPolicyRules.stream(), otherTargetsPolicyRules.stream()).collect(Collectors.toList()); + } + + @Override + public int getAllTargetsPolicyRulesCount() { + return thisTargetPolicyRules.size() + otherTargetsPolicyRules.size(); + } + + @Override + public Collection getPolicySituations() { + return policySituations; + } + + @Override + public void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers) { + boolean hasException = processRuleExceptions(this, rule, triggers); + + for (EvaluatedPolicyRuleTrigger trigger : triggers) { + if (trigger instanceof EvaluatedExclusionTrigger) { + EvaluatedExclusionTrigger exclTrigger = (EvaluatedExclusionTrigger) trigger; + if (exclTrigger.getConflictingAssignment() != null) { + hasException = + hasException || processRuleExceptions((EvaluatedAssignmentImpl) exclTrigger.getConflictingAssignment(), + rule, triggers); + } + } + } + + if (!hasException) { + LensUtil.triggerRule(rule, triggers, policySituations); + } + } + + private boolean processRuleExceptions(EvaluatedAssignmentImpl evaluatedAssignment, @NotNull EvaluatedPolicyRule rule, Collection> triggers) { + boolean hasException = false; + for (PolicyExceptionType policyException: evaluatedAssignment.getAssignmentType().getPolicyException()) { + if (policyException.getRuleName().equals(rule.getName())) { + LensUtil.processRuleWithException(rule, triggers, policyException); + hasException = true; + } + } + return hasException; + } + + @Override + public String debugDump(int indent) { + StringBuilder sb = new StringBuilder(); + DebugUtil.debugDumpLabelLn(sb, "EvaluatedAssignment", indent); + DebugUtil.debugDumpWithLabelLn(sb, "assignment old", String.valueOf(assignmentIdi.getItemOld()), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "assignment delta", String.valueOf(assignmentIdi.getDelta()), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "assignment new", String.valueOf(assignmentIdi.getItemNew()), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "evaluatedOld", evaluatedOld, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "target", String.valueOf(target), indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "isValid", isValid, indent + 1); + if (forceRecon) { + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, "forceRecon", forceRecon, indent + 1); + } + sb.append("\n"); + if (constructionTriple.isEmpty()) { + DebugUtil.debugDumpWithLabel(sb, "Constructions", "(empty)", indent+1); + } else { + DebugUtil.debugDumpWithLabel(sb, "Constructions", constructionTriple, indent+1); + } + if (!personaConstructionTriple.isEmpty()) { + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, "Persona constructions", personaConstructionTriple, indent+1); + } + if (!roles.isEmpty()) { + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, "Roles", roles, indent+1); + } + dumpRefList(indent, sb, "Orgs", orgRefVals); + dumpRefList(indent, sb, "Membership", membershipRefVals); + dumpRefList(indent, sb, "Delegation", delegationRefVals); + if (!authorizations.isEmpty()) { + sb.append("\n"); + DebugUtil.debugDumpLabel(sb, "Authorizations", indent+1); + for (Authorization autz: authorizations) { + sb.append("\n"); + DebugUtil.indentDebugDump(sb, indent+2); + sb.append(autz.toString()); + } + } + if (!focusMappingEvaluationRequests.isEmpty()) { + sb.append("\n"); + DebugUtil.debugDumpLabel(sb, "Focus mappings evaluation requests", indent+1); + for (AssignedFocusMappingEvaluationRequest request : focusMappingEvaluationRequests) { + sb.append("\n"); + DebugUtil.indentDebugDump(sb, indent+2); + sb.append(request.shortDump()); + } + } + if (!focusMappings.isEmpty()) { + sb.append("\n"); + DebugUtil.debugDumpLabel(sb, "Focus mappings", indent+1); + for (PrismValueDeltaSetTripleProducer mapping: focusMappings) { + sb.append("\n"); + DebugUtil.indentDebugDump(sb, indent+2); + sb.append(mapping.toString()); + } + } + if (target != null) { + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, "Target", target.toString(), indent+1); + } + sb.append("\n"); + DebugUtil.debugDumpWithLabelLn(sb, "focusPolicyRules " + ruleCountInfo(focusPolicyRules), focusPolicyRules, indent+1); + DebugUtil.debugDumpWithLabelLn(sb, "thisTargetPolicyRules " + ruleCountInfo(thisTargetPolicyRules), thisTargetPolicyRules, indent+1); + DebugUtil.debugDumpWithLabelLn(sb, "otherTargetsPolicyRules " + ruleCountInfo(otherTargetsPolicyRules), otherTargetsPolicyRules, indent+1); + DebugUtil.debugDumpWithLabelLn(sb, "origin", origin.toString(), indent+1); + return sb.toString(); + } + + private String ruleCountInfo(Collection rules) { + return "(" + rules.size() + ", triggered " + LensContext.getTriggeredRulesCount(rules) + ")"; + } + + private void dumpRefList(int indent, StringBuilder sb, String label, Collection referenceValues) { + if (!referenceValues.isEmpty()) { + sb.append("\n"); + DebugUtil.debugDumpLabel(sb, label, indent+1); + for (PrismReferenceValue refVal: referenceValues) { + sb.append("\n"); + DebugUtil.indentDebugDump(sb, indent+2); + sb.append(refVal.toString()); + } + } + } + + @Override + public String toString() { + return "EvaluatedAssignment(target=" + target + + "; constr=" + constructionTriple + + "; org=" + orgRefVals + + "; autz=" + authorizations + + "; " + focusMappingEvaluationRequests.size() + " focus mappings eval requests" + + "; " + focusMappings.size() + " focus mappings" + + "; " + focusPolicyRules.size()+" rules)"; + } + + String toHumanReadableString() { + if (target != null) { + return "EvaluatedAssignment(" + target + ")"; + } else if (!constructionTriple.isEmpty()) { + return "EvaluatedAssignment(" + constructionTriple + ")"; + } else if (!personaConstructionTriple.isEmpty()) { + return "EvaluatedAssignment(" + personaConstructionTriple + ")"; + } else { + return toString(); + } + } + + @Override + public void shortDump(StringBuilder sb) { + if (target != null) { + sb.append(target); + } else if (!constructionTriple.isEmpty()) { + sb.append("construction("); + constructionTriple.shortDump(sb); + sb.append(")"); + } else if (!personaConstructionTriple.isEmpty()) { + sb.append("personaConstruction("); + personaConstructionTriple.shortDump(sb); + sb.append(")"); + } else { + sb.append(toString()); + return; + } + if (!isValid()) { + sb.append(" invalid "); + } + } + + public List getNonNegativeTargets() { + List rv = new ArrayList<>(); + rv.addAll(roles.getZeroSet()); + rv.addAll(roles.getPlusSet()); + return rv; + } + + /** + * @return mode (adding, deleting, keeping) with respect to the *current* object (not the old one) + */ + @NotNull + public PlusMinusZero getMode() { + if (assignmentIdi.getItemNew() == null || assignmentIdi.getItemNew().isEmpty()) { + return MINUS; + } else if (origin.isCurrent()) { + return ZERO; + } else { + return PLUS; + } + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContextPlaceholder.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContextPlaceholder.java index 76acaae6f20..3bc914e8b2d 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContextPlaceholder.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContextPlaceholder.java @@ -1,48 +1,49 @@ -/* - * Copyright (c) 2014 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; - -/** - * This class does nothing. It just takes place when no real Lens Context is available. - * @see com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder - * - * @author semancik - * - */ -public class LensContextPlaceholder extends LensContext { - - public LensContextPlaceholder(PrismObject focus, PrismContext prismContext) { - super(prismContext); - createFocusContext((Class) focus.asObjectable().getClass()); - getFocusContext().setLoadedObject(focus); - } - - @Override - public String toString() { - return "LensContextPlaceholder()"; - } - - @Override - public String dump(boolean showTriples) { - return "LensContextPlaceholder()"; - } - - @Override - public String debugDump(int indent, boolean showTriples) { - StringBuilder sb = new StringBuilder(); - DebugUtil.indentDebugDump(sb, indent); - sb.append("LensContextPlaceholder"); - return sb.toString(); - } - - -} +/* + * Copyright (c) 2014 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +/** + * This class does nothing. It just takes place when no real Lens Context is available. + * @see ModelExpressionThreadLocalHolder + * + * @author semancik + * + */ +public class LensContextPlaceholder extends LensContext { + + public LensContextPlaceholder(PrismObject focus, PrismContext prismContext) { + super(prismContext); + createFocusContext((Class) focus.asObjectable().getClass()); + getFocusContext().setLoadedObject(focus); + } + + @Override + public String toString() { + return "LensContextPlaceholder()"; + } + + @Override + public String dump(boolean showTriples) { + return "LensContextPlaceholder()"; + } + + @Override + public String debugDump(int indent, boolean showTriples) { + StringBuilder sb = new StringBuilder(); + DebugUtil.indentDebugDump(sb, indent); + sb.append("LensContextPlaceholder"); + return sb.toString(); + } + + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java index 52e75bf975e..2a33379b47b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensElementContext.java @@ -1,859 +1,850 @@ -/* - * Copyright (c) 2010-2018 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.*; -import java.util.function.Consumer; - -import com.evolveum.midpoint.prism.ConsistencyCheckScope; -import com.evolveum.midpoint.prism.Objectable; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; -import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.DeltaConvertor; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -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.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.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; - -import com.evolveum.midpoint.common.crypto.CryptoUtil; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; -import com.evolveum.midpoint.model.api.context.ModelElementContext; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import org.jetbrains.annotations.NotNull; - -/** - * @author semancik - * - */ -public abstract class LensElementContext implements ModelElementContext, Cloneable { - - private static final long serialVersionUID = 1649567559396392861L; - - private static final Trace LOGGER = TraceManager.getTrace(LensElementContext.class); - - private PrismObject objectOld; - private transient PrismObject objectCurrent; - private PrismObject objectNew; - private ObjectDelta primaryDelta; - @NotNull private final List> executedDeltas = new ArrayList<>(); - @NotNull private final Class objectTypeClass; - private String oid = null; - private int iteration; - private String iterationToken; - - transient private SecurityPolicyType securityPolicy; - - /** - * These are policy state modifications that should be applied. - * Currently we apply them in ChangeExecutor.executeChanges only. - * - * In the future we plan to be able to apply some state modifications even - * if the clockwork is exited in non-standard way (e.g. in primary state or with an exception). - * But we must be sure what policy state to store, because some constraints might be triggered - * because of expectation of future state (like conflicting assignment is added etc.) - * --- - * Although placed in LensElementContext, support for this data is currently implemented only for focus, not for projections. - */ - @NotNull private final List> pendingObjectPolicyStateModifications = new ArrayList<>(); - - /** - * Policy state modifications for assignments. - * - * Although we put here also deltas for assignments that are to be deleted, we do not execute these - * (because we implement execution only for the standard exit-path from the clockwork). - */ - @NotNull private final Map>> pendingAssignmentPolicyStateModifications = new HashMap<>(); - - /** - * Initial intent regarding the account. It indicated what the initiator of the operation WANTS TO DO with the - * context. - * If set to null then the decision is left to "the engine". Null is also a typical value - * when the context is created. It may be pre-set under some circumstances, e.g. if an account is being unlinked. - */ - private SynchronizationIntent synchronizationIntent; - - private transient boolean isFresh = false; - - private LensContext lensContext; - - private transient PrismObjectDefinition objectDefinition = null; - - private final Collection policyRules = new ArrayList<>(); - private final Collection policySituations = new ArrayList<>(); - - public LensElementContext(@NotNull Class objectTypeClass, LensContext lensContext) { - super(); - Validate.notNull(objectTypeClass, "Object class is null"); - Validate.notNull(lensContext, "Lens context is null"); - this.lensContext = lensContext; - this.objectTypeClass = objectTypeClass; - } - - public int getIteration() { - return iteration; - } - - public void setIteration(int iteration) { - this.iteration = iteration; - } - - public String getIterationToken() { - return iterationToken; - } - - public void setIterationToken(String iterationToken) { - this.iterationToken = iterationToken; - } - - public SynchronizationIntent getSynchronizationIntent() { - return synchronizationIntent; - } - - public void setSynchronizationIntent(SynchronizationIntent synchronizationIntent) { - this.synchronizationIntent = synchronizationIntent; - } - - public LensContext getLensContext() { - return lensContext; - } - - protected PrismContext getNotNullPrismContext() { - return getLensContext().getNotNullPrismContext(); - } - - @Override - public Class getObjectTypeClass() { - return objectTypeClass; - } - - public boolean represents(Class type) { - return type.isAssignableFrom(objectTypeClass); - } - - public PrismContext getPrismContext() { - return lensContext.getPrismContext(); - } - - @Override - public PrismObject getObjectOld() { - return objectOld; - } - - public void setObjectOld(PrismObject objectOld) { - this.objectOld = objectOld; - } - - @Override - public PrismObject getObjectCurrent() { - return objectCurrent; - } - - public void setObjectCurrent(PrismObject objectCurrent) { - this.objectCurrent = objectCurrent; - } - - public PrismObject getObjectAny() { - if (objectNew != null) { - return objectNew; - } - if (objectCurrent != null) { - return objectCurrent; - } - return objectOld; - } - - /** - * Sets current and possibly also old object. This method is used with - * freshly loaded object. The object is set as current object. - * If the old object was not initialized yet (and if it should be initialized) - * then the object is also set as old object. - */ - public void setLoadedObject(PrismObject object) { - setObjectCurrent(object); - if (objectOld == null && !isAdd()) { - setObjectOld(object.clone()); - } - } - - @Override - public PrismObject getObjectNew() { - return objectNew; - } - - public void setObjectNew(PrismObject objectNew) { - this.objectNew = objectNew; - } - - @Override - public ObjectDelta getPrimaryDelta() { - return primaryDelta; - } - - public boolean hasPrimaryDelta() { - return primaryDelta != null && !primaryDelta.isEmpty(); - } - - /** - * As getPrimaryDelta() but caters for the possibility that an object already exists. - * So, if the primary delta is ADD and object already exists, it should be changed somehow, - * e.g. to MODIFY delta or to null. - * - * Actually, the question is what to do with the attribute values if changed to MODIFY. - * (a) Should they become REPLACE item deltas? (b) ADD ones? - * (c) Or should we compute a difference from objectCurrent to objectToAdd, hoping that - * secondary deltas will re-add everything that might be unknowingly removed by this step? - * (d) Or should we simply ignore ADD delta altogether, hoping that it was executed - * so it need not be repeated? - * - * And, should not we report AlreadyExistingException instead? - * - * It seems that (c) i.e. reverting back to objectToAdd is not a good idea at all. For example, this - * may erase linkRefs for good. - * - * For the time being let us proceed with (d), i.e. ignoring such a delta. - * - * TODO is this OK???? [med] - * - * @return - */ - public ObjectDelta getFixedPrimaryDelta() { - if (primaryDelta == null || !primaryDelta.isAdd() || objectCurrent == null) { - return primaryDelta; // nothing to do - } - // Object does exist. Let's ignore the delta - see description above. - return null; - } - - public void setPrimaryDelta(ObjectDelta primaryDelta) { - this.primaryDelta = primaryDelta; - } - - public void addPrimaryDelta(ObjectDelta delta) throws SchemaException { - if (primaryDelta == null) { - primaryDelta = delta; - } else { - primaryDelta.merge(delta); - } - } - - public void swallowToPrimaryDelta(ItemDelta itemDelta) throws SchemaException { - modifyOrCreatePrimaryDelta( - delta -> delta.swallow(itemDelta), - () -> { - ObjectDelta newPrimaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); - newPrimaryDelta.setOid(oid); - newPrimaryDelta.addModification(itemDelta); - return newPrimaryDelta; - }); - } - - @FunctionalInterface - private interface DeltaModifier { - void modify(ObjectDelta delta) throws SchemaException; - } - - @FunctionalInterface - private interface DeltaCreator { - ObjectDelta create() throws SchemaException; - } - - private void modifyOrCreatePrimaryDelta(DeltaModifier modifier, DeltaCreator creator) throws SchemaException { - if (primaryDelta == null) { - primaryDelta = creator.create(); - } else if (!primaryDelta.isImmutable()) { - modifier.modify(primaryDelta); - } else { - primaryDelta = primaryDelta.clone(); - modifier.modify(primaryDelta); - primaryDelta.freeze(); - } - } - - public abstract void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException; - - // TODO deduplicate with swallowToSecondaryDelta in LensFocusContext - public ObjectDelta swallowToDelta(ObjectDelta originalDelta, ItemDelta propDelta) throws SchemaException { - if (originalDelta == null) { - originalDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); - originalDelta.setOid(getOid()); - } else if (originalDelta.containsModification(propDelta, EquivalenceStrategy.LITERAL_IGNORE_METADATA)) { // todo why literal? - return originalDelta; - } - originalDelta.swallow(propDelta); - return originalDelta; - } - - /** - * Returns collection of all deltas that this element context contains. - * This is a nice method to use if we want to inspect all deltas (e.g. look for a changed item) - * but we want to avoid the overhead of merging all the deltas together. - */ - public abstract Collection> getAllDeltas(); - - @NotNull - public List> getPendingObjectPolicyStateModifications() { - return pendingObjectPolicyStateModifications; - } - - public void clearPendingObjectPolicyStateModifications() { - pendingObjectPolicyStateModifications.clear(); - } - - public void addToPendingObjectPolicyStateModifications(ItemDelta modification) { - pendingObjectPolicyStateModifications.add(modification); - } - - @NotNull - public Map>> getPendingAssignmentPolicyStateModifications() { - return pendingAssignmentPolicyStateModifications; - } - - public void clearPendingAssignmentPolicyStateModifications() { - pendingAssignmentPolicyStateModifications.clear(); - } - - public void addToPendingAssignmentPolicyStateModifications(@NotNull AssignmentType assignment, @NotNull PlusMinusZero mode, @NotNull ItemDelta modification) { - AssignmentSpec spec = new AssignmentSpec(assignment, mode); - pendingAssignmentPolicyStateModifications.computeIfAbsent(spec, k -> new ArrayList<>()).add(modification); - } - - public abstract boolean isAdd(); - - public boolean isModify() { - // TODO I'm not sure why isModify checks both primary and secondary deltas for focus context, while - // isAdd and isDelete care only for the primary delta. - return ObjectDelta.isModify(getPrimaryDelta()) || ObjectDelta.isModify(getSecondaryDelta()); - } - - public abstract boolean isDelete(); - - @NotNull - public SimpleOperationName getOperation() { - if (isAdd()) { - return SimpleOperationName.ADD; - } else if (isDelete()) { - return SimpleOperationName.DELETE; - } else { - return SimpleOperationName.MODIFY; - } - } - - @NotNull - @Override - public List> getExecutedDeltas() { - return executedDeltas; - } - - List> getExecutedDeltas(Boolean audited) { - if (audited == null) { - return executedDeltas; - } - List> deltas = new ArrayList<>(); - for (LensObjectDeltaOperation delta: executedDeltas) { - if (delta.isAudited() == audited) { - deltas.add(delta); - } - } - return deltas; - } - - public void markExecutedDeltasAudited() { - for(LensObjectDeltaOperation executedDelta: executedDeltas) { - executedDelta.setAudited(true); - } - } - - public void addToExecutedDeltas(LensObjectDeltaOperation executedDelta) { - executedDeltas.add(executedDelta.clone()); // must be cloned because e.g. for ADD deltas the object gets modified afterwards - } - - /** - * Returns user delta, both primary and secondary (merged together). - * The returned object is (kind of) immutable. Changing it may do strange things (but most likely the changes will be lost). - */ - public ObjectDelta getDelta() throws SchemaException { - return ObjectDeltaCollectionsUtil.union(primaryDelta, getSecondaryDelta()); - } - - public ObjectDelta getFixedDelta() throws SchemaException { - return ObjectDeltaCollectionsUtil.union(getFixedPrimaryDelta(), getSecondaryDelta()); - } - - public boolean wasAddExecuted() { - - for (LensObjectDeltaOperation executedDeltaOperation : getExecutedDeltas()){ - ObjectDelta executedDelta = executedDeltaOperation.getObjectDelta(); - if (!executedDelta.isAdd()){ - continue; - } else if (executedDelta.getObjectToAdd() != null && executedDelta.getObjectTypeClass().equals(getObjectTypeClass())){ - return true; - } - } - - return false; - } - - abstract public ObjectDeltaObject getObjectDeltaObject() throws SchemaException; - - @Override - public String getOid() { - if (oid == null) { - oid = determineOid(); - } - return oid; - } - - public String determineOid() { - if (getObjectOld() != null && getObjectOld().getOid() != null) { - return getObjectOld().getOid(); - } - if (getObjectCurrent() != null && getObjectCurrent().getOid() != null) { - return getObjectCurrent().getOid(); - } - if (getObjectNew() != null && getObjectNew().getOid() != null) { - return getObjectNew().getOid(); - } - if (getPrimaryDelta() != null && getPrimaryDelta().getOid() != null) { - return getPrimaryDelta().getOid(); - } - if (getSecondaryDelta() != null && getSecondaryDelta().getOid() != null) { - return getSecondaryDelta().getOid(); - } - return null; - } - - /** - * Sets oid to the field but also to the deltas (if applicable). - */ - public void setOid(String oid) { - this.oid = oid; - if (primaryDelta != null && !primaryDelta.isImmutable()) { - primaryDelta.setOid(oid); - } - // TODO What if primary delta is immutable ADD delta and objectNew was taken from it? - // It would be immutable as well in that case. We will see. - if (objectNew != null) { - objectNew.setOid(oid); - } - } - - public PrismObjectDefinition getObjectDefinition() { - if (objectDefinition == null) { - if (objectOld != null) { - objectDefinition = objectOld.getDefinition(); - } else if (objectCurrent != null) { - objectDefinition = objectCurrent.getDefinition(); - } else if (objectNew != null) { - objectDefinition = objectNew.getDefinition(); - } else { - objectDefinition = getNotNullPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass()); - } - } - return objectDefinition; - } - - public boolean isFresh() { - return isFresh; - } - - public void setFresh(boolean isFresh) { - this.isFresh = isFresh; - } - - @NotNull - public Collection getPolicyRules() { - return policyRules; - } - - public void addPolicyRule(EvaluatedPolicyRule policyRule) { - this.policyRules.add(policyRule); - } - - public void clearPolicyRules() { - policyRules.clear(); - } - - public void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers) { - LensUtil.triggerRule(rule, triggers, policySituations); - } - - @NotNull - public Collection getPolicySituations() { - return policySituations; - } - - /** - * Returns security policy applicable to the object. This means security policy - * applicable directory to focus or projection. It will NOT return global - * security policy. - */ - public SecurityPolicyType getSecurityPolicy() { - return securityPolicy; - } - - public void setSecurityPolicy(SecurityPolicyType securityPolicy) { - this.securityPolicy = securityPolicy; - } - - public CredentialsPolicyType getCredentialsPolicy() { - return securityPolicy != null ? securityPolicy.getCredentials() : null; - } - - public void recompute() throws SchemaException, ConfigurationException { - PrismObject base = getObjectCurrentOrOld(); - ObjectDelta delta = getDelta(); - if (delta == null) { - // No change - objectNew = base; - return; - } - objectNew = delta.computeChangedObject(base); - } - - public void checkConsistence() { - checkConsistence(null); - } - - public void checkConsistence(String contextDesc) { - if (getObjectOld() != null) { - checkConsistence(getObjectOld(), "old "+getElementDesc() , contextDesc); - } - if (getObjectCurrent() != null) { - checkConsistence(getObjectCurrent(), "current "+getElementDesc() , contextDesc); - } - if (primaryDelta != null) { - checkConsistence(primaryDelta, false, getElementDesc()+" primary delta in "+this + (contextDesc == null ? "" : " in " +contextDesc)); - } - if (getObjectNew() != null) { - checkConsistence(getObjectNew(), "new "+getElementDesc(), contextDesc); - } - } - - protected void checkConsistence(ObjectDelta delta, boolean requireOid, String contextDesc) { - try { - delta.checkConsistence(requireOid, true, true, ConsistencyCheckScope.THOROUGH); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(e.getMessage()+"; in "+contextDesc, e); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage()+"; in "+contextDesc, e); - } - if (delta.isAdd()) { - checkConsistence(delta.getObjectToAdd(), "add object", contextDesc); - } - } - - protected boolean isRequireSecondaryDeltaOid() { - return primaryDelta == null; - } - - protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { - String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); - try { - object.checkConsistence(true, ConsistencyCheckScope.THOROUGH); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(e.getMessage()+"; in "+desc, e); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage()+"; in "+desc, e); - } - if (object.getDefinition() == null) { - throw new IllegalStateException("No "+getElementDesc()+" definition "+desc); - } - O objectType = object.asObjectable(); - if (objectType instanceof ShadowType) { - ShadowUtil.checkConsistence((PrismObject) object, desc); - } - } - - /** - * Cleans up the contexts by removing some of the working state. - */ - public abstract void cleanup(); - - public void normalize() { - if (objectNew != null) { - objectNew.normalize(); - } - if (objectOld != null) { - objectOld.normalize(); - } - if (objectCurrent != null) { - objectCurrent.normalize(); - } - if (primaryDelta != null && !primaryDelta.isImmutable()) { - primaryDelta.normalize(); - } - } - - public void adopt(PrismContext prismContext) throws SchemaException { - if (objectNew != null) { - prismContext.adopt(objectNew); - } - if (objectOld != null) { - prismContext.adopt(objectOld); - } - if (objectCurrent != null) { - prismContext.adopt(objectCurrent); - } - if (primaryDelta != null) { - prismContext.adopt(primaryDelta); - } - // TODO: object definition? - } - - public abstract LensElementContext clone(LensContext lensContext); - - void copyValues(LensElementContext clone, LensContext lensContext) { - //noinspection unchecked - clone.lensContext = lensContext; - // This is de-facto immutable - clone.objectDefinition = this.objectDefinition; - clone.objectNew = cloneObject(this.objectNew); - clone.objectOld = cloneObject(this.objectOld); - clone.objectCurrent = cloneObject(this.objectCurrent); - clone.oid = this.oid; - clone.primaryDelta = cloneDelta(this.primaryDelta); - clone.isFresh = this.isFresh; - clone.iteration = this.iteration; - clone.iterationToken = this.iterationToken; - clone.securityPolicy = this.securityPolicy; - } - - protected ObjectDelta cloneDelta(ObjectDelta thisDelta) { - if (thisDelta == null) { - return null; - } - return thisDelta.clone(); - } - - private PrismObject cloneObject(PrismObject thisObject) { - if (thisObject == null) { - return null; - } - return thisObject.clone(); - } - - void storeIntoLensElementContextType(LensElementContextType lensElementContextType, LensContext.ExportType exportType) throws SchemaException { - if (objectOld != null && exportType != LensContext.ExportType.MINIMAL) { - if (exportType == LensContext.ExportType.REDUCED) { - lensElementContextType.setObjectOldRef(ObjectTypeUtil.createObjectRef(objectOld, getPrismContext())); - } else { - lensElementContextType.setObjectOld(objectOld.asObjectable().clone()); - } - } - if (objectCurrent != null && exportType == LensContext.ExportType.TRACE) { - lensElementContextType.setObjectCurrent(objectCurrent.asObjectable().clone()); - } - if (objectNew != null && exportType != LensContext.ExportType.MINIMAL) { - if (exportType == LensContext.ExportType.REDUCED) { - lensElementContextType.setObjectNewRef(ObjectTypeUtil.createObjectRef(objectNew, getPrismContext())); - } else { - lensElementContextType.setObjectNew(objectNew.asObjectable().clone()); - } - } - if (exportType != LensContext.ExportType.MINIMAL) { - lensElementContextType.setPrimaryDelta(primaryDelta != null ? DeltaConvertor.toObjectDeltaType(primaryDelta) : null); - for (LensObjectDeltaOperation executedDelta : executedDeltas) { - lensElementContextType.getExecutedDeltas() - .add(LensContext.simplifyExecutedDelta(executedDelta).toLensObjectDeltaOperationType()); - } - lensElementContextType.setObjectTypeClass(objectTypeClass != null ? objectTypeClass.getName() : null); - lensElementContextType.setOid(oid); - lensElementContextType.setIteration(iteration); - lensElementContextType.setIterationToken(iterationToken); - lensElementContextType.setSynchronizationIntent( - synchronizationIntent != null ? synchronizationIntent.toSynchronizationIntentType() : null); - } - lensElementContextType.setFresh(isFresh); - } - - public void retrieveFromLensElementContextType(LensElementContextType lensElementContextType, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { - - ObjectType objectTypeOld = lensElementContextType.getObjectOld(); - this.objectOld = objectTypeOld != null ? (PrismObject) objectTypeOld.asPrismObject() : null; - fixProvisioningTypeInObject(this.objectOld, task, result); - - ObjectType objectTypeNew = lensElementContextType.getObjectNew(); - this.objectNew = objectTypeNew != null ? (PrismObject) objectTypeNew.asPrismObject() : null; - fixProvisioningTypeInObject(this.objectNew, task, result); - - ObjectType object = objectTypeNew != null ? objectTypeNew : objectTypeOld; - - ObjectDeltaType primaryDeltaType = lensElementContextType.getPrimaryDelta(); - this.primaryDelta = primaryDeltaType != null ? (ObjectDelta) DeltaConvertor.createObjectDelta(primaryDeltaType, lensContext.getPrismContext()) : null; - fixProvisioningTypeInDelta(this.primaryDelta, object, task, result); - - for (LensObjectDeltaOperationType eDeltaOperationType : lensElementContextType.getExecutedDeltas()) { - LensObjectDeltaOperation objectDeltaOperation = LensObjectDeltaOperation.fromLensObjectDeltaOperationType(eDeltaOperationType, lensContext.getPrismContext()); - if (objectDeltaOperation.getObjectDelta() != null) { - fixProvisioningTypeInDelta(objectDeltaOperation.getObjectDelta(), object, task, result); - } - this.executedDeltas.add(objectDeltaOperation); - } - - this.oid = lensElementContextType.getOid(); - - this.iteration = lensElementContextType.getIteration() != null ? lensElementContextType.getIteration() : 0; - this.iterationToken = lensElementContextType.getIterationToken(); - this.synchronizationIntent = SynchronizationIntent.fromSynchronizationIntentType(lensElementContextType.getSynchronizationIntent()); - - // note: objectTypeClass is already converted (used in the constructor) - } - - protected void fixProvisioningTypeInDelta(ObjectDelta delta, Objectable object, Task task, OperationResult result) { - if (delta != null && delta.getObjectTypeClass() != null && (ShadowType.class.isAssignableFrom(delta.getObjectTypeClass()) || ResourceType.class.isAssignableFrom(delta.getObjectTypeClass()))) { - try { - lensContext.getProvisioningService().applyDefinition(delta, object, task, result); - } catch (Exception e) { - LOGGER.warn("Error applying provisioning definitions to delta {}: {}", delta, e.getMessage()); - // In case of error just go on. Maybe we do not have correct definition here. But at least we can - // display the GUI pages and maybe we can also salvage the operation. - result.recordWarning(e); - } - } - } - - private void fixProvisioningTypeInObject(PrismObject object, Task task, OperationResult result) { - if (object != null && object.getCompileTimeClass() != null && (ShadowType.class.isAssignableFrom(object.getCompileTimeClass()) || ResourceType.class.isAssignableFrom(object.getCompileTimeClass()))) { - try { - lensContext.getProvisioningService().applyDefinition(object, task, result); - } catch (Exception e) { - LOGGER.warn("Error applying provisioning definitions to object {}: {}", object, e.getMessage()); - // In case of error just go on. Maybe we do not have correct definition here. But at least we can - // display the GUI pages and maybe we can also salvage the operation. - result.recordWarning(e); - } - } - } - - public void checkEncrypted() { - if (objectNew != null) { - CryptoUtil.checkEncrypted(objectNew); - } - if (objectOld != null) { - CryptoUtil.checkEncrypted(objectOld); - } - if (objectCurrent != null) { - CryptoUtil.checkEncrypted(objectCurrent); - } - if (primaryDelta != null) { - CryptoUtil.checkEncrypted(primaryDelta); - } - } - - public boolean operationMatches(ChangeTypeType operation) { - switch (operation) { - case ADD: - return getOperation() == SimpleOperationName.ADD; - case MODIFY: - return getOperation() == SimpleOperationName.MODIFY; - case DELETE: - return getOperation() == SimpleOperationName.DELETE; - } - throw new IllegalArgumentException("Unknown operation "+operation); - } - - protected abstract String getElementDefaultDesc(); - - protected String getElementDesc() { - PrismObject object = getObjectNew(); - if (object == null) { - object = getObjectOld(); - } - if (object == null) { - object = getObjectCurrent(); - } - if (object == null) { - return getElementDefaultDesc(); - } - return object.toDebugType(); - } - - protected String getDebugDumpTitle() { - return StringUtils.capitalize(getElementDesc()); - } - - protected String getDebugDumpTitle(String suffix) { - return getDebugDumpTitle()+" "+suffix; - } - - public abstract String getHumanReadableName(); - - public String getObjectReadVersion() { - // Do NOT use version from object current. - // Current object may be re-read, but the computation - // might be based on older data (objectOld). -// if (getObjectCurrent() != null) { -// return getObjectCurrent().getVersion(); -// } - if (getObjectOld() != null) { - return getObjectOld().getVersion(); - } - return null; - } - - public PrismObject getObjectCurrentOrOld() { - return objectCurrent != null ? objectCurrent : objectOld; - } - - @Override - public boolean isOfType(Class aClass) { - if (aClass.isAssignableFrom(objectTypeClass)) { - return true; - } - PrismObject object = getObjectAny(); - return object != null && aClass.isAssignableFrom(object.asObjectable().getClass()); - } - - public abstract void deleteSecondaryDeltas(); - - public S_ItemEntry deltaBuilder() throws SchemaException { - return getPrismContext().deltaFor(getObjectTypeClass()); - } - - public void forEachObject(Consumer> consumer) { - if (objectCurrent != null) { - consumer.accept(objectCurrent); - } - if (objectOld != null) { - consumer.accept(objectOld); - } - if (objectNew != null) { - consumer.accept(objectNew); - } - } - - public void forEachDelta(Consumer> consumer) { - if (primaryDelta != null) { - consumer.accept(primaryDelta); - } - } - - public void finishBuild() { - if (primaryDelta != null) { - primaryDelta.normalize(); - primaryDelta.freeze(); - } - } -} +/* + * Copyright (c) 2010-2018 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import java.util.*; +import java.util.function.Consumer; + +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.prism.ConsistencyCheckScope; +import com.evolveum.midpoint.prism.Objectable; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; +import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +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.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.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; + +import com.evolveum.midpoint.common.crypto.CryptoUtil; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; +import com.evolveum.midpoint.model.api.context.ModelElementContext; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import org.jetbrains.annotations.NotNull; + +/** + * @author semancik + * + */ +public abstract class LensElementContext implements ModelElementContext, Cloneable { + + private static final long serialVersionUID = 1649567559396392861L; + + private static final Trace LOGGER = TraceManager.getTrace(LensElementContext.class); + + private PrismObject objectOld; + private transient PrismObject objectCurrent; + private PrismObject objectNew; + private ObjectDelta primaryDelta; + @NotNull private final List> executedDeltas = new ArrayList<>(); + @NotNull private final Class objectTypeClass; + private String oid = null; + private int iteration; + private String iterationToken; + + transient private SecurityPolicyType securityPolicy; + + /** + * These are policy state modifications that should be applied. + * Currently we apply them in ChangeExecutor.executeChanges only. + * + * In the future we plan to be able to apply some state modifications even + * if the clockwork is exited in non-standard way (e.g. in primary state or with an exception). + * But we must be sure what policy state to store, because some constraints might be triggered + * because of expectation of future state (like conflicting assignment is added etc.) + * --- + * Although placed in LensElementContext, support for this data is currently implemented only for focus, not for projections. + */ + @NotNull private final List> pendingObjectPolicyStateModifications = new ArrayList<>(); + + /** + * Policy state modifications for assignments. + * + * Although we put here also deltas for assignments that are to be deleted, we do not execute these + * (because we implement execution only for the standard exit-path from the clockwork). + */ + @NotNull private final Map>> pendingAssignmentPolicyStateModifications = new HashMap<>(); + + private SynchronizationIntent synchronizationIntent; + + private transient boolean isFresh = false; + + private LensContext lensContext; + + private transient PrismObjectDefinition objectDefinition = null; + + private final Collection policyRules = new ArrayList<>(); + private final Collection policySituations = new ArrayList<>(); + + public LensElementContext(@NotNull Class objectTypeClass, LensContext lensContext) { + super(); + Validate.notNull(objectTypeClass, "Object class is null"); + Validate.notNull(lensContext, "Lens context is null"); + this.lensContext = lensContext; + this.objectTypeClass = objectTypeClass; + } + + public int getIteration() { + return iteration; + } + + public void setIteration(int iteration) { + this.iteration = iteration; + } + + public String getIterationToken() { + return iterationToken; + } + + public void setIterationToken(String iterationToken) { + this.iterationToken = iterationToken; + } + + public SynchronizationIntent getSynchronizationIntent() { + return synchronizationIntent; + } + + public void setSynchronizationIntent(SynchronizationIntent synchronizationIntent) { + this.synchronizationIntent = synchronizationIntent; + } + + public LensContext getLensContext() { + return lensContext; + } + + protected PrismContext getNotNullPrismContext() { + return getLensContext().getNotNullPrismContext(); + } + + @Override + public Class getObjectTypeClass() { + return objectTypeClass; + } + + public boolean represents(Class type) { + return type.isAssignableFrom(objectTypeClass); + } + + public PrismContext getPrismContext() { + return lensContext.getPrismContext(); + } + + @Override + public PrismObject getObjectOld() { + return objectOld; + } + + public void setObjectOld(PrismObject objectOld) { + this.objectOld = objectOld; + } + + @Override + public PrismObject getObjectCurrent() { + return objectCurrent; + } + + public void setObjectCurrent(PrismObject objectCurrent) { + this.objectCurrent = objectCurrent; + } + + public PrismObject getObjectAny() { + if (objectNew != null) { + return objectNew; + } + if (objectCurrent != null) { + return objectCurrent; + } + return objectOld; + } + + /** + * Sets current and possibly also old object. This method is used with + * freshly loaded object. The object is set as current object. + * If the old object was not initialized yet (and if it should be initialized) + * then the object is also set as old object. + */ + public void setLoadedObject(PrismObject object) { + setObjectCurrent(object); + if (objectOld == null && !isAdd()) { + setObjectOld(object.clone()); + } + } + + @Override + public PrismObject getObjectNew() { + return objectNew; + } + + public void setObjectNew(PrismObject objectNew) { + this.objectNew = objectNew; + } + + @Override + public ObjectDelta getPrimaryDelta() { + return primaryDelta; + } + + public boolean hasPrimaryDelta() { + return primaryDelta != null && !primaryDelta.isEmpty(); + } + + /** + * As getPrimaryDelta() but caters for the possibility that an object already exists. + * So, if the primary delta is ADD and object already exists, it should be changed somehow, + * e.g. to MODIFY delta or to null. + * + * Actually, the question is what to do with the attribute values if changed to MODIFY. + * (a) Should they become REPLACE item deltas? (b) ADD ones? + * (c) Or should we compute a difference from objectCurrent to objectToAdd, hoping that + * secondary deltas will re-add everything that might be unknowingly removed by this step? + * (d) Or should we simply ignore ADD delta altogether, hoping that it was executed + * so it need not be repeated? + * + * And, should not we report AlreadyExistingException instead? + * + * It seems that (c) i.e. reverting back to objectToAdd is not a good idea at all. For example, this + * may erase linkRefs for good. + * + * For the time being let us proceed with (d), i.e. ignoring such a delta. + * + * TODO is this OK???? [med] + * + * @return + */ + public ObjectDelta getFixedPrimaryDelta() { + if (primaryDelta == null || !primaryDelta.isAdd() || objectCurrent == null) { + return primaryDelta; // nothing to do + } + // Object does exist. Let's ignore the delta - see description above. + return null; + } + + public void setPrimaryDelta(ObjectDelta primaryDelta) { + this.primaryDelta = primaryDelta; + } + + public void addPrimaryDelta(ObjectDelta delta) throws SchemaException { + if (primaryDelta == null) { + primaryDelta = delta; + } else { + primaryDelta.merge(delta); + } + } + + public void swallowToPrimaryDelta(ItemDelta itemDelta) throws SchemaException { + modifyOrCreatePrimaryDelta( + delta -> delta.swallow(itemDelta), + () -> { + ObjectDelta newPrimaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); + newPrimaryDelta.setOid(oid); + newPrimaryDelta.addModification(itemDelta); + return newPrimaryDelta; + }); + } + + @FunctionalInterface + private interface DeltaModifier { + void modify(ObjectDelta delta) throws SchemaException; + } + + @FunctionalInterface + private interface DeltaCreator { + ObjectDelta create() throws SchemaException; + } + + private void modifyOrCreatePrimaryDelta(DeltaModifier modifier, DeltaCreator creator) throws SchemaException { + if (primaryDelta == null) { + primaryDelta = creator.create(); + } else if (!primaryDelta.isImmutable()) { + modifier.modify(primaryDelta); + } else { + primaryDelta = primaryDelta.clone(); + modifier.modify(primaryDelta); + primaryDelta.freeze(); + } + } + + public abstract void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException; + + // TODO deduplicate with swallowToSecondaryDelta in LensFocusContext + public ObjectDelta swallowToDelta(ObjectDelta originalDelta, ItemDelta propDelta) throws SchemaException { + if (originalDelta == null) { + originalDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); + originalDelta.setOid(getOid()); + } else if (originalDelta.containsModification(propDelta, EquivalenceStrategy.LITERAL_IGNORE_METADATA)) { // todo why literal? + return originalDelta; + } + originalDelta.swallow(propDelta); + return originalDelta; + } + + /** + * Returns collection of all deltas that this element context contains. + * This is a nice method to use if we want to inspect all deltas (e.g. look for a changed item) + * but we want to avoid the overhead of merging all the deltas together. + */ + public abstract Collection> getAllDeltas(); + + @NotNull + public List> getPendingObjectPolicyStateModifications() { + return pendingObjectPolicyStateModifications; + } + + public void clearPendingObjectPolicyStateModifications() { + pendingObjectPolicyStateModifications.clear(); + } + + public void addToPendingObjectPolicyStateModifications(ItemDelta modification) { + pendingObjectPolicyStateModifications.add(modification); + } + + @NotNull + public Map>> getPendingAssignmentPolicyStateModifications() { + return pendingAssignmentPolicyStateModifications; + } + + public void clearPendingAssignmentPolicyStateModifications() { + pendingAssignmentPolicyStateModifications.clear(); + } + + public void addToPendingAssignmentPolicyStateModifications(@NotNull AssignmentType assignment, @NotNull PlusMinusZero mode, @NotNull ItemDelta modification) { + AssignmentSpec spec = new AssignmentSpec(assignment, mode); + pendingAssignmentPolicyStateModifications.computeIfAbsent(spec, k -> new ArrayList<>()).add(modification); + } + + public boolean isModify() { + // TODO I'm not sure why isModify checks both primary and secondary deltas for focus context, while + // isAdd and isDelete care only for the primary delta. + return ObjectDelta.isModify(getPrimaryDelta()) || ObjectDelta.isModify(getSecondaryDelta()); + } + + @NotNull + public SimpleOperationName getOperation() { + if (isAdd()) { + return SimpleOperationName.ADD; + } else if (isDelete()) { + return SimpleOperationName.DELETE; + } else { + return SimpleOperationName.MODIFY; + } + } + + @NotNull + @Override + public List> getExecutedDeltas() { + return executedDeltas; + } + + List> getExecutedDeltas(Boolean audited) { + if (audited == null) { + return executedDeltas; + } + List> deltas = new ArrayList<>(); + for (LensObjectDeltaOperation delta: executedDeltas) { + if (delta.isAudited() == audited) { + deltas.add(delta); + } + } + return deltas; + } + + public void markExecutedDeltasAudited() { + for(LensObjectDeltaOperation executedDelta: executedDeltas) { + executedDelta.setAudited(true); + } + } + + public void addToExecutedDeltas(LensObjectDeltaOperation executedDelta) { + executedDeltas.add(executedDelta.clone()); // must be cloned because e.g. for ADD deltas the object gets modified afterwards + } + + /** + * Returns user delta, both primary and secondary (merged together). + * The returned object is (kind of) immutable. Changing it may do strange things (but most likely the changes will be lost). + */ + public ObjectDelta getDelta() throws SchemaException { + return ObjectDeltaCollectionsUtil.union(primaryDelta, getSecondaryDelta()); + } + + public ObjectDelta getFixedDelta() throws SchemaException { + return ObjectDeltaCollectionsUtil.union(getFixedPrimaryDelta(), getSecondaryDelta()); + } + + public boolean wasAddExecuted() { + + for (LensObjectDeltaOperation executedDeltaOperation : getExecutedDeltas()){ + ObjectDelta executedDelta = executedDeltaOperation.getObjectDelta(); + if (!executedDelta.isAdd()){ + continue; + } else if (executedDelta.getObjectToAdd() != null && executedDelta.getObjectTypeClass().equals(getObjectTypeClass())){ + return true; + } + } + + return false; + } + + abstract public ObjectDeltaObject getObjectDeltaObject() throws SchemaException; + + @Override + public String getOid() { + if (oid == null) { + oid = determineOid(); + } + return oid; + } + + public String determineOid() { + if (getObjectOld() != null && getObjectOld().getOid() != null) { + return getObjectOld().getOid(); + } + if (getObjectCurrent() != null && getObjectCurrent().getOid() != null) { + return getObjectCurrent().getOid(); + } + if (getObjectNew() != null && getObjectNew().getOid() != null) { + return getObjectNew().getOid(); + } + if (getPrimaryDelta() != null && getPrimaryDelta().getOid() != null) { + return getPrimaryDelta().getOid(); + } + if (getSecondaryDelta() != null && getSecondaryDelta().getOid() != null) { + return getSecondaryDelta().getOid(); + } + return null; + } + + /** + * Sets oid to the field but also to the deltas (if applicable). + */ + public void setOid(String oid) { + this.oid = oid; + if (primaryDelta != null && !primaryDelta.isImmutable()) { + primaryDelta.setOid(oid); + } + // TODO What if primary delta is immutable ADD delta and objectNew was taken from it? + // It would be immutable as well in that case. We will see. + if (objectNew != null) { + objectNew.setOid(oid); + } + } + + public PrismObjectDefinition getObjectDefinition() { + if (objectDefinition == null) { + if (objectOld != null) { + objectDefinition = objectOld.getDefinition(); + } else if (objectCurrent != null) { + objectDefinition = objectCurrent.getDefinition(); + } else if (objectNew != null) { + objectDefinition = objectNew.getDefinition(); + } else { + objectDefinition = getNotNullPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass()); + } + } + return objectDefinition; + } + + public boolean isFresh() { + return isFresh; + } + + public void setFresh(boolean isFresh) { + this.isFresh = isFresh; + } + + @NotNull + public Collection getPolicyRules() { + return policyRules; + } + + public void addPolicyRule(EvaluatedPolicyRule policyRule) { + this.policyRules.add(policyRule); + } + + public void clearPolicyRules() { + policyRules.clear(); + } + + public void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers) { + LensUtil.triggerRule(rule, triggers, policySituations); + } + + @NotNull + public Collection getPolicySituations() { + return policySituations; + } + + /** + * Returns security policy applicable to the object. This means security policy + * applicable directory to focus or projection. It will NOT return global + * security policy. + */ + public SecurityPolicyType getSecurityPolicy() { + return securityPolicy; + } + + public void setSecurityPolicy(SecurityPolicyType securityPolicy) { + this.securityPolicy = securityPolicy; + } + + public CredentialsPolicyType getCredentialsPolicy() { + return securityPolicy != null ? securityPolicy.getCredentials() : null; + } + + public void recompute() throws SchemaException, ConfigurationException { + PrismObject base = getObjectCurrentOrOld(); + ObjectDelta delta = getDelta(); + if (delta == null) { + // No change + objectNew = base; + return; + } + objectNew = delta.computeChangedObject(base); + } + + public void checkConsistence() { + checkConsistence(null); + } + + public void checkConsistence(String contextDesc) { + if (getObjectOld() != null) { + checkConsistence(getObjectOld(), "old "+getElementDesc() , contextDesc); + } + if (getObjectCurrent() != null) { + checkConsistence(getObjectCurrent(), "current "+getElementDesc() , contextDesc); + } + if (primaryDelta != null) { + checkConsistence(primaryDelta, false, getElementDesc()+" primary delta in "+this + (contextDesc == null ? "" : " in " +contextDesc)); + } + if (getObjectNew() != null) { + checkConsistence(getObjectNew(), "new "+getElementDesc(), contextDesc); + } + } + + protected void checkConsistence(ObjectDelta delta, boolean requireOid, String contextDesc) { + try { + delta.checkConsistence(requireOid, true, true, ConsistencyCheckScope.THOROUGH); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(e.getMessage()+"; in "+contextDesc, e); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage()+"; in "+contextDesc, e); + } + if (delta.isAdd()) { + checkConsistence(delta.getObjectToAdd(), "add object", contextDesc); + } + } + + protected boolean isRequireSecondaryDeltaOid() { + return primaryDelta == null; + } + + protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { + String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); + try { + object.checkConsistence(true, ConsistencyCheckScope.THOROUGH); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(e.getMessage()+"; in "+desc, e); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage()+"; in "+desc, e); + } + if (object.getDefinition() == null) { + throw new IllegalStateException("No "+getElementDesc()+" definition "+desc); + } + O objectType = object.asObjectable(); + if (objectType instanceof ShadowType) { + ShadowUtil.checkConsistence((PrismObject) object, desc); + } + } + + /** + * Cleans up the contexts by removing some of the working state. + */ + public abstract void cleanup(); + + public void normalize() { + if (objectNew != null) { + objectNew.normalize(); + } + if (objectOld != null) { + objectOld.normalize(); + } + if (objectCurrent != null) { + objectCurrent.normalize(); + } + if (primaryDelta != null && !primaryDelta.isImmutable()) { + primaryDelta.normalize(); + } + } + + public void adopt(PrismContext prismContext) throws SchemaException { + if (objectNew != null) { + prismContext.adopt(objectNew); + } + if (objectOld != null) { + prismContext.adopt(objectOld); + } + if (objectCurrent != null) { + prismContext.adopt(objectCurrent); + } + if (primaryDelta != null) { + prismContext.adopt(primaryDelta); + } + // TODO: object definition? + } + + public abstract LensElementContext clone(LensContext lensContext); + + void copyValues(LensElementContext clone, LensContext lensContext) { + //noinspection unchecked + clone.lensContext = lensContext; + // This is de-facto immutable + clone.objectDefinition = this.objectDefinition; + clone.objectNew = cloneObject(this.objectNew); + clone.objectOld = cloneObject(this.objectOld); + clone.objectCurrent = cloneObject(this.objectCurrent); + clone.oid = this.oid; + clone.primaryDelta = cloneDelta(this.primaryDelta); + clone.isFresh = this.isFresh; + clone.iteration = this.iteration; + clone.iterationToken = this.iterationToken; + clone.securityPolicy = this.securityPolicy; + } + + protected ObjectDelta cloneDelta(ObjectDelta thisDelta) { + if (thisDelta == null) { + return null; + } + return thisDelta.clone(); + } + + private PrismObject cloneObject(PrismObject thisObject) { + if (thisObject == null) { + return null; + } + return thisObject.clone(); + } + + void storeIntoLensElementContextType(LensElementContextType lensElementContextType, LensContext.ExportType exportType) throws SchemaException { + if (objectOld != null && exportType != LensContext.ExportType.MINIMAL) { + if (exportType == LensContext.ExportType.REDUCED) { + lensElementContextType.setObjectOldRef(ObjectTypeUtil.createObjectRef(objectOld, getPrismContext())); + } else { + lensElementContextType.setObjectOld(objectOld.asObjectable().clone()); + } + } + if (objectCurrent != null && exportType == LensContext.ExportType.TRACE) { + lensElementContextType.setObjectCurrent(objectCurrent.asObjectable().clone()); + } + if (objectNew != null && exportType != LensContext.ExportType.MINIMAL) { + if (exportType == LensContext.ExportType.REDUCED) { + lensElementContextType.setObjectNewRef(ObjectTypeUtil.createObjectRef(objectNew, getPrismContext())); + } else { + lensElementContextType.setObjectNew(objectNew.asObjectable().clone()); + } + } + if (exportType != LensContext.ExportType.MINIMAL) { + lensElementContextType.setPrimaryDelta(primaryDelta != null ? DeltaConvertor.toObjectDeltaType(primaryDelta) : null); + for (LensObjectDeltaOperation executedDelta : executedDeltas) { + lensElementContextType.getExecutedDeltas() + .add(LensContext.simplifyExecutedDelta(executedDelta).toLensObjectDeltaOperationType()); + } + lensElementContextType.setObjectTypeClass(objectTypeClass != null ? objectTypeClass.getName() : null); + lensElementContextType.setOid(oid); + lensElementContextType.setIteration(iteration); + lensElementContextType.setIterationToken(iterationToken); + lensElementContextType.setSynchronizationIntent( + synchronizationIntent != null ? synchronizationIntent.toSynchronizationIntentType() : null); + } + lensElementContextType.setFresh(isFresh); + } + + public void retrieveFromLensElementContextType(LensElementContextType lensElementContextType, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { + + ObjectType objectTypeOld = lensElementContextType.getObjectOld(); + this.objectOld = objectTypeOld != null ? (PrismObject) objectTypeOld.asPrismObject() : null; + fixProvisioningTypeInObject(this.objectOld, task, result); + + ObjectType objectTypeNew = lensElementContextType.getObjectNew(); + this.objectNew = objectTypeNew != null ? (PrismObject) objectTypeNew.asPrismObject() : null; + fixProvisioningTypeInObject(this.objectNew, task, result); + + ObjectType object = objectTypeNew != null ? objectTypeNew : objectTypeOld; + + ObjectDeltaType primaryDeltaType = lensElementContextType.getPrimaryDelta(); + this.primaryDelta = primaryDeltaType != null ? (ObjectDelta) DeltaConvertor.createObjectDelta(primaryDeltaType, lensContext.getPrismContext()) : null; + fixProvisioningTypeInDelta(this.primaryDelta, object, task, result); + + for (LensObjectDeltaOperationType eDeltaOperationType : lensElementContextType.getExecutedDeltas()) { + LensObjectDeltaOperation objectDeltaOperation = LensObjectDeltaOperation.fromLensObjectDeltaOperationType(eDeltaOperationType, lensContext.getPrismContext()); + if (objectDeltaOperation.getObjectDelta() != null) { + fixProvisioningTypeInDelta(objectDeltaOperation.getObjectDelta(), object, task, result); + } + this.executedDeltas.add(objectDeltaOperation); + } + + this.oid = lensElementContextType.getOid(); + + this.iteration = lensElementContextType.getIteration() != null ? lensElementContextType.getIteration() : 0; + this.iterationToken = lensElementContextType.getIterationToken(); + this.synchronizationIntent = SynchronizationIntent.fromSynchronizationIntentType(lensElementContextType.getSynchronizationIntent()); + + // note: objectTypeClass is already converted (used in the constructor) + } + + protected void fixProvisioningTypeInDelta(ObjectDelta delta, Objectable object, Task task, OperationResult result) { + if (delta != null && delta.getObjectTypeClass() != null && (ShadowType.class.isAssignableFrom(delta.getObjectTypeClass()) || ResourceType.class.isAssignableFrom(delta.getObjectTypeClass()))) { + try { + lensContext.getProvisioningService().applyDefinition(delta, object, task, result); + } catch (Exception e) { + LOGGER.warn("Error applying provisioning definitions to delta {}: {}", delta, e.getMessage()); + // In case of error just go on. Maybe we do not have correct definition here. But at least we can + // display the GUI pages and maybe we can also salvage the operation. + result.recordWarning(e); + } + } + } + + private void fixProvisioningTypeInObject(PrismObject object, Task task, OperationResult result) { + if (object != null && object.getCompileTimeClass() != null && (ShadowType.class.isAssignableFrom(object.getCompileTimeClass()) || ResourceType.class.isAssignableFrom(object.getCompileTimeClass()))) { + try { + lensContext.getProvisioningService().applyDefinition(object, task, result); + } catch (Exception e) { + LOGGER.warn("Error applying provisioning definitions to object {}: {}", object, e.getMessage()); + // In case of error just go on. Maybe we do not have correct definition here. But at least we can + // display the GUI pages and maybe we can also salvage the operation. + result.recordWarning(e); + } + } + } + + public void checkEncrypted() { + if (objectNew != null) { + CryptoUtil.checkEncrypted(objectNew); + } + if (objectOld != null) { + CryptoUtil.checkEncrypted(objectOld); + } + if (objectCurrent != null) { + CryptoUtil.checkEncrypted(objectCurrent); + } + if (primaryDelta != null) { + CryptoUtil.checkEncrypted(primaryDelta); + } + } + + public boolean operationMatches(ChangeTypeType operation) { + switch (operation) { + case ADD: + return getOperation() == SimpleOperationName.ADD; + case MODIFY: + return getOperation() == SimpleOperationName.MODIFY; + case DELETE: + return getOperation() == SimpleOperationName.DELETE; + } + throw new IllegalArgumentException("Unknown operation "+operation); + } + + protected abstract String getElementDefaultDesc(); + + protected String getElementDesc() { + PrismObject object = getObjectNew(); + if (object == null) { + object = getObjectOld(); + } + if (object == null) { + object = getObjectCurrent(); + } + if (object == null) { + return getElementDefaultDesc(); + } + return object.toDebugType(); + } + + protected String getDebugDumpTitle() { + return StringUtils.capitalize(getElementDesc()); + } + + protected String getDebugDumpTitle(String suffix) { + return getDebugDumpTitle()+" "+suffix; + } + + public abstract String getHumanReadableName(); + + public String getObjectReadVersion() { + // Do NOT use version from object current. + // Current object may be re-read, but the computation + // might be based on older data (objectOld). +// if (getObjectCurrent() != null) { +// return getObjectCurrent().getVersion(); +// } + if (getObjectOld() != null) { + return getObjectOld().getVersion(); + } + return null; + } + + public PrismObject getObjectCurrentOrOld() { + return objectCurrent != null ? objectCurrent : objectOld; + } + + @Override + public boolean isOfType(Class aClass) { + if (aClass.isAssignableFrom(objectTypeClass)) { + return true; + } + PrismObject object = getObjectAny(); + return object != null && aClass.isAssignableFrom(object.asObjectable().getClass()); + } + + public abstract void deleteSecondaryDeltas(); + + public S_ItemEntry deltaBuilder() throws SchemaException { + return getPrismContext().deltaFor(getObjectTypeClass()); + } + + public void forEachObject(Consumer> consumer) { + if (objectCurrent != null) { + consumer.accept(objectCurrent); + } + if (objectOld != null) { + consumer.accept(objectOld); + } + if (objectNew != null) { + consumer.accept(objectNew); + } + } + + public void forEachDelta(Consumer> consumer) { + if (primaryDelta != null) { + consumer.accept(primaryDelta); + } + } + + public void finishBuild() { + if (primaryDelta != null) { + primaryDelta.normalize(); + primaryDelta.freeze(); + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java index f6403ba90d3..8b8f4ef0e58 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java @@ -1,1502 +1,1507 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.function.Consumer; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.refinery.*; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.schema.DeltaConvertor; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.jvnet.jaxb2_commons.lang.Validate; - -import com.evolveum.midpoint.common.crypto.CryptoUtil; -import com.evolveum.midpoint.model.api.context.ModelProjectionContext; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.prism.delta.ChangeType; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ReferenceDelta; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.processor.ResourceAttribute; -import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; -import com.evolveum.midpoint.schema.processor.ResourceSchema; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.schema.util.SchemaDebugUtil; -import com.evolveum.midpoint.util.Cloner; -import com.evolveum.midpoint.util.DebugUtil; - -/** - * @author semancik - * - */ -public class LensProjectionContext extends LensElementContext implements ModelProjectionContext { - - private static final Trace LOGGER = TraceManager.getTrace(LensProjectionContext.class); - - private ObjectDelta syncDelta; - - /** - * Is this projection the source of the synchronization? (The syncDelta attribute could be used for this but in - * reality it is not always present.) We need this information e.g. when it's not possible to record a clockwork - * exception to focus (e.g. as in MID-5801). The alternate way is to record it into shadow representing the synchronization - * source, e.g. the object being imported, reconciled, or live-synced. - */ - private boolean synchronizationSource; - - private ObjectDelta secondaryDelta; - - /** - * If set to true: absolute state of this projection was detected by the synchronization. - * This is mostly for debugging and visibility. It is not used by projection logic. - */ - private boolean syncAbsoluteTrigger = false; - - /** - * The wave in which this resource should be processed. Initial value of -1 means "undetermined". - */ - private int wave = -1; - - /** - * Indicates that the wave computation is still in progress. - */ - private transient boolean waveIncomplete = false; - - /** - * Definition of account type. - */ - private ResourceShadowDiscriminator resourceShadowDiscriminator; - - private boolean fullShadow = false; - - /** - * True if the account is assigned to the user by a valid assignment. It may be false for accounts that are either - * found to be illegal by live sync, were unassigned from user, etc. - * If set to null the situation is not yet known. Null is a typical value when the context is constructed. - */ - private boolean isAssigned; - private boolean isAssignedOld; - - /** - * True if the account should be part of the synchronization. E.g. outbound expression should be applied to it. - */ - private boolean isActive; - - /** - * True if there is a valid assignment for this projection and/or the policy allows such projection to exist. - */ - private Boolean isLegal = null; - private Boolean isLegalOld = null; - - /** - * True if the projection exists (or will exist) on resource. False if it does not exist. - * NOTE: entire projection is loaded with pointInTime=future. Therefore this does NOT - * reflect actual situation. If there is a pending operation to create the object then - * isExists will in fact be true. - */ - private boolean isExists; - - /** - * True if shadow exists in the repo. It is set to false after projector discovers that a shadow is gone. - * This is a corner case, but it may happen: if shadow is unintentionally deleted, if the shadow is - * cleaned up by another thread and so on. - */ - private transient boolean shadowExistsInRepo = true; - - /** - * Decision regarding the account. It indicated what the engine has DECIDED TO DO with the context. - * If set to null no decision was made yet. Null is also a typical value when the context is created. - */ - private SynchronizationPolicyDecision synchronizationPolicyDecision; - - /** - * True if we want to reconcile account in this context. - */ - private boolean doReconciliation; - - /** - * false if the context should be not taken into the account while synchronizing changes from other resource - */ - private boolean canProject = true; - - /** - * Synchronization situation as it was originally detected by the synchronization code (SynchronizationService). - * This is mostly for debug purposes. Projector and clockwork do not need to care about this. - * The synchronization intent is used instead. - */ - private SynchronizationSituationType synchronizationSituationDetected = null; - /** - * Synchronization situation which was the result of synchronization reaction (projector and clockwork run). - * This is mostly for debug purposes. Projector and clockwork do not care about this (except for setting it). - * The synchronization decision is used instead. - */ - private SynchronizationSituationType synchronizationSituationResolved = null; - - /** - * Delta set triple for accounts. Specifies which accounts should be added, removed or stay as they are. - * It tells almost nothing about attributes directly although the information about attributes are inside - * each account construction (in a form of ValueConstruction that contains attribute delta triples). - * - * Intermediary computation result. It is stored to allow re-computing of account constructions during - * iterative computations. - * - * Source: AssignmentProcessor - * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) - */ - private transient PrismValueDeltaSetTriple> constructionDeltaSetTriple; - - /** - * Triples for outbound mappings; similar to the above. - * Source: OutboundProcessor - * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) - */ - private transient Construction outboundConstruction; - - /** - * Postprocessed triples from the above two properties. - * Source: ConsolidationProcessor - * Target: ReconciliationProcessor - */ - private transient Map,PrismPropertyDefinition>>> squeezedAttributes; - private transient Map,PrismContainerDefinition>>> squeezedAssociations; - private transient Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses; - - private transient Collection dependencies = null; - - // Cached copy, to avoid constructing it over and over again - private transient PrismObjectDefinition shadowDefinition = null; - - private transient RefinedObjectClassDefinition structuralObjectClassDefinition; - private transient Collection auxiliaryObjectClassDefinitions; - private transient CompositeRefinedObjectClassDefinition compositeObjectClassDefinition; - - private SecurityPolicyType projectionSecurityPolicy; - - /** - * Resource that hosts this projection. - */ - transient private ResourceType resource; - - /** - * EXPERIMENTAL. A flag that this projection context has to be put into 'history archive'. - * Necessary to evaluate old state of hasLinkedAccount. - * - * TODO implement as non-transient. - */ - transient private boolean toBeArchived; - - transient private String humanReadableName; - - private Map> entitlementMap = new HashMap<>(); - - transient private String humanReadableString; - - LensProjectionContext(LensContext lensContext, ResourceShadowDiscriminator resourceAccountType) { - super(ShadowType.class, lensContext); - this.resourceShadowDiscriminator = resourceAccountType; - this.isAssigned = false; - this.isAssignedOld = false; - } - - public ObjectDelta getSyncDelta() { - return syncDelta; - } - - public void setSyncDelta(ObjectDelta syncDelta) { - this.syncDelta = syncDelta; - } - - @Override - public ObjectDelta getSecondaryDelta() { - return secondaryDelta; - } - - @Override - public Collection> getAllDeltas() { - List> deltas = new ArrayList<>(2); - ObjectDelta primaryDelta = getPrimaryDelta(); - if (primaryDelta != null) { - deltas.add(primaryDelta); - } - if (secondaryDelta != null) { - deltas.add(secondaryDelta); - } - return deltas; - } - - @Override - public ObjectDeltaObject getObjectDeltaObject() throws SchemaException { - return new ObjectDeltaObject<>(getObjectCurrent(), getDelta(), getObjectNew(), getObjectDefinition()); - } - - public boolean hasSecondaryDelta() { - return secondaryDelta != null && !secondaryDelta.isEmpty(); - } - - @Override - public void setSecondaryDelta(ObjectDelta secondaryDelta) { - this.secondaryDelta = secondaryDelta; - } - - public void addSecondaryDelta(ObjectDelta delta) throws SchemaException { - if (secondaryDelta == null) { - secondaryDelta = delta; - } else { - secondaryDelta.merge(delta); - } - } - - @Override - public void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException { - if (secondaryDelta == null) { - secondaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); - secondaryDelta.setOid(getOid()); - } - LensUtil.setDeltaOldValue(this, itemDelta); - secondaryDelta.swallow(itemDelta); - } - - @Override - public void deleteSecondaryDeltas() { - secondaryDelta = null; - } - - @Override - public void setOid(String oid) { - super.setOid(oid); - if (secondaryDelta != null) { - secondaryDelta.setOid(oid); - } - } - - public boolean isSyncAbsoluteTrigger() { - return syncAbsoluteTrigger; - } - - public void setSyncAbsoluteTrigger(boolean syncAbsoluteTrigger) { - this.syncAbsoluteTrigger = syncAbsoluteTrigger; - } - - public int getWave() { - return wave; - } - - public void setWave(int wave) { - this.wave = wave; - } - - public boolean isWaveIncomplete() { - return waveIncomplete; - } - - public void setWaveIncomplete(boolean waveIncomplete) { - this.waveIncomplete = waveIncomplete; - } - - public boolean isDoReconciliation() { - return doReconciliation; - } - - public void setDoReconciliation(boolean doReconciliation) { - this.doReconciliation = doReconciliation; - } - - @Override - public ResourceShadowDiscriminator getResourceShadowDiscriminator() { - return resourceShadowDiscriminator; - } - - public void markTombstone() { - if (resourceShadowDiscriminator != null) { - resourceShadowDiscriminator.setTombstone(true); - } - setExists(false); - setFullShadow(false); - humanReadableName = null; - } - - public void setResourceShadowDiscriminator(ResourceShadowDiscriminator resourceShadowDiscriminator) { - this.resourceShadowDiscriminator = resourceShadowDiscriminator; - } - - public boolean compareResourceShadowDiscriminator(ResourceShadowDiscriminator rsd, boolean compareOrder) { - Validate.notNull(rsd.getResourceOid()); - if (resourceShadowDiscriminator == null) { - // This may be valid case e.g. in case of broken contexts or if a context is just loading - return false; - } - if (!rsd.getResourceOid().equals(resourceShadowDiscriminator.getResourceOid())) { - return false; - } - if (!rsd.getKind().equals(resourceShadowDiscriminator.getKind())) { - return false; - } - if (rsd.isTombstone() != resourceShadowDiscriminator.isTombstone()) { - return false; - } - if (rsd.getIntent() == null) { - try { - if (!getStructuralObjectClassDefinition().isDefaultInAKind()) { - return false; - } - } catch (SchemaException e) { - throw new SystemException("Internal error: "+e.getMessage(), e); - } - } else if (!rsd.getIntent().equals(resourceShadowDiscriminator.getIntent())) { - return false; - } - if (!Objects.equals(rsd.getTag(), resourceShadowDiscriminator.getTag())) { - return false; - } - - if (compareOrder && rsd.getOrder() != resourceShadowDiscriminator.getOrder()) { - return false; - } - - return true; - } - - public boolean isTombstone() { - if (resourceShadowDiscriminator == null) { - return false; - } - return resourceShadowDiscriminator.isTombstone(); - } - - public void addAccountSyncDelta(ObjectDelta delta) throws SchemaException { - if (syncDelta == null) { - syncDelta = delta; - } else { - syncDelta.merge(delta); - } - } - - public boolean isAdd() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return ObjectDelta.isAdd(getPrimaryDelta()) || ObjectDelta.isAdd(getSecondaryDelta()); - } - } - - public boolean isModify() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.KEEP) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return super.isModify(); - } - } - - public boolean isDelete() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return ObjectDelta.isDelete(syncDelta) || ObjectDelta.isDelete(getPrimaryDelta()) || ObjectDelta.isDelete(getSecondaryDelta()); - } - } - - public ResourceType getResource() { - return resource; - } - - public void setResource(ResourceType resource) { - this.resource = resource; - } - - public Map> getEntitlementMap() { - return entitlementMap; - } - - public void setEntitlementMap(Map> entitlementMap) { - this.entitlementMap = entitlementMap; - } - - @Override - public PrismObjectDefinition getObjectDefinition() { - if (shadowDefinition == null) { - try { - shadowDefinition = ShadowUtil.applyObjectClass(super.getObjectDefinition(), getCompositeObjectClassDefinition()); - } catch (SchemaException e) { - // This should not happen - throw new SystemException(e.getMessage(), e); - } - } - return shadowDefinition; - } - - public boolean isAssigned() { - return isAssigned; - } - - public void setAssigned(boolean isAssigned) { - this.isAssigned = isAssigned; - } - - public boolean isAssignedOld() { - return isAssignedOld; - } - - public void setAssignedOld(boolean isAssignedOld) { - this.isAssignedOld = isAssignedOld; - } - - public boolean isActive() { - return isActive; - } - - public void setActive(boolean isActive) { - this.isActive = isActive; - } - - public Boolean isLegal() { - return isLegal; - } - - public void setLegal(Boolean isLegal) { - this.isLegal = isLegal; - } - - public Boolean isLegalOld() { - return isLegalOld; - } - - public void setLegalOld(Boolean isLegalOld) { - this.isLegalOld = isLegalOld; - } - - public boolean isExists() { - return isExists; - } - - public void setExists(boolean exists) { - this.isExists = exists; - } - - public boolean isShadowExistsInRepo() { - return shadowExistsInRepo; - } - - public void setShadowExistsInRepo(boolean shadowExistsInRepo) { - this.shadowExistsInRepo = shadowExistsInRepo; - } - - public SynchronizationPolicyDecision getSynchronizationPolicyDecision() { - return synchronizationPolicyDecision; - } - - public void setSynchronizationPolicyDecision(SynchronizationPolicyDecision policyDecision) { - this.synchronizationPolicyDecision = policyDecision; - } - - public SynchronizationSituationType getSynchronizationSituationDetected() { - return synchronizationSituationDetected; - } - - public void setSynchronizationSituationDetected( - SynchronizationSituationType synchronizationSituationDetected) { - this.synchronizationSituationDetected = synchronizationSituationDetected; - } - - public SynchronizationSituationType getSynchronizationSituationResolved() { - return synchronizationSituationResolved; - } - - void setSynchronizationSituationResolved(SynchronizationSituationType synchronizationSituationResolved) { - this.synchronizationSituationResolved = synchronizationSituationResolved; - } - - public boolean isFullShadow() { - return fullShadow; - } - - /** - * Returns true if full shadow is available, either loaded or in a create delta. - */ - public boolean hasFullShadow() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { - return true; - } - return isFullShadow(); - } - - public void setFullShadow(boolean fullShadow) { - this.fullShadow = fullShadow; - } - - public ShadowKindType getKind() { - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr != null) { - return discr.getKind(); - } - if (getObjectOld()!=null) { - return getObjectOld().asObjectable().getKind(); - } - if (getObjectCurrent()!=null) { - return getObjectCurrent().asObjectable().getKind(); - } - if (getObjectNew()!=null) { - return getObjectNew().asObjectable().getKind(); - } - return ShadowKindType.ACCOUNT; - } - - public PrismValueDeltaSetTriple> getConstructionDeltaSetTriple() { - return constructionDeltaSetTriple; - } - - public void setConstructionDeltaSetTriple( - PrismValueDeltaSetTriple> constructionDeltaSetTriple) { - this.constructionDeltaSetTriple = constructionDeltaSetTriple; - } - - public Construction getOutboundConstruction() { - return outboundConstruction; - } - - public void setOutboundConstruction(Construction outboundConstruction) { - this.outboundConstruction = outboundConstruction; - } - - public Map,PrismPropertyDefinition>>> getSqueezedAttributes() { - return squeezedAttributes; - } - - public void setSqueezedAttributes(Map,PrismPropertyDefinition>>> squeezedAttributes) { - this.squeezedAttributes = squeezedAttributes; - } - - public Map,PrismContainerDefinition>>> getSqueezedAssociations() { - return squeezedAssociations; - } - - public void setSqueezedAssociations( - Map,PrismContainerDefinition>>> squeezedAssociations) { - this.squeezedAssociations = squeezedAssociations; - } - - public Map, PrismPropertyDefinition>>> getSqueezedAuxiliaryObjectClasses() { - return squeezedAuxiliaryObjectClasses; - } - - public void setSqueezedAuxiliaryObjectClasses( - Map, PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses) { - this.squeezedAuxiliaryObjectClasses = squeezedAuxiliaryObjectClasses; - } - - public ResourceObjectTypeDefinitionType getResourceObjectTypeDefinitionType() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { - return null; - } - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr == null) { - return null; // maybe when an account is deleted - } - if (resource == null) { - return null; - } - ResourceObjectTypeDefinitionType def = ResourceTypeUtil.getResourceObjectTypeDefinitionType(resource, discr.getKind(), discr.getIntent()); - return def; - } - - private ResourceSchema getResourceSchema() throws SchemaException { - return RefinedResourceSchemaImpl.getResourceSchema(resource, getNotNullPrismContext()); - } - - public RefinedResourceSchema getRefinedResourceSchema() throws SchemaException { - if (resource == null) { - return null; - } - return RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, getNotNullPrismContext()); - } - - public RefinedObjectClassDefinition getStructuralObjectClassDefinition() throws SchemaException { - if (structuralObjectClassDefinition == null) { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return null; - } - structuralObjectClassDefinition = refinedSchema.getRefinedDefinition(getResourceShadowDiscriminator().getKind(), getResourceShadowDiscriminator().getIntent()); - } - return structuralObjectClassDefinition; - } - - public Collection getAuxiliaryObjectClassDefinitions() throws SchemaException { - if (auxiliaryObjectClassDefinitions == null) { - refreshAuxiliaryObjectClassDefinitions(); - } - return auxiliaryObjectClassDefinitions; - } - - public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return; - } - List auxiliaryObjectClassQNames = new ArrayList<>(); - addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectOld()); - addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectNew()); - auxiliaryObjectClassDefinitions = new ArrayList<>(auxiliaryObjectClassQNames.size()); - for (QName auxiliaryObjectClassQName: auxiliaryObjectClassQNames) { - RefinedObjectClassDefinition auxiliaryObjectClassDef = refinedSchema.getRefinedDefinition(auxiliaryObjectClassQName); - if (auxiliaryObjectClassDef == null) { - throw new SchemaException("Auxiliary object class "+auxiliaryObjectClassQName+" specified in "+this+" does not exist"); - } - auxiliaryObjectClassDefinitions.add(auxiliaryObjectClassDef); - } - compositeObjectClassDefinition = null; - } - - public CompositeRefinedObjectClassDefinition getCompositeObjectClassDefinition() throws SchemaException { - if (compositeObjectClassDefinition == null) { - RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); - if (structuralObjectClassDefinition != null) { - compositeObjectClassDefinition = new CompositeRefinedObjectClassDefinitionImpl( - structuralObjectClassDefinition, getAuxiliaryObjectClassDefinitions()); - } - } - return compositeObjectClassDefinition; - } - - private void addAuxiliaryObjectClassNames(List auxiliaryObjectClassQNames, - PrismObject shadow) { - if (shadow == null) { - return; - } - for (QName aux: shadow.asObjectable().getAuxiliaryObjectClass()) { - if (!auxiliaryObjectClassQNames.contains(aux)) { - auxiliaryObjectClassQNames.add(aux); - } - } - } - - public RefinedAttributeDefinition findAttributeDefinition(QName attrName) throws SchemaException { - RefinedAttributeDefinition attrDef = getStructuralObjectClassDefinition().findAttributeDefinition(attrName); - if (attrDef != null) { - return attrDef; - } - for (RefinedObjectClassDefinition auxOcDef: getAuxiliaryObjectClassDefinitions()) { - attrDef = auxOcDef.findAttributeDefinition(attrName); - if (attrDef != null) { - return attrDef; - } - } - return null; - } - - public Collection getDependencies() { - if (dependencies == null) { - ResourceObjectTypeDefinitionType resourceAccountTypeDefinitionType = getResourceObjectTypeDefinitionType(); - if (resourceAccountTypeDefinitionType == null) { - // No dependencies. But we cannot set null as that means "unknown". So let's set empty collection instead. - dependencies = new ArrayList<>(); - } else { - dependencies = resourceAccountTypeDefinitionType.getDependency(); - } - } - return dependencies; - } - - public SecurityPolicyType getProjectionSecurityPolicy() { - return projectionSecurityPolicy; - } - - public void setProjectionSecurityPolicy(SecurityPolicyType projectionSecurityPolicy) { - this.projectionSecurityPolicy = projectionSecurityPolicy; - } - - public void setCanProject(boolean canProject) { - this.canProject = canProject; - } - - public boolean isCanProject() { - return canProject; - } - - public AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType() throws SchemaException { - // TODO: per-resource assignment enforcement - ResourceType resource = getResource(); - ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); - - if (objectClassProjectionPolicy != null && objectClassProjectionPolicy.getAssignmentPolicyEnforcement() != null) { - return MiscSchemaUtil.getAssignmentPolicyEnforcementType(objectClassProjectionPolicy); - } - - ProjectionPolicyType globalAccountSynchronizationSettings = null; - if (resource != null){ - globalAccountSynchronizationSettings = resource.getProjection(); - } - - if (globalAccountSynchronizationSettings == null) { - globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); - } - AssignmentPolicyEnforcementType globalAssignmentPolicyEnforcement = MiscSchemaUtil.getAssignmentPolicyEnforcementType(globalAccountSynchronizationSettings); - return globalAssignmentPolicyEnforcement; - } - - public boolean isLegalize() throws SchemaException { - ResourceType resource = getResource(); - - ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); - if (objectClassProjectionPolicy != null) { - return BooleanUtils.isTrue(objectClassProjectionPolicy.isLegalize()); - } - ProjectionPolicyType globalAccountSynchronizationSettings = null; - if (resource != null){ - globalAccountSynchronizationSettings = resource.getProjection(); - } - - if (globalAccountSynchronizationSettings == null) { - globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); - } - - if (globalAccountSynchronizationSettings == null){ - return false; - } - - return BooleanUtils.isTrue(globalAccountSynchronizationSettings.isLegalize()); - } - - private ProjectionPolicyType determineObjectClassProjectionPolicy() throws SchemaException { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return null; - } - - RefinedObjectClassDefinition objectClassDef = refinedSchema.getRefinedDefinition(resourceShadowDiscriminator.getKind(), - resourceShadowDiscriminator.getIntent()); - - if (objectClassDef == null) { - return null; - } - return objectClassDef.getProjection(); - } - - /** - * Recomputes the new state of account (accountNew). It is computed by applying deltas to the old state (accountOld). - * Assuming that oldAccount is already set (or is null if it does not exist) - */ - public void recompute() throws SchemaException { - ObjectDelta accDelta = getDelta(); - - PrismObject base = getObjectCurrent(); - if (base == null) { - base = getObjectOld(); - } - ObjectDelta syncDelta = getSyncDelta(); - if (base == null && syncDelta != null - && ChangeType.ADD.equals(syncDelta.getChangeType())) { - PrismObject objectToAdd = syncDelta.getObjectToAdd(); - if (objectToAdd != null) { - PrismObjectDefinition objectDefinition = objectToAdd.getDefinition(); - // TODO: remove constructor, use some factory method instead - base = getNotNullPrismContext().itemFactory().createObject(objectToAdd.getElementName(), objectDefinition); - base = syncDelta.computeChangedObject(base); - } - } - - if (accDelta == null) { - // No change - setObjectNew(base); - return; - } - - if (base == null && accDelta.isModify()) { - RefinedObjectClassDefinition rOCD = getCompositeObjectClassDefinition(); - if (rOCD != null) { - base = rOCD.createBlankShadow(); - } - } - - setObjectNew(accDelta.computeChangedObject(base)); - } - - public void clearIntermediateResults() { - //constructionDeltaSetTriple = null; - outboundConstruction = null; - squeezedAttributes = null; - } - - /** - * Returns delta suitable for execution. The primary and secondary deltas may not make complete sense all by themselves. - * E.g. they may both be MODIFY deltas even in case that the account should be created. The deltas begin to make sense - * only if combined with sync decision. This method provides the deltas all combined and ready for execution. - */ - @Override - public ObjectDelta getExecutableDelta() throws SchemaException { - SynchronizationPolicyDecision policyDecision = getSynchronizationPolicyDecision(); - ObjectDelta origDelta = getFixedDelta(); - if (policyDecision == SynchronizationPolicyDecision.ADD) { - // let's try to retrieve original (non-fixed) delta. Maybe it's ADD delta so we spare fixing it. - origDelta = getDelta(); - if (origDelta == null || origDelta.isModify()) { - // We need to convert modify delta to ADD - ObjectDelta addDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), - ChangeType.ADD); - RefinedObjectClassDefinition rObjectClassDef = getCompositeObjectClassDefinition(); - - if (rObjectClassDef == null) { - throw new IllegalStateException("Definition for account type " + getResourceShadowDiscriminator() - + " not found in the context, but it should be there"); - } - PrismObject newAccount = rObjectClassDef.createBlankShadow(); - addDelta.setObjectToAdd(newAccount); - - if (origDelta != null) { - addDelta.merge(origDelta); - } - return addDelta; - } - } else if (policyDecision == SynchronizationPolicyDecision.KEEP) { - // Any delta is OK - } else if (policyDecision == SynchronizationPolicyDecision.DELETE) { - ObjectDelta deleteDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), - ChangeType.DELETE); - String oid = getOid(); - if (oid == null) { - throw new IllegalStateException( - "Internal error: account context OID is null during attempt to create delete secondary delta; context=" - +this); - } - deleteDelta.setOid(oid); - return deleteDelta; - } else { - // This is either UNLINK or null, both are in fact the same as KEEP - // Any delta is OK - } - if (origDelta != null && origDelta.isImmutable()) { - // E.g. locked primary delta. - // We need modifiable delta for execution, e.g. to set metadata, oid and so on. - return origDelta.clone(); - } else { - return origDelta; - } - } - - public void checkConsistence() { - checkConsistence(null, true, false); - } - - @Override - public void checkConsistence(String contextDesc) { - super.checkConsistence(contextDesc); - if (secondaryDelta != null) { - boolean requireOid = isRequireSecondaryDeltaOid(); - // Secondary delta may not have OID yet (as it may relate to ADD primary delta that doesn't have OID yet) - checkConsistence(secondaryDelta, requireOid, getElementDesc() + " secondary delta in " + this + (contextDesc == null ? "" : " in " + contextDesc)); - } - } - - public void checkConsistence(String contextDesc, boolean fresh, boolean force) { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { - // No not check these. they may be quite wild. - return; - } - super.checkConsistence(contextDesc); - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { - return; - } - if (fresh && !force && resourceShadowDiscriminator != null && !resourceShadowDiscriminator.isTombstone()) { - if (resource == null) { - throw new IllegalStateException("Null resource in "+this + (contextDesc == null ? "" : " in " +contextDesc)); - } - if (resourceShadowDiscriminator == null) { - throw new IllegalStateException("Null resource account type in "+this + (contextDesc == null ? "" : " in " +contextDesc)); - } - } - if (syncDelta != null) { - try { - syncDelta.checkConsistence(true, true, true, ConsistencyCheckScope.THOROUGH); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); - } - } - } - - @Override - protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { - super.checkConsistence(object, elementDesc, contextDesc); - ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(object); - if (attributesContainer != null) { - ResourceType resource = getResource(); - if (resource != null) { - String resourceNamespace = ResourceTypeUtil.getResourceNamespace(resource); - for(ResourceAttribute attribute: attributesContainer.getAttributes()) { - QName attrName = attribute.getElementName(); - if (SchemaConstants.NS_ICF_SCHEMA.equals(attrName.getNamespaceURI())) { - continue; - } - if (resourceNamespace.equals(attrName.getNamespaceURI())) { - continue; - } - String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); - throw new IllegalStateException("Invalid namespace for attribute "+attrName+" in "+desc); - } - } - } - } - - protected boolean isRequireSecondaryDeltaOid() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD || - synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN || - synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { - return false; - } - if (getResourceShadowDiscriminator() != null && getResourceShadowDiscriminator().getOrder() > 0) { - // These may not have the OID yet - return false; - } - return super.isRequireSecondaryDeltaOid(); - } - - @Override - public void cleanup() { - secondaryDelta = null; - resetSynchronizationPolicyDecision(); -// isLegal = null; -// isLegalOld = null; - isAssigned = false; - isAssignedOld = false; // ??? [med] - isActive = false; - } - - @Override - public void normalize() { - super.normalize(); - if (secondaryDelta != null) { - secondaryDelta.normalize(); - } - if (syncDelta != null) { - syncDelta.normalize(); - } - } - -// @Override -// public void reset() { -// super.reset(); -// wave = -1; -// fullShadow = false; -// isAssigned = false; -// isAssignedOld = false; -// isActive = false; -// resetSynchronizationPolicyDecision(); -// constructionDeltaSetTriple = null; -// outboundConstruction = null; -// dependencies = null; -// squeezedAttributes = null; -// accountPasswordPolicy = null; -// } - - protected void resetSynchronizationPolicyDecision() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) { - toBeArchived = true; - } else if (synchronizationPolicyDecision != null) { - toBeArchived = false; - } - synchronizationPolicyDecision = null; - } - - @Override - public void adopt(PrismContext prismContext) throws SchemaException { - super.adopt(prismContext); - if (syncDelta != null) { - prismContext.adopt(syncDelta); - } - if (secondaryDelta != null) { - prismContext.adopt(secondaryDelta); - } - } - - @Override - public LensProjectionContext clone(LensContext lensContext) { - LensProjectionContext clone = new LensProjectionContext(lensContext, resourceShadowDiscriminator); - copyValues(clone, lensContext); - return clone; - } - - protected void copyValues(LensProjectionContext clone, LensContext lensContext) { - super.copyValues(clone, lensContext); - // do NOT clone transient values such as accountConstructionDeltaSetTriple - // these are not meant to be cloned and they are also not directly cloneable - clone.dependencies = this.dependencies; - clone.doReconciliation = this.doReconciliation; - clone.fullShadow = this.fullShadow; - clone.isAssigned = this.isAssigned; - clone.isAssignedOld = this.isAssignedOld; - clone.outboundConstruction = this.outboundConstruction; - clone.synchronizationPolicyDecision = this.synchronizationPolicyDecision; - clone.resource = this.resource; - clone.resourceShadowDiscriminator = this.resourceShadowDiscriminator; - clone.squeezedAttributes = cloneSqueezedAttributes(); - if (this.syncDelta != null) { - clone.syncDelta = this.syncDelta.clone(); - } - clone.secondaryDelta = cloneDelta(this.secondaryDelta); - clone.wave = this.wave; - clone.synchronizationSource = this.synchronizationSource; - } - - private Map,PrismPropertyDefinition>>> cloneSqueezedAttributes() { - if (squeezedAttributes == null) { - return null; - } - Map,PrismPropertyDefinition>>> clonedMap = new HashMap<>(); - for (Entry,PrismPropertyDefinition>>> entry: squeezedAttributes.entrySet()) { - clonedMap.put(entry.getKey(), entry.getValue().clone(ItemValueWithOrigin::clone)); - } - return clonedMap; - } - - /** - * Returns true if the projection has any value for specified attribute. - */ - public boolean hasValueForAttribute(QName attributeName) { - ItemPath attrPath = ItemPath.create(ShadowType.F_ATTRIBUTES, attributeName); - if (getObjectNew() != null) { - PrismProperty attrNew = getObjectNew().findProperty(attrPath); - if (attrNew != null && !attrNew.isEmpty()) { - return true; - } - } - return false; - } - - private boolean hasValueForAttribute(QName attributeName, Collection> acPpvSet) { - if (acPpvSet == null) { - return false; - } - for (PrismPropertyValue acPpv: acPpvSet) { - Construction ac = acPpv.getValue(); - if (ac.hasValueForAttribute(attributeName)) { - return true; - } - } - return false; - } - - @Override - public void checkEncrypted() { - super.checkEncrypted(); - if (syncDelta != null) { - CryptoUtil.checkEncrypted(syncDelta); - } - if (secondaryDelta != null) { - CryptoUtil.checkEncrypted(secondaryDelta); - } - } - - @Override - public String getHumanReadableName() { - if (humanReadableName == null) { - StringBuilder sb = new StringBuilder(); - sb.append("account("); - String humanReadableAccountIdentifier = getHumanReadableIdentifier(); - if (StringUtils.isEmpty(humanReadableAccountIdentifier)) { - sb.append("no ID"); - } else { - sb.append("ID "); - sb.append(humanReadableAccountIdentifier); - } - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr != null) { - sb.append(", type '"); - sb.append(discr.getIntent()); - sb.append("', "); - if (discr.getOrder() != 0) { - sb.append("order ").append(discr.getOrder()).append(", "); - } - } else { - sb.append(" (no discriminator) "); - } - sb.append(getResource()); - sb.append(")"); - humanReadableName = sb.toString(); - } - return humanReadableName; - } - - private String getHumanReadableIdentifier() { - PrismObject object = getObjectNew(); - if (object == null) { - object = getObjectOld(); - } - if (object == null) { - object = getObjectCurrent(); - } - if (object == null) { - return null; - } - if (object.canRepresent(ShadowType.class)) { - PrismObject shadow = (PrismObject)object; - Collection> identifiers = ShadowUtil.getPrimaryIdentifiers(shadow); - if (identifiers == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - Iterator> iterator = identifiers.iterator(); - while (iterator.hasNext()) { - ResourceAttribute id = iterator.next(); - sb.append(id.toHumanReadableString()); - if (iterator.hasNext()) { - sb.append(","); - } - } - return sb.toString(); - } else { - return object.toString(); - } - } - - @Override - public String debugDump(int indent) { - return debugDump(indent, true); - } - - public String debugDump(int indent, boolean showTriples) { - StringBuilder sb = new StringBuilder(); - SchemaDebugUtil.indentDebugDump(sb, indent); - sb.append("PROJECTION "); - sb.append(getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()); - sb.append(" "); - sb.append(getResourceShadowDiscriminator()); - if (resource != null) { - sb.append(" : "); - sb.append(resource.getName().getOrig()); - } - sb.append("\n"); - SchemaDebugUtil.indentDebugDump(sb, indent + 1); - sb.append("OID: ").append(getOid()); - sb.append(", wave ").append(wave); - if (fullShadow) { - sb.append(", full"); - } else { - sb.append(", shadow"); - } - sb.append(", exists=").append(isExists); - if (!shadowExistsInRepo) { - sb.append(" (shadow not in repo)"); - } - sb.append(", assigned=").append(isAssignedOld).append("->").append(isAssigned); - sb.append(", active=").append(isActive); - sb.append(", legal=").append(isLegalOld).append("->").append(isLegal); - sb.append(", recon=").append(doReconciliation); - sb.append(", canProject=").append(canProject); - sb.append(", syncIntent=").append(getSynchronizationIntent()); - sb.append(", decision=").append(synchronizationPolicyDecision); - if (!isFresh()) { - sb.append(", NOT FRESH"); - } - if (resourceShadowDiscriminator != null && resourceShadowDiscriminator.isTombstone()) { - sb.append(", TOMBSTONE"); - } - if (syncAbsoluteTrigger) { - sb.append(", SYNC TRIGGER"); - } - if (getIteration() != 0) { - sb.append(", iteration=").append(getIteration()).append(" (").append(getIterationToken()).append(")"); - } - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("old"), getObjectOld(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("current"), getObjectCurrent(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("new"), getObjectNew(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("primary delta"), getPrimaryDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("secondary delta"), getSecondaryDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("sync delta"), getSyncDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("executed deltas"), getExecutedDeltas(), indent+1); - - if (showTriples) { - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("constructionDeltaSetTriple"), constructionDeltaSetTriple, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("outbound account construction"), outboundConstruction, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed attributes"), squeezedAttributes, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed associations"), squeezedAssociations, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed auxiliary object classes"), squeezedAuxiliaryObjectClasses, indent + 1); - - // This is just a debug thing -// sb.append("\n"); -// DebugUtil.indentDebugDump(sb, indent); -// sb.append("ACCOUNT dependencies\n"); -// sb.append(DebugUtil.debugDump(dependencies, indent + 1)); - } - - return sb.toString(); - } - - @Override - protected String getElementDefaultDesc() { - return "projection"; - } - - @Override - public String toString() { - return "LensProjectionContext(" + (getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()) + ":" + getOid() + - ( resource == null ? "" : " on " + resource ) + ")"; - } - - /** - * Return a human readable name of the projection object suitable for logs. - */ - public String toHumanReadableString() { - if (humanReadableString == null) { - if (resourceShadowDiscriminator == null) { - humanReadableString = "(null" + resource + ")"; - } else if (resource != null) { - humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resource + ")"; - } else { - humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resourceShadowDiscriminator.getResourceOid() + ")"; - } - } - return humanReadableString; - } - - public String getHumanReadableKind() { - if (resourceShadowDiscriminator == null) { - return "resource object"; - } - return getKindValue(resourceShadowDiscriminator.getKind()); - } - - private String getKindValue(ShadowKindType kind) { - if (kind == null) { - return "null"; - } - return kind.value(); - } - - @Override - protected String getElementDesc() { - if (resourceShadowDiscriminator == null) { - return "shadow"; - } - return getKindValue(resourceShadowDiscriminator.getKind()); - } - - void addToPrismContainer(PrismContainer lensProjectionContextTypeContainer, LensContext.ExportType exportType) throws SchemaException { - LensProjectionContextType lensProjectionContextType = lensProjectionContextTypeContainer.createNewValue().asContainerable(); - super.storeIntoLensElementContextType(lensProjectionContextType, exportType); - lensProjectionContextType.setWave(wave); - lensProjectionContextType.setResourceShadowDiscriminator(resourceShadowDiscriminator != null ? - resourceShadowDiscriminator.toResourceShadowDiscriminatorType() : null); - lensProjectionContextType.setFullShadow(fullShadow); - lensProjectionContextType.setIsExists(isExists); - lensProjectionContextType.setSynchronizationPolicyDecision(synchronizationPolicyDecision != null ? synchronizationPolicyDecision.toSynchronizationPolicyDecisionType() : null); - lensProjectionContextType.setDoReconciliation(doReconciliation); - lensProjectionContextType.setSynchronizationSituationDetected(synchronizationSituationDetected); - lensProjectionContextType.setSynchronizationSituationResolved(synchronizationSituationResolved); - if (exportType != LensContext.ExportType.MINIMAL) { - lensProjectionContextType.setSyncDelta(syncDelta != null ? DeltaConvertor.toObjectDeltaType(syncDelta) : null); - lensProjectionContextType - .setSecondaryDelta(secondaryDelta != null ? DeltaConvertor.toObjectDeltaType(secondaryDelta) : null); - lensProjectionContextType.setIsAssigned(isAssigned); - lensProjectionContextType.setIsAssignedOld(isAssignedOld); - lensProjectionContextType.setIsActive(isActive); - lensProjectionContextType.setIsLegal(isLegal); - lensProjectionContextType.setIsLegalOld(isLegalOld); - if (exportType != LensContext.ExportType.REDUCED && projectionSecurityPolicy != null) { - ObjectReferenceType secRef = new ObjectReferenceType(); - secRef.asReferenceValue().setObject(projectionSecurityPolicy.asPrismObject()); - lensProjectionContextType.setProjectionSecurityPolicyRef(secRef); - } - lensProjectionContextType.setSyncAbsoluteTrigger(syncAbsoluteTrigger); - } - } - - public static LensProjectionContext fromLensProjectionContextType(LensProjectionContextType projectionContextType, LensContext lensContext, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { - - String objectTypeClassString = projectionContextType.getObjectTypeClass(); - if (StringUtils.isEmpty(objectTypeClassString)) { - throw new SystemException("Object type class is undefined in LensProjectionContextType"); - } - ResourceShadowDiscriminator resourceShadowDiscriminator = ResourceShadowDiscriminator.fromResourceShadowDiscriminatorType( - projectionContextType.getResourceShadowDiscriminator(), false); - - LensProjectionContext projectionContext = new LensProjectionContext(lensContext, resourceShadowDiscriminator); - - projectionContext.retrieveFromLensElementContextType(projectionContextType, task, result); - if (projectionContextType.getSyncDelta() != null) { - projectionContext.syncDelta = DeltaConvertor.createObjectDelta(projectionContextType.getSyncDelta(), lensContext.getPrismContext()); - } else { - projectionContext.syncDelta = null; - } - ObjectDeltaType secondaryDeltaType = projectionContextType.getSecondaryDelta(); - projectionContext.secondaryDelta = secondaryDeltaType != null ? - DeltaConvertor.createObjectDelta(secondaryDeltaType, lensContext.getPrismContext()) : null; - ObjectType object = projectionContextType.getObjectNew() != null ? projectionContextType.getObjectNew() : projectionContextType.getObjectOld(); - projectionContext.fixProvisioningTypeInDelta(projectionContext.secondaryDelta, object, task, result); - - projectionContext.wave = projectionContextType.getWave() != null ? projectionContextType.getWave() : 0; - projectionContext.fullShadow = projectionContextType.isFullShadow() != null ? projectionContextType.isFullShadow() : false; - projectionContext.isAssigned = projectionContextType.isIsAssigned() != null ? projectionContextType.isIsAssigned() : false; - projectionContext.isAssignedOld = projectionContextType.isIsAssignedOld() != null ? projectionContextType.isIsAssignedOld() : false; - projectionContext.isActive = projectionContextType.isIsActive() != null ? projectionContextType.isIsActive() : false; - projectionContext.isLegal = projectionContextType.isIsLegal(); - projectionContext.isLegalOld = projectionContextType.isIsLegalOld(); - projectionContext.isExists = projectionContextType.isIsExists() != null ? projectionContextType.isIsExists() : false; - projectionContext.synchronizationPolicyDecision = SynchronizationPolicyDecision.fromSynchronizationPolicyDecisionType(projectionContextType.getSynchronizationPolicyDecision()); - projectionContext.doReconciliation = projectionContextType.isDoReconciliation() != null ? projectionContextType.isDoReconciliation() : false; - projectionContext.synchronizationSituationDetected = projectionContextType.getSynchronizationSituationDetected(); - projectionContext.synchronizationSituationResolved = projectionContextType.getSynchronizationSituationResolved(); - ObjectReferenceType projectionSecurityPolicyRef = projectionContextType.getProjectionSecurityPolicyRef(); - if (projectionSecurityPolicyRef != null) { - projectionContext.projectionSecurityPolicy = (SecurityPolicyType) projectionSecurityPolicyRef.getObjectable(); - } - projectionContext.syncAbsoluteTrigger = projectionContextType.isSyncAbsoluteTrigger(); - - return projectionContext; - } - - // determines whether full shadow is present, based on operation result got from provisioning - public void determineFullShadowFlag(PrismObject loadedShadow) { - ShadowType shadowType = loadedShadow.asObjectable(); - if (ShadowUtil.isDead(shadowType) || !ShadowUtil.isExists(shadowType)) { - setFullShadow(false); - return; - } - OperationResultType fetchResult = shadowType.getFetchResult(); - if (fetchResult != null - && (fetchResult.getStatus() == OperationResultStatusType.PARTIAL_ERROR - || fetchResult.getStatus() == OperationResultStatusType.FATAL_ERROR)) { // todo what about other kinds of status? [e.g. in-progress] - setFullShadow(false); - } else { - setFullShadow(true); - } - } - - public boolean isToBeArchived() { - return toBeArchived; - } - - public void setToBeArchived(boolean toBeArchived) { - this.toBeArchived = toBeArchived; - } - - public String getResourceOid() { - if (resource != null) { - return resource.getOid(); - } else if (resourceShadowDiscriminator != null) { - return resourceShadowDiscriminator.getResourceOid(); - } else { - return null; - } - } - - public ResourceObjectVolatilityType getVolatility() throws SchemaException { - RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); - if (structuralObjectClassDefinition == null) { - return null; - } - return structuralObjectClassDefinition.getVolatility(); - } - - public boolean hasPendingOperations() { - PrismObject current = getObjectCurrent(); - if (current == null) { - return false; - } - return !current.asObjectable().getPendingOperation().isEmpty(); - } - - @Override - public void forEachDelta(Consumer> consumer) { - super.forEachDelta(consumer); - if (secondaryDelta != null) { - consumer.accept(secondaryDelta); - } - } - - PolyString resolveNameIfKnown(Class objectClass, String oid) { - if (ResourceType.class.equals(objectClass)) { - if (resource != null && oid.equals(resource.getOid())) { - return PolyString.toPolyString(resource.getName()); - } - } else if (ShadowType.class.equals(objectClass)) { - PrismObject object = getObjectAny(); - if (object != null && oid.equals(object.getOid())) { - if (object.getName() != null) { - return object.getName(); - } else { - try { - return ShadowUtil.determineShadowName(object); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine shadow name for {}", e, object); - return null; - } - } - } - } - return null; - } - - public String getResourceName() { - ResourceType resource = getResource(); - return resource != null ? PolyString.getOrig(resource.getName()) : getResourceOid(); - } - - public boolean isSynchronizationSource() { - return synchronizationSource; - } - - public void setSynchronizationSource(boolean synchronizationSource) { - this.synchronizationSource = synchronizationSource; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Consumer; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.refinery.*; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.jvnet.jaxb2_commons.lang.Validate; + +import com.evolveum.midpoint.common.crypto.CryptoUtil; +import com.evolveum.midpoint.model.api.context.ModelProjectionContext; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.prism.delta.ChangeType; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.delta.ReferenceDelta; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.processor.ResourceAttribute; +import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; +import com.evolveum.midpoint.schema.processor.ResourceSchema; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.schema.util.SchemaDebugUtil; +import com.evolveum.midpoint.util.Cloner; +import com.evolveum.midpoint.util.DebugUtil; + +/** + * @author semancik + * + */ +public class LensProjectionContext extends LensElementContext implements ModelProjectionContext { + + private static final Trace LOGGER = TraceManager.getTrace(LensProjectionContext.class); + + private ObjectDelta syncDelta; + + /** + * Is this projection the source of the synchronization? (The syncDelta attribute could be used for this but in + * reality it is not always present.) We need this information e.g. when it's not possible to record a clockwork + * exception to focus (e.g. as in MID-5801). The alternate way is to record it into shadow representing the synchronization + * source, e.g. the object being imported, reconciled, or live-synced. + */ + private boolean synchronizationSource; + + private ObjectDelta secondaryDelta; + + /** + * If set to true: absolute state of this projection was detected by the synchronization. + * This is mostly for debugging and visibility. It is not used by projection logic. + */ + private boolean syncAbsoluteTrigger = false; + + /** + * The wave in which this resource should be processed. Initial value of -1 means "undetermined". + */ + private int wave = -1; + + /** + * Indicates that the wave computation is still in progress. + */ + private transient boolean waveIncomplete = false; + + /** + * Definition of account type. + */ + private ResourceShadowDiscriminator resourceShadowDiscriminator; + + private boolean fullShadow = false; + + /** + * True if the account is assigned to the user by a valid assignment. It may be false for accounts that are either + * found to be illegal by live sync, were unassigned from user, etc. + * If set to null the situation is not yet known. Null is a typical value when the context is constructed. + */ + private boolean isAssigned; + private boolean isAssignedOld; + + /** + * True if the account should be part of the synchronization. E.g. outbound expression should be applied to it. + */ + private boolean isActive; + + /** + * True if there is a valid assignment for this projection and/or the policy allows such projection to exist. + */ + private Boolean isLegal = null; + private Boolean isLegalOld = null; + + /** + * True if the projection exists (or will exist) on resource. False if it does not exist. + * NOTE: entire projection is loaded with pointInTime=future. Therefore this does NOT + * reflect actual situation. If there is a pending operation to create the object then + * isExists will in fact be true. + */ + private boolean isExists; + + /** + * True if shadow exists in the repo. It is set to false after projector discovers that a shadow is gone. + * This is a corner case, but it may happen: if shadow is unintentionally deleted, if the shadow is + * cleaned up by another thread and so on. + */ + private transient boolean shadowExistsInRepo = true; + + /** + * Decision regarding the account. It indicated what the engine has DECIDED TO DO with the context. + * If set to null no decision was made yet. Null is also a typical value when the context is created. + */ + private SynchronizationPolicyDecision synchronizationPolicyDecision; + + /** + * True if we want to reconcile account in this context. + */ + private boolean doReconciliation; + + /** + * false if the context should be not taken into the account while synchronizing changes from other resource + */ + private boolean canProject = true; + + /** + * Synchronization situation as it was originally detected by the synchronization code (SynchronizationService). + * This is mostly for debug purposes. Projector and clockwork do not need to care about this. + * The synchronization intent is used instead. + */ + private SynchronizationSituationType synchronizationSituationDetected = null; + /** + * Synchronization situation which was the result of synchronization reaction (projector and clockwork run). + * This is mostly for debug purposes. Projector and clockwork do not care about this (except for setting it). + * The synchronization decision is used instead. + */ + private SynchronizationSituationType synchronizationSituationResolved = null; + + /** + * Delta set triple for accounts. Specifies which accounts should be added, removed or stay as they are. + * It tells almost nothing about attributes directly although the information about attributes are inside + * each account construction (in a form of ValueConstruction that contains attribute delta triples). + * + * Intermediary computation result. It is stored to allow re-computing of account constructions during + * iterative computations. + * + * Source: AssignmentProcessor + * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) + */ + private transient PrismValueDeltaSetTriple> constructionDeltaSetTriple; + + /** + * Triples for outbound mappings; similar to the above. + * Source: OutboundProcessor + * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) + */ + private transient Construction outboundConstruction; + + /** + * Postprocessed triples from the above two properties. + * Source: ConsolidationProcessor + * Target: ReconciliationProcessor + */ + private transient Map,PrismPropertyDefinition>>> squeezedAttributes; + private transient Map,PrismContainerDefinition>>> squeezedAssociations; + private transient Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses; + + private transient Collection dependencies = null; + + // Cached copy, to avoid constructing it over and over again + private transient PrismObjectDefinition shadowDefinition = null; + + private transient RefinedObjectClassDefinition structuralObjectClassDefinition; + private transient Collection auxiliaryObjectClassDefinitions; + private transient CompositeRefinedObjectClassDefinition compositeObjectClassDefinition; + + private SecurityPolicyType projectionSecurityPolicy; + + /** + * Resource that hosts this projection. + */ + transient private ResourceType resource; + + /** + * EXPERIMENTAL. A flag that this projection context has to be put into 'history archive'. + * Necessary to evaluate old state of hasLinkedAccount. + * + * TODO implement as non-transient. + */ + transient private boolean toBeArchived; + + transient private String humanReadableName; + + private Map> entitlementMap = new HashMap<>(); + + transient private String humanReadableString; + + LensProjectionContext(LensContext lensContext, ResourceShadowDiscriminator resourceAccountType) { + super(ShadowType.class, lensContext); + this.resourceShadowDiscriminator = resourceAccountType; + this.isAssigned = false; + this.isAssignedOld = false; + } + + public ObjectDelta getSyncDelta() { + return syncDelta; + } + + public void setSyncDelta(ObjectDelta syncDelta) { + this.syncDelta = syncDelta; + } + + @Override + public ObjectDelta getSecondaryDelta() { + return secondaryDelta; + } + + @Override + public Collection> getAllDeltas() { + List> deltas = new ArrayList<>(2); + ObjectDelta primaryDelta = getPrimaryDelta(); + if (primaryDelta != null) { + deltas.add(primaryDelta); + } + if (secondaryDelta != null) { + deltas.add(secondaryDelta); + } + return deltas; + } + + @Override + public ObjectDeltaObject getObjectDeltaObject() throws SchemaException { + return new ObjectDeltaObject<>(getObjectCurrent(), getDelta(), getObjectNew(), getObjectDefinition()); + } + + public boolean hasSecondaryDelta() { + return secondaryDelta != null && !secondaryDelta.isEmpty(); + } + + @Override + public void setSecondaryDelta(ObjectDelta secondaryDelta) { + this.secondaryDelta = secondaryDelta; + } + + public void addSecondaryDelta(ObjectDelta delta) throws SchemaException { + if (secondaryDelta == null) { + secondaryDelta = delta; + } else { + secondaryDelta.merge(delta); + } + } + + @Override + public void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException { + if (secondaryDelta == null) { + secondaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); + secondaryDelta.setOid(getOid()); + } + LensUtil.setDeltaOldValue(this, itemDelta); + secondaryDelta.swallow(itemDelta); + } + + @Override + public void deleteSecondaryDeltas() { + secondaryDelta = null; + } + + @Override + public void setOid(String oid) { + super.setOid(oid); + if (secondaryDelta != null) { + secondaryDelta.setOid(oid); + } + } + + public boolean isSyncAbsoluteTrigger() { + return syncAbsoluteTrigger; + } + + public void setSyncAbsoluteTrigger(boolean syncAbsoluteTrigger) { + this.syncAbsoluteTrigger = syncAbsoluteTrigger; + } + + public int getWave() { + return wave; + } + + public void setWave(int wave) { + this.wave = wave; + } + + public boolean isWaveIncomplete() { + return waveIncomplete; + } + + public void setWaveIncomplete(boolean waveIncomplete) { + this.waveIncomplete = waveIncomplete; + } + + public boolean isDoReconciliation() { + return doReconciliation; + } + + public void setDoReconciliation(boolean doReconciliation) { + this.doReconciliation = doReconciliation; + } + + @Override + public ResourceShadowDiscriminator getResourceShadowDiscriminator() { + return resourceShadowDiscriminator; + } + + public void markTombstone() { + if (resourceShadowDiscriminator != null) { + resourceShadowDiscriminator.setTombstone(true); + } + setExists(false); + setFullShadow(false); + humanReadableName = null; + } + + public void setResourceShadowDiscriminator(ResourceShadowDiscriminator resourceShadowDiscriminator) { + this.resourceShadowDiscriminator = resourceShadowDiscriminator; + } + + public boolean compareResourceShadowDiscriminator(ResourceShadowDiscriminator rsd, boolean compareOrder) { + Validate.notNull(rsd.getResourceOid()); + if (resourceShadowDiscriminator == null) { + // This may be valid case e.g. in case of broken contexts or if a context is just loading + return false; + } + if (!rsd.getResourceOid().equals(resourceShadowDiscriminator.getResourceOid())) { + return false; + } + if (!rsd.getKind().equals(resourceShadowDiscriminator.getKind())) { + return false; + } + if (rsd.isTombstone() != resourceShadowDiscriminator.isTombstone()) { + return false; + } + if (rsd.getIntent() == null) { + try { + if (!getStructuralObjectClassDefinition().isDefaultInAKind()) { + return false; + } + } catch (SchemaException e) { + throw new SystemException("Internal error: "+e.getMessage(), e); + } + } else if (!rsd.getIntent().equals(resourceShadowDiscriminator.getIntent())) { + return false; + } + if (!Objects.equals(rsd.getTag(), resourceShadowDiscriminator.getTag())) { + return false; + } + + if (compareOrder && rsd.getOrder() != resourceShadowDiscriminator.getOrder()) { + return false; + } + + return true; + } + + public boolean isTombstone() { + if (resourceShadowDiscriminator == null) { + return false; + } + return resourceShadowDiscriminator.isTombstone(); + } + + public void addAccountSyncDelta(ObjectDelta delta) throws SchemaException { + if (syncDelta == null) { + syncDelta = delta; + } else { + syncDelta.merge(delta); + } + } + + public boolean isAdd() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return ObjectDelta.isAdd(getPrimaryDelta()) || ObjectDelta.isAdd(getSecondaryDelta()); + } + } + + public boolean isModify() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.KEEP) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return super.isModify(); + } + } + + public boolean isDelete() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return ObjectDelta.isDelete(syncDelta) || ObjectDelta.isDelete(getPrimaryDelta()) || ObjectDelta.isDelete(getSecondaryDelta()); + } + } + + @Override + public ArchetypeType getArchetype() { + throw new UnsupportedOperationException("Archetypes are not supported for projections."); + } + + public ResourceType getResource() { + return resource; + } + + public void setResource(ResourceType resource) { + this.resource = resource; + } + + public Map> getEntitlementMap() { + return entitlementMap; + } + + public void setEntitlementMap(Map> entitlementMap) { + this.entitlementMap = entitlementMap; + } + + @Override + public PrismObjectDefinition getObjectDefinition() { + if (shadowDefinition == null) { + try { + shadowDefinition = ShadowUtil.applyObjectClass(super.getObjectDefinition(), getCompositeObjectClassDefinition()); + } catch (SchemaException e) { + // This should not happen + throw new SystemException(e.getMessage(), e); + } + } + return shadowDefinition; + } + + public boolean isAssigned() { + return isAssigned; + } + + public void setAssigned(boolean isAssigned) { + this.isAssigned = isAssigned; + } + + public boolean isAssignedOld() { + return isAssignedOld; + } + + public void setAssignedOld(boolean isAssignedOld) { + this.isAssignedOld = isAssignedOld; + } + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean isActive) { + this.isActive = isActive; + } + + public Boolean isLegal() { + return isLegal; + } + + public void setLegal(Boolean isLegal) { + this.isLegal = isLegal; + } + + public Boolean isLegalOld() { + return isLegalOld; + } + + public void setLegalOld(Boolean isLegalOld) { + this.isLegalOld = isLegalOld; + } + + public boolean isExists() { + return isExists; + } + + public void setExists(boolean exists) { + this.isExists = exists; + } + + public boolean isShadowExistsInRepo() { + return shadowExistsInRepo; + } + + public void setShadowExistsInRepo(boolean shadowExistsInRepo) { + this.shadowExistsInRepo = shadowExistsInRepo; + } + + public SynchronizationPolicyDecision getSynchronizationPolicyDecision() { + return synchronizationPolicyDecision; + } + + public void setSynchronizationPolicyDecision(SynchronizationPolicyDecision policyDecision) { + this.synchronizationPolicyDecision = policyDecision; + } + + public SynchronizationSituationType getSynchronizationSituationDetected() { + return synchronizationSituationDetected; + } + + public void setSynchronizationSituationDetected( + SynchronizationSituationType synchronizationSituationDetected) { + this.synchronizationSituationDetected = synchronizationSituationDetected; + } + + public SynchronizationSituationType getSynchronizationSituationResolved() { + return synchronizationSituationResolved; + } + + void setSynchronizationSituationResolved(SynchronizationSituationType synchronizationSituationResolved) { + this.synchronizationSituationResolved = synchronizationSituationResolved; + } + + public boolean isFullShadow() { + return fullShadow; + } + + /** + * Returns true if full shadow is available, either loaded or in a create delta. + */ + public boolean hasFullShadow() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { + return true; + } + return isFullShadow(); + } + + public void setFullShadow(boolean fullShadow) { + this.fullShadow = fullShadow; + } + + public ShadowKindType getKind() { + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr != null) { + return discr.getKind(); + } + if (getObjectOld()!=null) { + return getObjectOld().asObjectable().getKind(); + } + if (getObjectCurrent()!=null) { + return getObjectCurrent().asObjectable().getKind(); + } + if (getObjectNew()!=null) { + return getObjectNew().asObjectable().getKind(); + } + return ShadowKindType.ACCOUNT; + } + + public PrismValueDeltaSetTriple> getConstructionDeltaSetTriple() { + return constructionDeltaSetTriple; + } + + public void setConstructionDeltaSetTriple( + PrismValueDeltaSetTriple> constructionDeltaSetTriple) { + this.constructionDeltaSetTriple = constructionDeltaSetTriple; + } + + public Construction getOutboundConstruction() { + return outboundConstruction; + } + + public void setOutboundConstruction(Construction outboundConstruction) { + this.outboundConstruction = outboundConstruction; + } + + public Map,PrismPropertyDefinition>>> getSqueezedAttributes() { + return squeezedAttributes; + } + + public void setSqueezedAttributes(Map,PrismPropertyDefinition>>> squeezedAttributes) { + this.squeezedAttributes = squeezedAttributes; + } + + public Map,PrismContainerDefinition>>> getSqueezedAssociations() { + return squeezedAssociations; + } + + public void setSqueezedAssociations( + Map,PrismContainerDefinition>>> squeezedAssociations) { + this.squeezedAssociations = squeezedAssociations; + } + + public Map, PrismPropertyDefinition>>> getSqueezedAuxiliaryObjectClasses() { + return squeezedAuxiliaryObjectClasses; + } + + public void setSqueezedAuxiliaryObjectClasses( + Map, PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses) { + this.squeezedAuxiliaryObjectClasses = squeezedAuxiliaryObjectClasses; + } + + public ResourceObjectTypeDefinitionType getResourceObjectTypeDefinitionType() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { + return null; + } + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr == null) { + return null; // maybe when an account is deleted + } + if (resource == null) { + return null; + } + ResourceObjectTypeDefinitionType def = ResourceTypeUtil.getResourceObjectTypeDefinitionType(resource, discr.getKind(), discr.getIntent()); + return def; + } + + private ResourceSchema getResourceSchema() throws SchemaException { + return RefinedResourceSchemaImpl.getResourceSchema(resource, getNotNullPrismContext()); + } + + public RefinedResourceSchema getRefinedResourceSchema() throws SchemaException { + if (resource == null) { + return null; + } + return RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, getNotNullPrismContext()); + } + + public RefinedObjectClassDefinition getStructuralObjectClassDefinition() throws SchemaException { + if (structuralObjectClassDefinition == null) { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return null; + } + structuralObjectClassDefinition = refinedSchema.getRefinedDefinition(getResourceShadowDiscriminator().getKind(), getResourceShadowDiscriminator().getIntent()); + } + return structuralObjectClassDefinition; + } + + public Collection getAuxiliaryObjectClassDefinitions() throws SchemaException { + if (auxiliaryObjectClassDefinitions == null) { + refreshAuxiliaryObjectClassDefinitions(); + } + return auxiliaryObjectClassDefinitions; + } + + public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return; + } + List auxiliaryObjectClassQNames = new ArrayList<>(); + addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectOld()); + addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectNew()); + auxiliaryObjectClassDefinitions = new ArrayList<>(auxiliaryObjectClassQNames.size()); + for (QName auxiliaryObjectClassQName: auxiliaryObjectClassQNames) { + RefinedObjectClassDefinition auxiliaryObjectClassDef = refinedSchema.getRefinedDefinition(auxiliaryObjectClassQName); + if (auxiliaryObjectClassDef == null) { + throw new SchemaException("Auxiliary object class "+auxiliaryObjectClassQName+" specified in "+this+" does not exist"); + } + auxiliaryObjectClassDefinitions.add(auxiliaryObjectClassDef); + } + compositeObjectClassDefinition = null; + } + + public CompositeRefinedObjectClassDefinition getCompositeObjectClassDefinition() throws SchemaException { + if (compositeObjectClassDefinition == null) { + RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); + if (structuralObjectClassDefinition != null) { + compositeObjectClassDefinition = new CompositeRefinedObjectClassDefinitionImpl( + structuralObjectClassDefinition, getAuxiliaryObjectClassDefinitions()); + } + } + return compositeObjectClassDefinition; + } + + private void addAuxiliaryObjectClassNames(List auxiliaryObjectClassQNames, + PrismObject shadow) { + if (shadow == null) { + return; + } + for (QName aux: shadow.asObjectable().getAuxiliaryObjectClass()) { + if (!auxiliaryObjectClassQNames.contains(aux)) { + auxiliaryObjectClassQNames.add(aux); + } + } + } + + public RefinedAttributeDefinition findAttributeDefinition(QName attrName) throws SchemaException { + RefinedAttributeDefinition attrDef = getStructuralObjectClassDefinition().findAttributeDefinition(attrName); + if (attrDef != null) { + return attrDef; + } + for (RefinedObjectClassDefinition auxOcDef: getAuxiliaryObjectClassDefinitions()) { + attrDef = auxOcDef.findAttributeDefinition(attrName); + if (attrDef != null) { + return attrDef; + } + } + return null; + } + + public Collection getDependencies() { + if (dependencies == null) { + ResourceObjectTypeDefinitionType resourceAccountTypeDefinitionType = getResourceObjectTypeDefinitionType(); + if (resourceAccountTypeDefinitionType == null) { + // No dependencies. But we cannot set null as that means "unknown". So let's set empty collection instead. + dependencies = new ArrayList<>(); + } else { + dependencies = resourceAccountTypeDefinitionType.getDependency(); + } + } + return dependencies; + } + + public SecurityPolicyType getProjectionSecurityPolicy() { + return projectionSecurityPolicy; + } + + public void setProjectionSecurityPolicy(SecurityPolicyType projectionSecurityPolicy) { + this.projectionSecurityPolicy = projectionSecurityPolicy; + } + + public void setCanProject(boolean canProject) { + this.canProject = canProject; + } + + public boolean isCanProject() { + return canProject; + } + + public AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType() throws SchemaException { + // TODO: per-resource assignment enforcement + ResourceType resource = getResource(); + ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); + + if (objectClassProjectionPolicy != null && objectClassProjectionPolicy.getAssignmentPolicyEnforcement() != null) { + return MiscSchemaUtil.getAssignmentPolicyEnforcementType(objectClassProjectionPolicy); + } + + ProjectionPolicyType globalAccountSynchronizationSettings = null; + if (resource != null){ + globalAccountSynchronizationSettings = resource.getProjection(); + } + + if (globalAccountSynchronizationSettings == null) { + globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); + } + AssignmentPolicyEnforcementType globalAssignmentPolicyEnforcement = MiscSchemaUtil.getAssignmentPolicyEnforcementType(globalAccountSynchronizationSettings); + return globalAssignmentPolicyEnforcement; + } + + public boolean isLegalize() throws SchemaException { + ResourceType resource = getResource(); + + ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); + if (objectClassProjectionPolicy != null) { + return BooleanUtils.isTrue(objectClassProjectionPolicy.isLegalize()); + } + ProjectionPolicyType globalAccountSynchronizationSettings = null; + if (resource != null){ + globalAccountSynchronizationSettings = resource.getProjection(); + } + + if (globalAccountSynchronizationSettings == null) { + globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); + } + + if (globalAccountSynchronizationSettings == null){ + return false; + } + + return BooleanUtils.isTrue(globalAccountSynchronizationSettings.isLegalize()); + } + + private ProjectionPolicyType determineObjectClassProjectionPolicy() throws SchemaException { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return null; + } + + RefinedObjectClassDefinition objectClassDef = refinedSchema.getRefinedDefinition(resourceShadowDiscriminator.getKind(), + resourceShadowDiscriminator.getIntent()); + + if (objectClassDef == null) { + return null; + } + return objectClassDef.getProjection(); + } + + /** + * Recomputes the new state of account (accountNew). It is computed by applying deltas to the old state (accountOld). + * Assuming that oldAccount is already set (or is null if it does not exist) + */ + public void recompute() throws SchemaException { + ObjectDelta accDelta = getDelta(); + + PrismObject base = getObjectCurrent(); + if (base == null) { + base = getObjectOld(); + } + ObjectDelta syncDelta = getSyncDelta(); + if (base == null && syncDelta != null + && ChangeType.ADD.equals(syncDelta.getChangeType())) { + PrismObject objectToAdd = syncDelta.getObjectToAdd(); + if (objectToAdd != null) { + PrismObjectDefinition objectDefinition = objectToAdd.getDefinition(); + // TODO: remove constructor, use some factory method instead + base = getNotNullPrismContext().itemFactory().createObject(objectToAdd.getElementName(), objectDefinition); + base = syncDelta.computeChangedObject(base); + } + } + + if (accDelta == null) { + // No change + setObjectNew(base); + return; + } + + if (base == null && accDelta.isModify()) { + RefinedObjectClassDefinition rOCD = getCompositeObjectClassDefinition(); + if (rOCD != null) { + base = rOCD.createBlankShadow(); + } + } + + setObjectNew(accDelta.computeChangedObject(base)); + } + + public void clearIntermediateResults() { + //constructionDeltaSetTriple = null; + outboundConstruction = null; + squeezedAttributes = null; + } + + /** + * Returns delta suitable for execution. The primary and secondary deltas may not make complete sense all by themselves. + * E.g. they may both be MODIFY deltas even in case that the account should be created. The deltas begin to make sense + * only if combined with sync decision. This method provides the deltas all combined and ready for execution. + */ + @Override + public ObjectDelta getExecutableDelta() throws SchemaException { + SynchronizationPolicyDecision policyDecision = getSynchronizationPolicyDecision(); + ObjectDelta origDelta = getFixedDelta(); + if (policyDecision == SynchronizationPolicyDecision.ADD) { + // let's try to retrieve original (non-fixed) delta. Maybe it's ADD delta so we spare fixing it. + origDelta = getDelta(); + if (origDelta == null || origDelta.isModify()) { + // We need to convert modify delta to ADD + ObjectDelta addDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), + ChangeType.ADD); + RefinedObjectClassDefinition rObjectClassDef = getCompositeObjectClassDefinition(); + + if (rObjectClassDef == null) { + throw new IllegalStateException("Definition for account type " + getResourceShadowDiscriminator() + + " not found in the context, but it should be there"); + } + PrismObject newAccount = rObjectClassDef.createBlankShadow(); + addDelta.setObjectToAdd(newAccount); + + if (origDelta != null) { + addDelta.merge(origDelta); + } + return addDelta; + } + } else if (policyDecision == SynchronizationPolicyDecision.KEEP) { + // Any delta is OK + } else if (policyDecision == SynchronizationPolicyDecision.DELETE) { + ObjectDelta deleteDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), + ChangeType.DELETE); + String oid = getOid(); + if (oid == null) { + throw new IllegalStateException( + "Internal error: account context OID is null during attempt to create delete secondary delta; context=" + +this); + } + deleteDelta.setOid(oid); + return deleteDelta; + } else { + // This is either UNLINK or null, both are in fact the same as KEEP + // Any delta is OK + } + if (origDelta != null && origDelta.isImmutable()) { + // E.g. locked primary delta. + // We need modifiable delta for execution, e.g. to set metadata, oid and so on. + return origDelta.clone(); + } else { + return origDelta; + } + } + + public void checkConsistence() { + checkConsistence(null, true, false); + } + + @Override + public void checkConsistence(String contextDesc) { + super.checkConsistence(contextDesc); + if (secondaryDelta != null) { + boolean requireOid = isRequireSecondaryDeltaOid(); + // Secondary delta may not have OID yet (as it may relate to ADD primary delta that doesn't have OID yet) + checkConsistence(secondaryDelta, requireOid, getElementDesc() + " secondary delta in " + this + (contextDesc == null ? "" : " in " + contextDesc)); + } + } + + public void checkConsistence(String contextDesc, boolean fresh, boolean force) { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { + // No not check these. they may be quite wild. + return; + } + super.checkConsistence(contextDesc); + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { + return; + } + if (fresh && !force && resourceShadowDiscriminator != null && !resourceShadowDiscriminator.isTombstone()) { + if (resource == null) { + throw new IllegalStateException("Null resource in "+this + (contextDesc == null ? "" : " in " +contextDesc)); + } + if (resourceShadowDiscriminator == null) { + throw new IllegalStateException("Null resource account type in "+this + (contextDesc == null ? "" : " in " +contextDesc)); + } + } + if (syncDelta != null) { + try { + syncDelta.checkConsistence(true, true, true, ConsistencyCheckScope.THOROUGH); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); + } + } + } + + @Override + protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { + super.checkConsistence(object, elementDesc, contextDesc); + ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(object); + if (attributesContainer != null) { + ResourceType resource = getResource(); + if (resource != null) { + String resourceNamespace = ResourceTypeUtil.getResourceNamespace(resource); + for(ResourceAttribute attribute: attributesContainer.getAttributes()) { + QName attrName = attribute.getElementName(); + if (SchemaConstants.NS_ICF_SCHEMA.equals(attrName.getNamespaceURI())) { + continue; + } + if (resourceNamespace.equals(attrName.getNamespaceURI())) { + continue; + } + String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); + throw new IllegalStateException("Invalid namespace for attribute "+attrName+" in "+desc); + } + } + } + } + + protected boolean isRequireSecondaryDeltaOid() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD || + synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN || + synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { + return false; + } + if (getResourceShadowDiscriminator() != null && getResourceShadowDiscriminator().getOrder() > 0) { + // These may not have the OID yet + return false; + } + return super.isRequireSecondaryDeltaOid(); + } + + @Override + public void cleanup() { + secondaryDelta = null; + resetSynchronizationPolicyDecision(); +// isLegal = null; +// isLegalOld = null; + isAssigned = false; + isAssignedOld = false; // ??? [med] + isActive = false; + } + + @Override + public void normalize() { + super.normalize(); + if (secondaryDelta != null) { + secondaryDelta.normalize(); + } + if (syncDelta != null) { + syncDelta.normalize(); + } + } + +// @Override +// public void reset() { +// super.reset(); +// wave = -1; +// fullShadow = false; +// isAssigned = false; +// isAssignedOld = false; +// isActive = false; +// resetSynchronizationPolicyDecision(); +// constructionDeltaSetTriple = null; +// outboundConstruction = null; +// dependencies = null; +// squeezedAttributes = null; +// accountPasswordPolicy = null; +// } + + protected void resetSynchronizationPolicyDecision() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) { + toBeArchived = true; + } else if (synchronizationPolicyDecision != null) { + toBeArchived = false; + } + synchronizationPolicyDecision = null; + } + + @Override + public void adopt(PrismContext prismContext) throws SchemaException { + super.adopt(prismContext); + if (syncDelta != null) { + prismContext.adopt(syncDelta); + } + if (secondaryDelta != null) { + prismContext.adopt(secondaryDelta); + } + } + + @Override + public LensProjectionContext clone(LensContext lensContext) { + LensProjectionContext clone = new LensProjectionContext(lensContext, resourceShadowDiscriminator); + copyValues(clone, lensContext); + return clone; + } + + protected void copyValues(LensProjectionContext clone, LensContext lensContext) { + super.copyValues(clone, lensContext); + // do NOT clone transient values such as accountConstructionDeltaSetTriple + // these are not meant to be cloned and they are also not directly cloneable + clone.dependencies = this.dependencies; + clone.doReconciliation = this.doReconciliation; + clone.fullShadow = this.fullShadow; + clone.isAssigned = this.isAssigned; + clone.isAssignedOld = this.isAssignedOld; + clone.outboundConstruction = this.outboundConstruction; + clone.synchronizationPolicyDecision = this.synchronizationPolicyDecision; + clone.resource = this.resource; + clone.resourceShadowDiscriminator = this.resourceShadowDiscriminator; + clone.squeezedAttributes = cloneSqueezedAttributes(); + if (this.syncDelta != null) { + clone.syncDelta = this.syncDelta.clone(); + } + clone.secondaryDelta = cloneDelta(this.secondaryDelta); + clone.wave = this.wave; + clone.synchronizationSource = this.synchronizationSource; + } + + private Map,PrismPropertyDefinition>>> cloneSqueezedAttributes() { + if (squeezedAttributes == null) { + return null; + } + Map,PrismPropertyDefinition>>> clonedMap = new HashMap<>(); + for (Entry,PrismPropertyDefinition>>> entry: squeezedAttributes.entrySet()) { + clonedMap.put(entry.getKey(), entry.getValue().clone(ItemValueWithOrigin::clone)); + } + return clonedMap; + } + + /** + * Returns true if the projection has any value for specified attribute. + */ + public boolean hasValueForAttribute(QName attributeName) { + ItemPath attrPath = ItemPath.create(ShadowType.F_ATTRIBUTES, attributeName); + if (getObjectNew() != null) { + PrismProperty attrNew = getObjectNew().findProperty(attrPath); + if (attrNew != null && !attrNew.isEmpty()) { + return true; + } + } + return false; + } + + private boolean hasValueForAttribute(QName attributeName, Collection> acPpvSet) { + if (acPpvSet == null) { + return false; + } + for (PrismPropertyValue acPpv: acPpvSet) { + Construction ac = acPpv.getValue(); + if (ac.hasValueForAttribute(attributeName)) { + return true; + } + } + return false; + } + + @Override + public void checkEncrypted() { + super.checkEncrypted(); + if (syncDelta != null) { + CryptoUtil.checkEncrypted(syncDelta); + } + if (secondaryDelta != null) { + CryptoUtil.checkEncrypted(secondaryDelta); + } + } + + @Override + public String getHumanReadableName() { + if (humanReadableName == null) { + StringBuilder sb = new StringBuilder(); + sb.append("account("); + String humanReadableAccountIdentifier = getHumanReadableIdentifier(); + if (StringUtils.isEmpty(humanReadableAccountIdentifier)) { + sb.append("no ID"); + } else { + sb.append("ID "); + sb.append(humanReadableAccountIdentifier); + } + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr != null) { + sb.append(", type '"); + sb.append(discr.getIntent()); + sb.append("', "); + if (discr.getOrder() != 0) { + sb.append("order ").append(discr.getOrder()).append(", "); + } + } else { + sb.append(" (no discriminator) "); + } + sb.append(getResource()); + sb.append(")"); + humanReadableName = sb.toString(); + } + return humanReadableName; + } + + private String getHumanReadableIdentifier() { + PrismObject object = getObjectNew(); + if (object == null) { + object = getObjectOld(); + } + if (object == null) { + object = getObjectCurrent(); + } + if (object == null) { + return null; + } + if (object.canRepresent(ShadowType.class)) { + PrismObject shadow = (PrismObject)object; + Collection> identifiers = ShadowUtil.getPrimaryIdentifiers(shadow); + if (identifiers == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + Iterator> iterator = identifiers.iterator(); + while (iterator.hasNext()) { + ResourceAttribute id = iterator.next(); + sb.append(id.toHumanReadableString()); + if (iterator.hasNext()) { + sb.append(","); + } + } + return sb.toString(); + } else { + return object.toString(); + } + } + + @Override + public String debugDump(int indent) { + return debugDump(indent, true); + } + + public String debugDump(int indent, boolean showTriples) { + StringBuilder sb = new StringBuilder(); + SchemaDebugUtil.indentDebugDump(sb, indent); + sb.append("PROJECTION "); + sb.append(getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()); + sb.append(" "); + sb.append(getResourceShadowDiscriminator()); + if (resource != null) { + sb.append(" : "); + sb.append(resource.getName().getOrig()); + } + sb.append("\n"); + SchemaDebugUtil.indentDebugDump(sb, indent + 1); + sb.append("OID: ").append(getOid()); + sb.append(", wave ").append(wave); + if (fullShadow) { + sb.append(", full"); + } else { + sb.append(", shadow"); + } + sb.append(", exists=").append(isExists); + if (!shadowExistsInRepo) { + sb.append(" (shadow not in repo)"); + } + sb.append(", assigned=").append(isAssignedOld).append("->").append(isAssigned); + sb.append(", active=").append(isActive); + sb.append(", legal=").append(isLegalOld).append("->").append(isLegal); + sb.append(", recon=").append(doReconciliation); + sb.append(", canProject=").append(canProject); + sb.append(", syncIntent=").append(getSynchronizationIntent()); + sb.append(", decision=").append(synchronizationPolicyDecision); + if (!isFresh()) { + sb.append(", NOT FRESH"); + } + if (resourceShadowDiscriminator != null && resourceShadowDiscriminator.isTombstone()) { + sb.append(", TOMBSTONE"); + } + if (syncAbsoluteTrigger) { + sb.append(", SYNC TRIGGER"); + } + if (getIteration() != 0) { + sb.append(", iteration=").append(getIteration()).append(" (").append(getIterationToken()).append(")"); + } + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("old"), getObjectOld(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("current"), getObjectCurrent(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("new"), getObjectNew(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("primary delta"), getPrimaryDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("secondary delta"), getSecondaryDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("sync delta"), getSyncDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("executed deltas"), getExecutedDeltas(), indent+1); + + if (showTriples) { + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("constructionDeltaSetTriple"), constructionDeltaSetTriple, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("outbound account construction"), outboundConstruction, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed attributes"), squeezedAttributes, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed associations"), squeezedAssociations, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed auxiliary object classes"), squeezedAuxiliaryObjectClasses, indent + 1); + + // This is just a debug thing +// sb.append("\n"); +// DebugUtil.indentDebugDump(sb, indent); +// sb.append("ACCOUNT dependencies\n"); +// sb.append(DebugUtil.debugDump(dependencies, indent + 1)); + } + + return sb.toString(); + } + + @Override + protected String getElementDefaultDesc() { + return "projection"; + } + + @Override + public String toString() { + return "LensProjectionContext(" + (getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()) + ":" + getOid() + + ( resource == null ? "" : " on " + resource ) + ")"; + } + + /** + * Return a human readable name of the projection object suitable for logs. + */ + public String toHumanReadableString() { + if (humanReadableString == null) { + if (resourceShadowDiscriminator == null) { + humanReadableString = "(null" + resource + ")"; + } else if (resource != null) { + humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resource + ")"; + } else { + humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resourceShadowDiscriminator.getResourceOid() + ")"; + } + } + return humanReadableString; + } + + public String getHumanReadableKind() { + if (resourceShadowDiscriminator == null) { + return "resource object"; + } + return getKindValue(resourceShadowDiscriminator.getKind()); + } + + private String getKindValue(ShadowKindType kind) { + if (kind == null) { + return "null"; + } + return kind.value(); + } + + @Override + protected String getElementDesc() { + if (resourceShadowDiscriminator == null) { + return "shadow"; + } + return getKindValue(resourceShadowDiscriminator.getKind()); + } + + void addToPrismContainer(PrismContainer lensProjectionContextTypeContainer, LensContext.ExportType exportType) throws SchemaException { + LensProjectionContextType lensProjectionContextType = lensProjectionContextTypeContainer.createNewValue().asContainerable(); + super.storeIntoLensElementContextType(lensProjectionContextType, exportType); + lensProjectionContextType.setWave(wave); + lensProjectionContextType.setResourceShadowDiscriminator(resourceShadowDiscriminator != null ? + resourceShadowDiscriminator.toResourceShadowDiscriminatorType() : null); + lensProjectionContextType.setFullShadow(fullShadow); + lensProjectionContextType.setIsExists(isExists); + lensProjectionContextType.setSynchronizationPolicyDecision(synchronizationPolicyDecision != null ? synchronizationPolicyDecision.toSynchronizationPolicyDecisionType() : null); + lensProjectionContextType.setDoReconciliation(doReconciliation); + lensProjectionContextType.setSynchronizationSituationDetected(synchronizationSituationDetected); + lensProjectionContextType.setSynchronizationSituationResolved(synchronizationSituationResolved); + if (exportType != LensContext.ExportType.MINIMAL) { + lensProjectionContextType.setSyncDelta(syncDelta != null ? DeltaConvertor.toObjectDeltaType(syncDelta) : null); + lensProjectionContextType + .setSecondaryDelta(secondaryDelta != null ? DeltaConvertor.toObjectDeltaType(secondaryDelta) : null); + lensProjectionContextType.setIsAssigned(isAssigned); + lensProjectionContextType.setIsAssignedOld(isAssignedOld); + lensProjectionContextType.setIsActive(isActive); + lensProjectionContextType.setIsLegal(isLegal); + lensProjectionContextType.setIsLegalOld(isLegalOld); + if (exportType != LensContext.ExportType.REDUCED && projectionSecurityPolicy != null) { + ObjectReferenceType secRef = new ObjectReferenceType(); + secRef.asReferenceValue().setObject(projectionSecurityPolicy.asPrismObject()); + lensProjectionContextType.setProjectionSecurityPolicyRef(secRef); + } + lensProjectionContextType.setSyncAbsoluteTrigger(syncAbsoluteTrigger); + } + } + + public static LensProjectionContext fromLensProjectionContextType(LensProjectionContextType projectionContextType, LensContext lensContext, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { + + String objectTypeClassString = projectionContextType.getObjectTypeClass(); + if (StringUtils.isEmpty(objectTypeClassString)) { + throw new SystemException("Object type class is undefined in LensProjectionContextType"); + } + ResourceShadowDiscriminator resourceShadowDiscriminator = ResourceShadowDiscriminator.fromResourceShadowDiscriminatorType( + projectionContextType.getResourceShadowDiscriminator(), false); + + LensProjectionContext projectionContext = new LensProjectionContext(lensContext, resourceShadowDiscriminator); + + projectionContext.retrieveFromLensElementContextType(projectionContextType, task, result); + if (projectionContextType.getSyncDelta() != null) { + projectionContext.syncDelta = DeltaConvertor.createObjectDelta(projectionContextType.getSyncDelta(), lensContext.getPrismContext()); + } else { + projectionContext.syncDelta = null; + } + ObjectDeltaType secondaryDeltaType = projectionContextType.getSecondaryDelta(); + projectionContext.secondaryDelta = secondaryDeltaType != null ? + DeltaConvertor.createObjectDelta(secondaryDeltaType, lensContext.getPrismContext()) : null; + ObjectType object = projectionContextType.getObjectNew() != null ? projectionContextType.getObjectNew() : projectionContextType.getObjectOld(); + projectionContext.fixProvisioningTypeInDelta(projectionContext.secondaryDelta, object, task, result); + + projectionContext.wave = projectionContextType.getWave() != null ? projectionContextType.getWave() : 0; + projectionContext.fullShadow = projectionContextType.isFullShadow() != null ? projectionContextType.isFullShadow() : false; + projectionContext.isAssigned = projectionContextType.isIsAssigned() != null ? projectionContextType.isIsAssigned() : false; + projectionContext.isAssignedOld = projectionContextType.isIsAssignedOld() != null ? projectionContextType.isIsAssignedOld() : false; + projectionContext.isActive = projectionContextType.isIsActive() != null ? projectionContextType.isIsActive() : false; + projectionContext.isLegal = projectionContextType.isIsLegal(); + projectionContext.isLegalOld = projectionContextType.isIsLegalOld(); + projectionContext.isExists = projectionContextType.isIsExists() != null ? projectionContextType.isIsExists() : false; + projectionContext.synchronizationPolicyDecision = SynchronizationPolicyDecision.fromSynchronizationPolicyDecisionType(projectionContextType.getSynchronizationPolicyDecision()); + projectionContext.doReconciliation = projectionContextType.isDoReconciliation() != null ? projectionContextType.isDoReconciliation() : false; + projectionContext.synchronizationSituationDetected = projectionContextType.getSynchronizationSituationDetected(); + projectionContext.synchronizationSituationResolved = projectionContextType.getSynchronizationSituationResolved(); + ObjectReferenceType projectionSecurityPolicyRef = projectionContextType.getProjectionSecurityPolicyRef(); + if (projectionSecurityPolicyRef != null) { + projectionContext.projectionSecurityPolicy = (SecurityPolicyType) projectionSecurityPolicyRef.getObjectable(); + } + projectionContext.syncAbsoluteTrigger = projectionContextType.isSyncAbsoluteTrigger(); + + return projectionContext; + } + + // determines whether full shadow is present, based on operation result got from provisioning + public void determineFullShadowFlag(PrismObject loadedShadow) { + ShadowType shadowType = loadedShadow.asObjectable(); + if (ShadowUtil.isDead(shadowType) || !ShadowUtil.isExists(shadowType)) { + setFullShadow(false); + return; + } + OperationResultType fetchResult = shadowType.getFetchResult(); + if (fetchResult != null + && (fetchResult.getStatus() == OperationResultStatusType.PARTIAL_ERROR + || fetchResult.getStatus() == OperationResultStatusType.FATAL_ERROR)) { // todo what about other kinds of status? [e.g. in-progress] + setFullShadow(false); + } else { + setFullShadow(true); + } + } + + public boolean isToBeArchived() { + return toBeArchived; + } + + public void setToBeArchived(boolean toBeArchived) { + this.toBeArchived = toBeArchived; + } + + public String getResourceOid() { + if (resource != null) { + return resource.getOid(); + } else if (resourceShadowDiscriminator != null) { + return resourceShadowDiscriminator.getResourceOid(); + } else { + return null; + } + } + + public ResourceObjectVolatilityType getVolatility() throws SchemaException { + RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); + if (structuralObjectClassDefinition == null) { + return null; + } + return structuralObjectClassDefinition.getVolatility(); + } + + public boolean hasPendingOperations() { + PrismObject current = getObjectCurrent(); + if (current == null) { + return false; + } + return !current.asObjectable().getPendingOperation().isEmpty(); + } + + @Override + public void forEachDelta(Consumer> consumer) { + super.forEachDelta(consumer); + if (secondaryDelta != null) { + consumer.accept(secondaryDelta); + } + } + + PolyString resolveNameIfKnown(Class objectClass, String oid) { + if (ResourceType.class.equals(objectClass)) { + if (resource != null && oid.equals(resource.getOid())) { + return PolyString.toPolyString(resource.getName()); + } + } else if (ShadowType.class.equals(objectClass)) { + PrismObject object = getObjectAny(); + if (object != null && oid.equals(object.getOid())) { + if (object.getName() != null) { + return object.getName(); + } else { + try { + return ShadowUtil.determineShadowName(object); + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine shadow name for {}", e, object); + return null; + } + } + } + } + return null; + } + + public String getResourceName() { + ResourceType resource = getResource(); + return resource != null ? PolyString.getOrig(resource.getName()) : getResourceOid(); + } + + public boolean isSynchronizationSource() { + return synchronizationSource; + } + + public void setSynchronizationSource(boolean synchronizationSource) { + this.synchronizationSource = synchronizationSource; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 44d33931aa5..02e25894415 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -41,8 +41,8 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ItemDelta; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java index d3d18602559..cebb9b5f278 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java @@ -1,944 +1,943 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens.projector; - -import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.Source; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.api.expr.MidpointFunctions; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.impl.lens.SynchronizationIntent; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.delta.PropertyDelta; -import com.evolveum.midpoint.prism.path.UniformItemPath; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.schema.CapabilityUtil; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -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.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.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceActivationDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceBidirectionalMappingType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectLifecycleDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectTypeDefinitionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -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.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.ActivationValidityCapabilityType; - -import org.apache.commons.lang.mutable.MutableBoolean; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -/** - * The processor that takes care of user activation mapping to an account (outbound direction). - * - * @author Radovan Semancik - */ -@Component -public class ActivationProcessor { - - private static final Trace LOGGER = TraceManager.getTrace(ActivationProcessor.class); - - private static final QName SHADOW_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "shadowExists"); - private static final QName LEGAL_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "legal"); - private static final QName ASSIGNED_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "assigned"); - private static final QName FOCUS_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "focusExists"); - - @Autowired private ContextLoader contextLoader; - @Autowired private PrismContext prismContext; - @Autowired private MappingEvaluator mappingEvaluator; - @Autowired private MidpointFunctions midpointFunctions; - - private PrismObjectDefinition userDefinition; - private PrismContainerDefinition activationDefinition; - - public void processActivation(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal object. - return; - } - - processActivationFocal((LensContext)context, projectionContext, now, task, result); - } - - private void processActivationFocal(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - processActivationMetadata(context, projectionContext, now, result); - return; - } - try { - - processActivationUserCurrent(context, projectionContext, now, task, result); - processActivationMetadata(context, projectionContext, now, result); - processActivationUserFuture(context, projectionContext, now, task, result); - - } catch (ObjectNotFoundException e) { - if (projectionContext.isTombstone()) { - // This is not critical. The projection is marked as thombstone and we can go on with processing - // No extra action is needed. - } else { - throw e; - } - } - } - - public void processActivationUserCurrent(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - String projCtxDesc = projCtx.toHumanReadableString(); - SynchronizationPolicyDecision decision = projCtx.getSynchronizationPolicyDecision(); - SynchronizationIntent synchronizationIntent = projCtx.getSynchronizationIntent(); - - if (decision == SynchronizationPolicyDecision.BROKEN) { - LOGGER.trace("Broken projection {}, skipping further activation processing", projCtxDesc); - return; - } - if (decision != null) { - throw new IllegalStateException("Decision "+decision+" already present for projection "+projCtxDesc); - } - - if (synchronizationIntent == SynchronizationIntent.UNLINK) { - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); - LOGGER.trace("Evaluated decision for {} to {} because of unlink synchronization intent, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.UNLINK); - return; - } - - if (projCtx.isTombstone()) { - if (projCtx.isDelete() && ModelExecuteOptions.isForce(context.getOptions())) { - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); - LOGGER.trace("Evaluated decision for tombstone {} to {} (force), skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); - return; - } else { - // Let's keep thombstones linked until they expire. So we do not have shadows without owners. - // This is also needed for async delete operations. - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.KEEP); - LOGGER.trace("Evaluated decision for {} to {} because it is tombstone, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.KEEP); - return; - } - } - - if (synchronizationIntent == SynchronizationIntent.DELETE || projCtx.isDelete()) { - // TODO: is this OK? - projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); - LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); - return; - } - - LOGGER.trace("Evaluating intended existence of projection {} (legal={})", projCtxDesc, projCtx.isLegal()); - - boolean shadowShouldExist = evaluateExistenceMapping(context, projCtx, now, MappingTimeEval.CURRENT, task, result); - - LOGGER.trace("Evaluated intended existence of projection {} to {} (legal={})", projCtxDesc, shadowShouldExist, projCtx.isLegal()); - - // Let's reconcile the existence intent (shadowShouldExist) and the synchronization intent in the context - - LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, projCtx); - - if (synchronizationIntent == null || synchronizationIntent == SynchronizationIntent.SYNCHRONIZE) { - if (shadowShouldExist) { - projCtx.setActive(true); - if (projCtx.isExists()) { - if (lowerOrderContext != null && lowerOrderContext.isDelete()) { - // HACK HACK HACK - decision = SynchronizationPolicyDecision.DELETE; - } else { - decision = SynchronizationPolicyDecision.KEEP; - } - } else { - if (lowerOrderContext != null) { - if (lowerOrderContext.isDelete()) { - // HACK HACK HACK - decision = SynchronizationPolicyDecision.DELETE; - } else { - // If there is a lower-order context then that one will be ADD - // and this one is KEEP. When the execution comes to this context - // then the projection already exists - decision = SynchronizationPolicyDecision.KEEP; - } - } else { - decision = SynchronizationPolicyDecision.ADD; - } - } - } else { - // Delete - if (projCtx.isExists()) { - decision = SynchronizationPolicyDecision.DELETE; - } else { - // we should delete the entire context, but then we will lost track of what - // happened. So just ignore it. - decision = SynchronizationPolicyDecision.IGNORE; - // if there are any triggers then move them to focus. We may still need them. - LensUtil.moveTriggers(projCtx, context.getFocusContext()); - } - } - - } else if (synchronizationIntent == SynchronizationIntent.ADD) { - if (shadowShouldExist) { - projCtx.setActive(true); - if (projCtx.isExists()) { - // Attempt to add something that is already there, but should be OK - decision = SynchronizationPolicyDecision.KEEP; - } else { - decision = SynchronizationPolicyDecision.ADD; - } - } else { - throw new PolicyViolationException("Request to add projection "+projCtxDesc+" but the activation policy decided that it should not exist"); - } - - } else if (synchronizationIntent == SynchronizationIntent.KEEP) { - if (shadowShouldExist) { - projCtx.setActive(true); - if (projCtx.isExists()) { - decision = SynchronizationPolicyDecision.KEEP; - } else { - decision = SynchronizationPolicyDecision.ADD; - } - } else { - throw new PolicyViolationException("Request to keep projection "+projCtxDesc+" but the activation policy decided that it should not exist"); - } - - } else { - throw new IllegalStateException("Unknown sync intent "+synchronizationIntent); - } - - LOGGER.trace("Evaluated decision for projection {} to {}", projCtxDesc, decision); - - projCtx.setSynchronizationPolicyDecision(decision); - - PrismObject focusNew = context.getFocusContext().getObjectNew(); - if (focusNew == null) { - // This must be a user delete or something similar. No point in proceeding - LOGGER.trace("focusNew is null, skipping activation processing of {}", projCtxDesc); - return; - } - - if (decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { - LOGGER.trace("Decision is {}, skipping activation properties processing for {}", decision, projCtxDesc); - return; - } - - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType == null) { - LOGGER.trace("No refined object definition, therefore also no activation outbound definition, skipping activation processing for account " + projCtxDesc); - return; - } - ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); - if (activationType == null) { - LOGGER.trace("No activation definition in projection {}, skipping activation properties processing", projCtxDesc); - return; - } - - ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), ActivationCapabilityType.class); - if (capActivation == null) { - LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", projCtx.getResource()); - return; - } - - ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); - ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); - ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); - ActivationLockoutStatusCapabilityType capLockoutStatus = CapabilityUtil.getEffectiveActivationLockoutStatus(capActivation); - - if (capStatus != null) { - evaluateActivationMapping(context, projCtx, - activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, - SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, - capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); - } else { - LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", projCtx.getResource()); - } - - ResourceBidirectionalMappingType validFromMappingType = activationType.getValidFrom(); - if (validFromMappingType == null || validFromMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", projCtx.getResource()); - } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validFromMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", projCtx.getResource()); - } else { - evaluateActivationMapping(context, projCtx, activationType.getValidFrom(), - SchemaConstants.PATH_ACTIVATION_VALID_FROM, - SchemaConstants.PATH_ACTIVATION_VALID_FROM, - null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_FROM.getLocalPart(), task, result); - } - - ResourceBidirectionalMappingType validToMappingType = activationType.getValidTo(); - if (validToMappingType == null || validToMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", projCtx.getResource()); - } else if (capValidTo == null && !ExpressionUtil.hasExplicitTarget(validToMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", projCtx.getResource()); - } else { - evaluateActivationMapping(context, projCtx, activationType.getValidTo(), - SchemaConstants.PATH_ACTIVATION_VALID_TO, - SchemaConstants.PATH_ACTIVATION_VALID_TO, - null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_TO.getLocalPart(), task, result); - } - - if (capLockoutStatus != null) { - evaluateActivationMapping(context, projCtx, - activationType.getLockoutStatus(), - SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, - capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_LOCKOUT_STATUS.getLocalPart(), task, result); - } else { - LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", projCtx.getResource()); - } - - } - - public void processActivationMetadata(LensContext context, LensProjectionContext accCtx, - XMLGregorianCalendar now, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - ObjectDelta projDelta = accCtx.getDelta(); - if (projDelta == null) { - return; - } - - PropertyDelta statusDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS); - - if (statusDelta != null && !statusDelta.isDelete()) { - - // we have to determine if the status really changed - PrismObject oldShadow = accCtx.getObjectOld(); - ActivationStatusType statusOld = null; - if (oldShadow != null && oldShadow.asObjectable().getActivation() != null) { - statusOld = oldShadow.asObjectable().getActivation().getAdministrativeStatus(); - } - - PrismProperty statusPropNew = (PrismProperty) statusDelta.getItemNewMatchingPath(null); - ActivationStatusType statusNew = statusPropNew.getRealValue(); - - if (statusNew == statusOld) { - LOGGER.trace("Administrative status not changed ({}), timestamp and/or reason will not be recorded", statusNew); - } else { - // timestamps - PropertyDelta timestampDelta = LensUtil.createActivationTimestampDelta(statusNew, - now, getActivationDefinition(), OriginType.OUTBOUND, prismContext); - accCtx.swallowToSecondaryDelta(timestampDelta); - - // disableReason - if (statusNew == ActivationStatusType.DISABLED) { - PropertyDelta disableReasonDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_DISABLE_REASON); - if (disableReasonDelta == null) { - String disableReason = null; - ObjectDelta projPrimaryDelta = accCtx.getPrimaryDelta(); - ObjectDelta projSecondaryDelta = accCtx.getSecondaryDelta(); - if (projPrimaryDelta != null - && projPrimaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) != null - && (projSecondaryDelta == null || projSecondaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) == null)) { - disableReason = SchemaConstants.MODEL_DISABLE_REASON_EXPLICIT; - } else if (accCtx.isLegal()) { - disableReason = SchemaConstants.MODEL_DISABLE_REASON_MAPPED; - } else { - disableReason = SchemaConstants.MODEL_DISABLE_REASON_DEPROVISION; - } - - PrismPropertyDefinition disableReasonDef = activationDefinition.findPropertyDefinition(ActivationType.F_DISABLE_REASON); - disableReasonDelta = disableReasonDef.createEmptyDelta( - ItemPath.create(FocusType.F_ACTIVATION, ActivationType.F_DISABLE_REASON)); - disableReasonDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(disableReason, OriginType.OUTBOUND, null)); - accCtx.swallowToSecondaryDelta(disableReasonDelta); - } - } - } - } - - } - - public void processActivationUserFuture(LensContext context, LensProjectionContext accCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - String accCtxDesc = accCtx.toHumanReadableString(); - SynchronizationPolicyDecision decision = accCtx.getSynchronizationPolicyDecision(); - SynchronizationIntent synchronizationIntent = accCtx.getSynchronizationIntent(); - - if (accCtx.isTombstone() || decision == SynchronizationPolicyDecision.BROKEN - || decision == SynchronizationPolicyDecision.IGNORE - || decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { - return; - } - - accCtx.recompute(); - - evaluateExistenceMapping(context, accCtx, now, MappingTimeEval.FUTURE, task, result); - - PrismObject focusNew = context.getFocusContext().getObjectNew(); - if (focusNew == null) { - // This must be a user delete or something similar. No point in proceeding - LOGGER.trace("focusNew is null, skipping activation processing of {}", accCtxDesc); - return; - } - - ResourceObjectTypeDefinitionType resourceAccountDefType = accCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType == null) { - return; - } - ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); - if (activationType == null) { - return; - } - - ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(accCtx.getResource(), ActivationCapabilityType.class); - if (capActivation == null) { - return; - } - - ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); - ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); - ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); - - if (capStatus != null) { - - evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, - capActivation, now, MappingTimeEval.FUTURE, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); - } - - if (capValidFrom != null) { - evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_VALID_FROM, SchemaConstants.PATH_ACTIVATION_VALID_FROM, - null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); - } - - if (capValidTo != null) { - evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), - SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, - null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); - } - - } - - - private boolean evaluateExistenceMapping(final LensContext context, - final LensProjectionContext projCtx, final XMLGregorianCalendar now, final MappingTimeEval current, - Task task, final OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - final String projCtxDesc = projCtx.toHumanReadableString(); - - final Boolean legal = projCtx.isLegal(); - if (legal == null) { - throw new IllegalStateException("Null 'legal' for "+projCtxDesc); - } - - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType == null) { - return legal; - } - ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); - if (activationType == null) { - return legal; - } - ResourceBidirectionalMappingType existenceType = activationType.getExistence(); - if (existenceType == null) { - return legal; - } - List outbound = existenceType.getOutbound(); - if (outbound == null || outbound.isEmpty()) { - // "default mapping" - return legal; - } - - MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); - params.setMappingTypes(outbound); - params.setMappingDesc("outbound existence mapping in projection " + projCtxDesc); - params.setNow(now); - params.setAPrioriTargetObject(projCtx.getObjectOld()); - params.setEvaluateCurrent(current); - params.setTargetContext(projCtx); - params.setFixTarget(true); - params.setContext(context); - - params.setInitializer(builder -> { - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(projCtx); - Source,PrismPropertyDefinition> legalSource - = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL_QNAME); - builder.defaultSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); - builder.addSource(focusExistsSource); - - // Variable: focus - builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); - - // Variable: user (for convenience, same as "focus"), DEPRECATED - builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); - builder.addAliasRegistration(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS); - - // Variable: projection - // This may be tricky when creation of a new projection is considered. - // In that case we do not have any projection object (account) yet, neither new nor old. But we already have - // projection context. We have to pass projection definition explicitly here. - builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); - builder.addVariableDefinition(ExpressionConstants.VAR_PROJECTION, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); - builder.addAliasRegistration(ExpressionConstants.VAR_SHADOW, ExpressionConstants.VAR_PROJECTION); - - // Variable: resource - builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, projCtx.getResource(), ResourceType.class); - - builder.originType(OriginType.OUTBOUND); - builder.originObject(projCtx.getResource()); - return builder; - }); - - PrismValueDeltaSetTriple> aggregatedOutputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple(); - - params.setProcessor((mappingOutputPath, outputStruct) -> { - // This is a very primitive implementation of output processing. - // Maybe we should somehow use the default processing in MappingEvaluator, but it's quite complex - // and therefore we should perhaps wait for general mapping cleanup (MID-3847). - PrismValueDeltaSetTriple> outputTriple = outputStruct.getOutputTriple(); - if (outputTriple != null) { - aggregatedOutputTriple.merge(outputTriple); - } - return false; - }); - - MutablePrismPropertyDefinition shadowExistenceTargetDef = prismContext.definitionFactory().createPropertyDefinition(SHADOW_EXISTS_PROPERTY_NAME, DOMUtil.XSD_BOOLEAN); - shadowExistenceTargetDef.setMinOccurs(1); - shadowExistenceTargetDef.setMaxOccurs(1); - params.setTargetItemDefinition(shadowExistenceTargetDef); - mappingEvaluator.evaluateMappingSetProjection(params, task, result); - - boolean output; - if (aggregatedOutputTriple.isEmpty()) { - output = legal; // the default - } else { - Collection> nonNegativeValues = aggregatedOutputTriple.getNonNegativeValues(); - if (nonNegativeValues.isEmpty()) { - throw new ExpressionEvaluationException("Activation existence expression resulted in no values for projection " + projCtxDesc); - } else if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + projCtxDesc + ": " + nonNegativeValues); - } else { - PrismPropertyValue value = nonNegativeValues.iterator().next(); - if (value != null && value.getRealValue() != null) { - output = value.getRealValue(); - } else { - // TODO could this even occur? - throw new ExpressionEvaluationException("Activation existence expression resulted in null value for projection " + projCtxDesc); - } - } - } - - return output; - } - - private void evaluateActivationMapping(final LensContext context, - final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, - final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, - final ActivationCapabilityType capActivation, XMLGregorianCalendar now, final MappingTimeEval current, - String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - MappingInitializer,PrismPropertyDefinition> initializer = - builder -> { - // Source: administrativeStatus, validFrom or validTo - ItemDeltaItem,PrismPropertyDefinition> sourceIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); - - if (capActivation != null && focusPropertyPath.equivalent(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS)) { - ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); - ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); - - // Source: computedShadowStatus - ItemDeltaItem,PrismPropertyDefinition> computedIdi; - if (capValidFrom != null && capValidTo != null) { - // "Native" validFrom and validTo, directly use administrativeStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); - - } else { - // Simulate validFrom and validTo using effectiveStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS); - - } - - Source,PrismPropertyDefinition> computedSource = new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME); - - builder.defaultSource(computedSource); - - Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME); - builder.addSource(source); - - } else { - Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME); - builder.defaultSource(source); - } - - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalIdi = getLegalIdi(projCtx); - Source,PrismPropertyDefinition> legalSource = new Source<>(legalIdi, ExpressionConstants.VAR_LEGAL_QNAME); - builder.addSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); - builder.addSource(focusExistsSource); - - return builder; - }; - - evaluateOutboundMapping(context, projCtx, bidirectionalMappingType, focusPropertyPath, projectionPropertyPath, initializer, - now, current, desc + " outbound activation mapping", task, result); - - } - - private void evaluateOutboundMapping(final LensContext context, - final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, - final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, - final MappingInitializer,PrismPropertyDefinition> initializer, - XMLGregorianCalendar now, final MappingTimeEval evaluateCurrent, String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (bidirectionalMappingType == null) { - LOGGER.trace("No '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); - return; - } - List outboundMappingTypes = bidirectionalMappingType.getOutbound(); - if (outboundMappingTypes == null || outboundMappingTypes.isEmpty()) { - LOGGER.trace("No outbound definition in '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); - return; - } - - String projCtxDesc = projCtx.toHumanReadableString(); - PrismObject shadowNew = projCtx.getObjectNew(); - - MappingInitializer,PrismPropertyDefinition> internalInitializer = - builder -> { - - builder.addVariableDefinitions(ModelImplUtils.getDefaultExpressionVariables(context, projCtx)); - - builder.originType(OriginType.OUTBOUND); - builder.originObject(projCtx.getResource()); - - initializer.initialize(builder); - - return builder; - }; - - MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); - params.setMappingTypes(outboundMappingTypes); - params.setMappingDesc(desc + " in projection " + projCtxDesc); - params.setNow(now); - params.setInitializer(internalInitializer); - params.setTargetLoader(new ProjectionMappingLoader<>(context, projCtx, contextLoader)); - params.setAPrioriTargetObject(shadowNew); - params.setAPrioriTargetDelta(LensUtil.findAPrioriDelta(context, projCtx)); - if (context.getFocusContext() != null) { - params.setSourceContext(context.getFocusContext().getObjectDeltaObject()); - } - params.setTargetContext(projCtx); - params.setDefaultTargetItemPath(projectionPropertyPath); - params.setEvaluateCurrent(evaluateCurrent); - params.setEvaluateWeak(true); - params.setContext(context); - params.setHasFullTargetObject(projCtx.hasFullShadow()); - - Map>> outputTripleMap = mappingEvaluator.evaluateMappingSetProjection(params, task, result); - - LOGGER.trace("Mapping processing output after {} ({}):\n{}", desc, evaluateCurrent, - DebugUtil.debugDumpLazily(outputTripleMap, 1)); - - if (projCtx.isDoReconciliation()) { - reconcileOutboundValue(context, projCtx, outputTripleMap, desc); - } - - } - - /** - * TODO: can we align this with ReconciliationProcessor? - */ - private void reconcileOutboundValue(LensContext context, LensProjectionContext projCtx, - Map>> outputTripleMap, String desc) throws SchemaException { - - // TODO: check for full shadow? - - for (Entry>> entry: outputTripleMap.entrySet()) { - UniformItemPath mappingOutputPath = entry.getKey(); - MappingOutputStruct> mappingOutputStruct = entry.getValue(); - if (mappingOutputStruct.isWeakMappingWasUsed()) { - // Thing to do. All deltas should already be in context - LOGGER.trace("Skip reconciliation of {} in {} because of weak", mappingOutputPath, desc); - continue; - } - if (!mappingOutputStruct.isStrongMappingWasUsed()) { - // Normal mappings are not processed for reconciliation - LOGGER.trace("Skip reconciliation of {} in {} because not strong", mappingOutputPath, desc); - continue; - } - LOGGER.trace("reconciliation of {} for {}", mappingOutputPath, desc); - - PrismObjectDefinition targetObjectDefinition = projCtx.getObjectDefinition(); - PrismPropertyDefinition targetItemDefinition = targetObjectDefinition.findPropertyDefinition(mappingOutputPath); - if (targetItemDefinition == null) { - throw new SchemaException("No definition for item "+mappingOutputPath+" in "+targetObjectDefinition); - } - PropertyDelta targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath); - - PrismValueDeltaSetTriple> outputTriple = mappingOutputStruct.getOutputTriple(); - - PrismProperty currentTargetItem = null; - PrismObject shadowCurrent = projCtx.getObjectCurrent(); - if (shadowCurrent != null) { - currentTargetItem = shadowCurrent.findProperty(mappingOutputPath); - } - Collection> hasValues = new ArrayList<>(); - if (currentTargetItem != null) { - hasValues.addAll(currentTargetItem.getValues()); - } - - Collection> shouldHaveValues = outputTriple.getNonNegativeValues(); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Reconciliation of {}:\n hasValues:\n{}\n shouldHaveValues\n{}", - mappingOutputPath, DebugUtil.debugDump(hasValues, 2), DebugUtil.debugDump(shouldHaveValues, 2)); - } - - for (PrismPropertyValue shouldHaveValue: shouldHaveValues) { - if (!PrismValueCollectionsUtil.containsRealValue(hasValues, shouldHaveValue)) { - if (targetItemDefinition.isSingleValue()) { - targetItemDelta.setValueToReplace(shouldHaveValue.clone()); - } else { - targetItemDelta.addValueToAdd(shouldHaveValue.clone()); - } - } - } - - if (targetItemDefinition.isSingleValue()) { - if (!targetItemDelta.isReplace() && shouldHaveValues.isEmpty()) { - targetItemDelta.setValueToReplace(); - } - } else { - for (PrismPropertyValue hasValue: hasValues) { - if (!PrismValueCollectionsUtil.containsRealValue(shouldHaveValues, hasValue)) { - targetItemDelta.addValueToDelete(hasValue.clone()); - } - } - } - - if (!targetItemDelta.isEmpty()) { - LOGGER.trace("Reconciliation delta:\n{}", targetItemDelta.debugDumpLazily(1)); - projCtx.swallowToSecondaryDelta(targetItemDelta); - } - } - - } - - - - private ItemDeltaItem,PrismPropertyDefinition> getLegalIdi(LensProjectionContext accCtx) throws SchemaException { - Boolean legal = accCtx.isLegal(); - Boolean legalOld = accCtx.isLegalOld(); - return createBooleanIdi(LEGAL_PROPERTY_NAME, legalOld, legal); - } - - @NotNull - private ItemDeltaItem, PrismPropertyDefinition> createBooleanIdi( - QName propertyName, Boolean old, Boolean current) throws SchemaException { - MutablePrismPropertyDefinition definition = prismContext.definitionFactory().createPropertyDefinition(propertyName, DOMUtil.XSD_BOOLEAN); - definition.setMinOccurs(1); - definition.setMaxOccurs(1); - PrismProperty property = definition.instantiate(); - property.add(prismContext.itemFactory().createPropertyValue(current)); - - if (current == old) { - return new ItemDeltaItem<>(property); - } else { - PrismProperty propertyOld = property.clone(); - propertyOld.setRealValue(old); - PropertyDelta delta = propertyOld.createDelta(); - delta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(current)); - return new ItemDeltaItem<>(propertyOld, delta, property, definition); - } - } - - private ItemDeltaItem,PrismPropertyDefinition> getAssignedIdi(LensProjectionContext accCtx) throws SchemaException { - Boolean assigned = accCtx.isAssigned(); - Boolean assignedOld = accCtx.isAssignedOld(); - return createBooleanIdi(ASSIGNED_PROPERTY_NAME, assignedOld, assigned); - } - - private ItemDeltaItem,PrismPropertyDefinition> getFocusExistsIdi( - LensFocusContext lensFocusContext) throws SchemaException { - Boolean existsOld = null; - Boolean existsNew = null; - - if (lensFocusContext != null) { - if (lensFocusContext.isDelete()) { - existsOld = true; - existsNew = false; - } else if (lensFocusContext.isAdd()) { - existsOld = false; - existsNew = true; - } else { - existsOld = true; - existsNew = true; - } - } - - MutablePrismPropertyDefinition existsDef = prismContext.definitionFactory().createPropertyDefinition(FOCUS_EXISTS_PROPERTY_NAME, - DOMUtil.XSD_BOOLEAN); - existsDef.setMinOccurs(1); - existsDef.setMaxOccurs(1); - PrismProperty existsProp = existsDef.instantiate(); - - existsProp.add(prismContext.itemFactory().createPropertyValue(existsNew)); - - if (existsOld == existsNew) { - return new ItemDeltaItem<>(existsProp); - } else { - PrismProperty existsPropOld = existsProp.clone(); - existsPropOld.setRealValue(existsOld); - PropertyDelta existsDelta = existsPropOld.createDelta(); - existsDelta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(existsNew)); - return new ItemDeltaItem<>(existsPropOld, existsDelta, existsProp, existsDef); - } - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void processLifecycle(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal object. - LOGGER.trace("Skipping lifecycle evaluation because focus is not FocusType"); - return; - } - - processLifecycleFocus((LensContext)context, projCtx, now, task, result); - } - - private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no focus"); - return; - } - - ResourceObjectLifecycleDefinitionType lifecycleDef = null; - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType != null) { - lifecycleDef = resourceAccountDefType.getLifecycle(); - } - ResourceBidirectionalMappingType lifecycleStateMappingType = null; - if (lifecycleDef != null) { - lifecycleStateMappingType = lifecycleDef.getLifecycleState(); - } - - if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { - - if (!projCtx.isAdd()) { - LOGGER.trace("Skipping lifecycle evaluation because this is not add operation (default expression)"); - return; - } - - PrismObject focusNew = focusContext.getObjectNew(); - if (focusNew == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no new focus (default expression)"); - return; - } - - PrismObject projectionNew = projCtx.getObjectNew(); - if (projectionNew == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no new projection (default expression)"); - return; - } - - String lifecycle = midpointFunctions.computeProjectionLifecycle( - focusNew.asObjectable(), projectionNew.asObjectable(), projCtx.getResource()); - - LOGGER.trace("Computed projection lifecycle (default expression): {}", lifecycle); - - if (lifecycle != null) { - PrismPropertyDefinition propDef = projCtx.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_LIFECYCLE_STATE); - PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); - PrismPropertyValue pval = prismContext.itemFactory().createPropertyValue(lifecycle); - pval.setOriginType(OriginType.OUTBOUND); - lifeCycleDelta.setValuesToReplace(pval); - projCtx.swallowToSecondaryDelta(lifeCycleDelta); - } - - } else { - - LOGGER.trace("Computing projection lifecycle (mapping): {}", lifecycleStateMappingType); - evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, - SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, - null, now, MappingTimeEval.CURRENT, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); - } - - } - - private PrismObjectDefinition getUserDefinition() { - if (userDefinition == null) { - userDefinition = prismContext.getSchemaRegistry() - .findObjectDefinitionByCompileTimeClass(UserType.class); - } - return userDefinition; - } - - private PrismContainerDefinition getActivationDefinition() { - if (activationDefinition == null) { - PrismObjectDefinition userDefinition = getUserDefinition(); - activationDefinition = userDefinition.findContainerDefinition(UserType.F_ACTIVATION); - } - return activationDefinition; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens.projector; + +import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.Source; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.api.expr.MidpointFunctions; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.delta.PropertyDelta; +import com.evolveum.midpoint.prism.path.UniformItemPath; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.schema.CapabilityUtil; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +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.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.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceActivationDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceBidirectionalMappingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectLifecycleDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectTypeDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; +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.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.ActivationValidityCapabilityType; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * The processor that takes care of user activation mapping to an account (outbound direction). + * + * @author Radovan Semancik + */ +@Component +public class ActivationProcessor { + + private static final Trace LOGGER = TraceManager.getTrace(ActivationProcessor.class); + + private static final QName SHADOW_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "shadowExists"); + private static final QName LEGAL_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "legal"); + private static final QName ASSIGNED_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "assigned"); + private static final QName FOCUS_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "focusExists"); + + @Autowired private ContextLoader contextLoader; + @Autowired private PrismContext prismContext; + @Autowired private MappingEvaluator mappingEvaluator; + @Autowired private MidpointFunctions midpointFunctions; + + private PrismObjectDefinition userDefinition; + private PrismContainerDefinition activationDefinition; + + public void processActivation(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for focal object. + return; + } + + processActivationFocal((LensContext)context, projectionContext, now, task, result); + } + + private void processActivationFocal(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + processActivationMetadata(context, projectionContext, now, result); + return; + } + try { + + processActivationUserCurrent(context, projectionContext, now, task, result); + processActivationMetadata(context, projectionContext, now, result); + processActivationUserFuture(context, projectionContext, now, task, result); + + } catch (ObjectNotFoundException e) { + if (projectionContext.isTombstone()) { + // This is not critical. The projection is marked as thombstone and we can go on with processing + // No extra action is needed. + } else { + throw e; + } + } + } + + public void processActivationUserCurrent(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + String projCtxDesc = projCtx.toHumanReadableString(); + SynchronizationPolicyDecision decision = projCtx.getSynchronizationPolicyDecision(); + SynchronizationIntent synchronizationIntent = projCtx.getSynchronizationIntent(); + + if (decision == SynchronizationPolicyDecision.BROKEN) { + LOGGER.trace("Broken projection {}, skipping further activation processing", projCtxDesc); + return; + } + if (decision != null) { + throw new IllegalStateException("Decision "+decision+" already present for projection "+projCtxDesc); + } + + if (synchronizationIntent == SynchronizationIntent.UNLINK) { + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); + LOGGER.trace("Evaluated decision for {} to {} because of unlink synchronization intent, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.UNLINK); + return; + } + + if (projCtx.isTombstone()) { + if (projCtx.isDelete() && ModelExecuteOptions.isForce(context.getOptions())) { + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); + LOGGER.trace("Evaluated decision for tombstone {} to {} (force), skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); + return; + } else { + // Let's keep thombstones linked until they expire. So we do not have shadows without owners. + // This is also needed for async delete operations. + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.KEEP); + LOGGER.trace("Evaluated decision for {} to {} because it is tombstone, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.KEEP); + return; + } + } + + if (synchronizationIntent == SynchronizationIntent.DELETE || projCtx.isDelete()) { + // TODO: is this OK? + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); + LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", projCtxDesc, SynchronizationPolicyDecision.DELETE); + return; + } + + LOGGER.trace("Evaluating intended existence of projection {} (legal={})", projCtxDesc, projCtx.isLegal()); + + boolean shadowShouldExist = evaluateExistenceMapping(context, projCtx, now, MappingTimeEval.CURRENT, task, result); + + LOGGER.trace("Evaluated intended existence of projection {} to {} (legal={})", projCtxDesc, shadowShouldExist, projCtx.isLegal()); + + // Let's reconcile the existence intent (shadowShouldExist) and the synchronization intent in the context + + LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, projCtx); + + if (synchronizationIntent == null || synchronizationIntent == SynchronizationIntent.SYNCHRONIZE) { + if (shadowShouldExist) { + projCtx.setActive(true); + if (projCtx.isExists()) { + if (lowerOrderContext != null && lowerOrderContext.isDelete()) { + // HACK HACK HACK + decision = SynchronizationPolicyDecision.DELETE; + } else { + decision = SynchronizationPolicyDecision.KEEP; + } + } else { + if (lowerOrderContext != null) { + if (lowerOrderContext.isDelete()) { + // HACK HACK HACK + decision = SynchronizationPolicyDecision.DELETE; + } else { + // If there is a lower-order context then that one will be ADD + // and this one is KEEP. When the execution comes to this context + // then the projection already exists + decision = SynchronizationPolicyDecision.KEEP; + } + } else { + decision = SynchronizationPolicyDecision.ADD; + } + } + } else { + // Delete + if (projCtx.isExists()) { + decision = SynchronizationPolicyDecision.DELETE; + } else { + // we should delete the entire context, but then we will lost track of what + // happened. So just ignore it. + decision = SynchronizationPolicyDecision.IGNORE; + // if there are any triggers then move them to focus. We may still need them. + LensUtil.moveTriggers(projCtx, context.getFocusContext()); + } + } + + } else if (synchronizationIntent == SynchronizationIntent.ADD) { + if (shadowShouldExist) { + projCtx.setActive(true); + if (projCtx.isExists()) { + // Attempt to add something that is already there, but should be OK + decision = SynchronizationPolicyDecision.KEEP; + } else { + decision = SynchronizationPolicyDecision.ADD; + } + } else { + throw new PolicyViolationException("Request to add projection "+projCtxDesc+" but the activation policy decided that it should not exist"); + } + + } else if (synchronizationIntent == SynchronizationIntent.KEEP) { + if (shadowShouldExist) { + projCtx.setActive(true); + if (projCtx.isExists()) { + decision = SynchronizationPolicyDecision.KEEP; + } else { + decision = SynchronizationPolicyDecision.ADD; + } + } else { + throw new PolicyViolationException("Request to keep projection "+projCtxDesc+" but the activation policy decided that it should not exist"); + } + + } else { + throw new IllegalStateException("Unknown sync intent "+synchronizationIntent); + } + + LOGGER.trace("Evaluated decision for projection {} to {}", projCtxDesc, decision); + + projCtx.setSynchronizationPolicyDecision(decision); + + PrismObject focusNew = context.getFocusContext().getObjectNew(); + if (focusNew == null) { + // This must be a user delete or something similar. No point in proceeding + LOGGER.trace("focusNew is null, skipping activation processing of {}", projCtxDesc); + return; + } + + if (decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { + LOGGER.trace("Decision is {}, skipping activation properties processing for {}", decision, projCtxDesc); + return; + } + + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType == null) { + LOGGER.trace("No refined object definition, therefore also no activation outbound definition, skipping activation processing for account " + projCtxDesc); + return; + } + ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); + if (activationType == null) { + LOGGER.trace("No activation definition in projection {}, skipping activation properties processing", projCtxDesc); + return; + } + + ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), ActivationCapabilityType.class); + if (capActivation == null) { + LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", projCtx.getResource()); + return; + } + + ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); + ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); + ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); + ActivationLockoutStatusCapabilityType capLockoutStatus = CapabilityUtil.getEffectiveActivationLockoutStatus(capActivation); + + if (capStatus != null) { + evaluateActivationMapping(context, projCtx, + activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, + SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, + capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); + } else { + LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", projCtx.getResource()); + } + + ResourceBidirectionalMappingType validFromMappingType = activationType.getValidFrom(); + if (validFromMappingType == null || validFromMappingType.getOutbound() == null) { + LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", projCtx.getResource()); + } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validFromMappingType.getOutbound())) { + LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", projCtx.getResource()); + } else { + evaluateActivationMapping(context, projCtx, activationType.getValidFrom(), + SchemaConstants.PATH_ACTIVATION_VALID_FROM, + SchemaConstants.PATH_ACTIVATION_VALID_FROM, + null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_FROM.getLocalPart(), task, result); + } + + ResourceBidirectionalMappingType validToMappingType = activationType.getValidTo(); + if (validToMappingType == null || validToMappingType.getOutbound() == null) { + LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", projCtx.getResource()); + } else if (capValidTo == null && !ExpressionUtil.hasExplicitTarget(validToMappingType.getOutbound())) { + LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", projCtx.getResource()); + } else { + evaluateActivationMapping(context, projCtx, activationType.getValidTo(), + SchemaConstants.PATH_ACTIVATION_VALID_TO, + SchemaConstants.PATH_ACTIVATION_VALID_TO, + null, now, MappingTimeEval.CURRENT, ActivationType.F_VALID_TO.getLocalPart(), task, result); + } + + if (capLockoutStatus != null) { + evaluateActivationMapping(context, projCtx, + activationType.getLockoutStatus(), + SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, + capActivation, now, MappingTimeEval.CURRENT, ActivationType.F_LOCKOUT_STATUS.getLocalPart(), task, result); + } else { + LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", projCtx.getResource()); + } + + } + + public void processActivationMetadata(LensContext context, LensProjectionContext accCtx, + XMLGregorianCalendar now, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + ObjectDelta projDelta = accCtx.getDelta(); + if (projDelta == null) { + return; + } + + PropertyDelta statusDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS); + + if (statusDelta != null && !statusDelta.isDelete()) { + + // we have to determine if the status really changed + PrismObject oldShadow = accCtx.getObjectOld(); + ActivationStatusType statusOld = null; + if (oldShadow != null && oldShadow.asObjectable().getActivation() != null) { + statusOld = oldShadow.asObjectable().getActivation().getAdministrativeStatus(); + } + + PrismProperty statusPropNew = (PrismProperty) statusDelta.getItemNewMatchingPath(null); + ActivationStatusType statusNew = statusPropNew.getRealValue(); + + if (statusNew == statusOld) { + LOGGER.trace("Administrative status not changed ({}), timestamp and/or reason will not be recorded", statusNew); + } else { + // timestamps + PropertyDelta timestampDelta = LensUtil.createActivationTimestampDelta(statusNew, + now, getActivationDefinition(), OriginType.OUTBOUND, prismContext); + accCtx.swallowToSecondaryDelta(timestampDelta); + + // disableReason + if (statusNew == ActivationStatusType.DISABLED) { + PropertyDelta disableReasonDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_DISABLE_REASON); + if (disableReasonDelta == null) { + String disableReason = null; + ObjectDelta projPrimaryDelta = accCtx.getPrimaryDelta(); + ObjectDelta projSecondaryDelta = accCtx.getSecondaryDelta(); + if (projPrimaryDelta != null + && projPrimaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) != null + && (projSecondaryDelta == null || projSecondaryDelta.findPropertyDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS) == null)) { + disableReason = SchemaConstants.MODEL_DISABLE_REASON_EXPLICIT; + } else if (accCtx.isLegal()) { + disableReason = SchemaConstants.MODEL_DISABLE_REASON_MAPPED; + } else { + disableReason = SchemaConstants.MODEL_DISABLE_REASON_DEPROVISION; + } + + PrismPropertyDefinition disableReasonDef = activationDefinition.findPropertyDefinition(ActivationType.F_DISABLE_REASON); + disableReasonDelta = disableReasonDef.createEmptyDelta( + ItemPath.create(FocusType.F_ACTIVATION, ActivationType.F_DISABLE_REASON)); + disableReasonDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(disableReason, OriginType.OUTBOUND, null)); + accCtx.swallowToSecondaryDelta(disableReasonDelta); + } + } + } + } + + } + + public void processActivationUserFuture(LensContext context, LensProjectionContext accCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + String accCtxDesc = accCtx.toHumanReadableString(); + SynchronizationPolicyDecision decision = accCtx.getSynchronizationPolicyDecision(); + SynchronizationIntent synchronizationIntent = accCtx.getSynchronizationIntent(); + + if (accCtx.isTombstone() || decision == SynchronizationPolicyDecision.BROKEN + || decision == SynchronizationPolicyDecision.IGNORE + || decision == SynchronizationPolicyDecision.UNLINK || decision == SynchronizationPolicyDecision.DELETE) { + return; + } + + accCtx.recompute(); + + evaluateExistenceMapping(context, accCtx, now, MappingTimeEval.FUTURE, task, result); + + PrismObject focusNew = context.getFocusContext().getObjectNew(); + if (focusNew == null) { + // This must be a user delete or something similar. No point in proceeding + LOGGER.trace("focusNew is null, skipping activation processing of {}", accCtxDesc); + return; + } + + ResourceObjectTypeDefinitionType resourceAccountDefType = accCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType == null) { + return; + } + ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); + if (activationType == null) { + return; + } + + ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(accCtx.getResource(), ActivationCapabilityType.class); + if (capActivation == null) { + return; + } + + ActivationStatusCapabilityType capStatus = CapabilityUtil.getEffectiveActivationStatus(capActivation); + ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); + ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); + + if (capStatus != null) { + + evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, + capActivation, now, MappingTimeEval.FUTURE, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); + } + + if (capValidFrom != null) { + evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_VALID_FROM, SchemaConstants.PATH_ACTIVATION_VALID_FROM, + null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); + } + + if (capValidTo != null) { + evaluateActivationMapping(context, accCtx, activationType.getAdministrativeStatus(), + SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, + null, now, MappingTimeEval.FUTURE, ActivationType.F_VALID_FROM.getLocalPart(), task, result); + } + + } + + + private boolean evaluateExistenceMapping(final LensContext context, + final LensProjectionContext projCtx, final XMLGregorianCalendar now, final MappingTimeEval current, + Task task, final OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + final String projCtxDesc = projCtx.toHumanReadableString(); + + final Boolean legal = projCtx.isLegal(); + if (legal == null) { + throw new IllegalStateException("Null 'legal' for "+projCtxDesc); + } + + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType == null) { + return legal; + } + ResourceActivationDefinitionType activationType = resourceAccountDefType.getActivation(); + if (activationType == null) { + return legal; + } + ResourceBidirectionalMappingType existenceType = activationType.getExistence(); + if (existenceType == null) { + return legal; + } + List outbound = existenceType.getOutbound(); + if (outbound == null || outbound.isEmpty()) { + // "default mapping" + return legal; + } + + MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); + params.setMappingTypes(outbound); + params.setMappingDesc("outbound existence mapping in projection " + projCtxDesc); + params.setNow(now); + params.setAPrioriTargetObject(projCtx.getObjectOld()); + params.setEvaluateCurrent(current); + params.setTargetContext(projCtx); + params.setFixTarget(true); + params.setContext(context); + + params.setInitializer(builder -> { + // Source: legal + ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(projCtx); + Source,PrismPropertyDefinition> legalSource + = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL_QNAME); + builder.defaultSource(legalSource); + + // Source: assigned + ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); + Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); + builder.addSource(assignedSource); + + // Source: focusExists + ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); + Source,PrismPropertyDefinition> focusExistsSource + = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); + builder.addSource(focusExistsSource); + + // Variable: focus + builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); + + // Variable: user (for convenience, same as "focus"), DEPRECATED + builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDefinition()); + builder.addAliasRegistration(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS); + + // Variable: projection + // This may be tricky when creation of a new projection is considered. + // In that case we do not have any projection object (account) yet, neither new nor old. But we already have + // projection context. We have to pass projection definition explicitly here. + builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); + builder.addVariableDefinition(ExpressionConstants.VAR_PROJECTION, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); + builder.addAliasRegistration(ExpressionConstants.VAR_SHADOW, ExpressionConstants.VAR_PROJECTION); + + // Variable: resource + builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, projCtx.getResource(), ResourceType.class); + + builder.originType(OriginType.OUTBOUND); + builder.originObject(projCtx.getResource()); + return builder; + }); + + PrismValueDeltaSetTriple> aggregatedOutputTriple = prismContext.deltaFactory().createPrismValueDeltaSetTriple(); + + params.setProcessor((mappingOutputPath, outputStruct) -> { + // This is a very primitive implementation of output processing. + // Maybe we should somehow use the default processing in MappingEvaluator, but it's quite complex + // and therefore we should perhaps wait for general mapping cleanup (MID-3847). + PrismValueDeltaSetTriple> outputTriple = outputStruct.getOutputTriple(); + if (outputTriple != null) { + aggregatedOutputTriple.merge(outputTriple); + } + return false; + }); + + MutablePrismPropertyDefinition shadowExistenceTargetDef = prismContext.definitionFactory().createPropertyDefinition(SHADOW_EXISTS_PROPERTY_NAME, DOMUtil.XSD_BOOLEAN); + shadowExistenceTargetDef.setMinOccurs(1); + shadowExistenceTargetDef.setMaxOccurs(1); + params.setTargetItemDefinition(shadowExistenceTargetDef); + mappingEvaluator.evaluateMappingSetProjection(params, task, result); + + boolean output; + if (aggregatedOutputTriple.isEmpty()) { + output = legal; // the default + } else { + Collection> nonNegativeValues = aggregatedOutputTriple.getNonNegativeValues(); + if (nonNegativeValues.isEmpty()) { + throw new ExpressionEvaluationException("Activation existence expression resulted in no values for projection " + projCtxDesc); + } else if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + projCtxDesc + ": " + nonNegativeValues); + } else { + PrismPropertyValue value = nonNegativeValues.iterator().next(); + if (value != null && value.getRealValue() != null) { + output = value.getRealValue(); + } else { + // TODO could this even occur? + throw new ExpressionEvaluationException("Activation existence expression resulted in null value for projection " + projCtxDesc); + } + } + } + + return output; + } + + private void evaluateActivationMapping(final LensContext context, + final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, + final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, + final ActivationCapabilityType capActivation, XMLGregorianCalendar now, final MappingTimeEval current, + String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + MappingInitializer,PrismPropertyDefinition> initializer = + builder -> { + // Source: administrativeStatus, validFrom or validTo + ItemDeltaItem,PrismPropertyDefinition> sourceIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); + + if (capActivation != null && focusPropertyPath.equivalent(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS)) { + ActivationValidityCapabilityType capValidFrom = CapabilityUtil.getEffectiveActivationValidFrom(capActivation); + ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); + + // Source: computedShadowStatus + ItemDeltaItem,PrismPropertyDefinition> computedIdi; + if (capValidFrom != null && capValidTo != null) { + // "Native" validFrom and validTo, directly use administrativeStatus + computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); + + } else { + // Simulate validFrom and validTo using effectiveStatus + computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS); + + } + + Source,PrismPropertyDefinition> computedSource = new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME); + + builder.defaultSource(computedSource); + + Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME); + builder.addSource(source); + + } else { + Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME); + builder.defaultSource(source); + } + + // Source: legal + ItemDeltaItem,PrismPropertyDefinition> legalIdi = getLegalIdi(projCtx); + Source,PrismPropertyDefinition> legalSource = new Source<>(legalIdi, ExpressionConstants.VAR_LEGAL_QNAME); + builder.addSource(legalSource); + + // Source: assigned + ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(projCtx); + Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED_QNAME); + builder.addSource(assignedSource); + + // Source: focusExists + ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); + Source,PrismPropertyDefinition> focusExistsSource + = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS_QNAME); + builder.addSource(focusExistsSource); + + return builder; + }; + + evaluateOutboundMapping(context, projCtx, bidirectionalMappingType, focusPropertyPath, projectionPropertyPath, initializer, + now, current, desc + " outbound activation mapping", task, result); + + } + + private void evaluateOutboundMapping(final LensContext context, + final LensProjectionContext projCtx, ResourceBidirectionalMappingType bidirectionalMappingType, + final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, + final MappingInitializer,PrismPropertyDefinition> initializer, + XMLGregorianCalendar now, final MappingTimeEval evaluateCurrent, String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (bidirectionalMappingType == null) { + LOGGER.trace("No '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); + return; + } + List outboundMappingTypes = bidirectionalMappingType.getOutbound(); + if (outboundMappingTypes == null || outboundMappingTypes.isEmpty()) { + LOGGER.trace("No outbound definition in '{}' definition in projection {}, skipping", desc, projCtx.toHumanReadableString()); + return; + } + + String projCtxDesc = projCtx.toHumanReadableString(); + PrismObject shadowNew = projCtx.getObjectNew(); + + MappingInitializer,PrismPropertyDefinition> internalInitializer = + builder -> { + + builder.addVariableDefinitions(ModelImplUtils.getDefaultExpressionVariables(context, projCtx)); + + builder.originType(OriginType.OUTBOUND); + builder.originObject(projCtx.getResource()); + + initializer.initialize(builder); + + return builder; + }; + + MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); + params.setMappingTypes(outboundMappingTypes); + params.setMappingDesc(desc + " in projection " + projCtxDesc); + params.setNow(now); + params.setInitializer(internalInitializer); + params.setTargetLoader(new ProjectionMappingLoader<>(context, projCtx, contextLoader)); + params.setAPrioriTargetObject(shadowNew); + params.setAPrioriTargetDelta(LensUtil.findAPrioriDelta(context, projCtx)); + if (context.getFocusContext() != null) { + params.setSourceContext(context.getFocusContext().getObjectDeltaObject()); + } + params.setTargetContext(projCtx); + params.setDefaultTargetItemPath(projectionPropertyPath); + params.setEvaluateCurrent(evaluateCurrent); + params.setEvaluateWeak(true); + params.setContext(context); + params.setHasFullTargetObject(projCtx.hasFullShadow()); + + Map>> outputTripleMap = mappingEvaluator.evaluateMappingSetProjection(params, task, result); + + LOGGER.trace("Mapping processing output after {} ({}):\n{}", desc, evaluateCurrent, + DebugUtil.debugDumpLazily(outputTripleMap, 1)); + + if (projCtx.isDoReconciliation()) { + reconcileOutboundValue(context, projCtx, outputTripleMap, desc); + } + + } + + /** + * TODO: can we align this with ReconciliationProcessor? + */ + private void reconcileOutboundValue(LensContext context, LensProjectionContext projCtx, + Map>> outputTripleMap, String desc) throws SchemaException { + + // TODO: check for full shadow? + + for (Entry>> entry: outputTripleMap.entrySet()) { + UniformItemPath mappingOutputPath = entry.getKey(); + MappingOutputStruct> mappingOutputStruct = entry.getValue(); + if (mappingOutputStruct.isWeakMappingWasUsed()) { + // Thing to do. All deltas should already be in context + LOGGER.trace("Skip reconciliation of {} in {} because of weak", mappingOutputPath, desc); + continue; + } + if (!mappingOutputStruct.isStrongMappingWasUsed()) { + // Normal mappings are not processed for reconciliation + LOGGER.trace("Skip reconciliation of {} in {} because not strong", mappingOutputPath, desc); + continue; + } + LOGGER.trace("reconciliation of {} for {}", mappingOutputPath, desc); + + PrismObjectDefinition targetObjectDefinition = projCtx.getObjectDefinition(); + PrismPropertyDefinition targetItemDefinition = targetObjectDefinition.findPropertyDefinition(mappingOutputPath); + if (targetItemDefinition == null) { + throw new SchemaException("No definition for item "+mappingOutputPath+" in "+targetObjectDefinition); + } + PropertyDelta targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath); + + PrismValueDeltaSetTriple> outputTriple = mappingOutputStruct.getOutputTriple(); + + PrismProperty currentTargetItem = null; + PrismObject shadowCurrent = projCtx.getObjectCurrent(); + if (shadowCurrent != null) { + currentTargetItem = shadowCurrent.findProperty(mappingOutputPath); + } + Collection> hasValues = new ArrayList<>(); + if (currentTargetItem != null) { + hasValues.addAll(currentTargetItem.getValues()); + } + + Collection> shouldHaveValues = outputTriple.getNonNegativeValues(); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Reconciliation of {}:\n hasValues:\n{}\n shouldHaveValues\n{}", + mappingOutputPath, DebugUtil.debugDump(hasValues, 2), DebugUtil.debugDump(shouldHaveValues, 2)); + } + + for (PrismPropertyValue shouldHaveValue: shouldHaveValues) { + if (!PrismValueCollectionsUtil.containsRealValue(hasValues, shouldHaveValue)) { + if (targetItemDefinition.isSingleValue()) { + targetItemDelta.setValueToReplace(shouldHaveValue.clone()); + } else { + targetItemDelta.addValueToAdd(shouldHaveValue.clone()); + } + } + } + + if (targetItemDefinition.isSingleValue()) { + if (!targetItemDelta.isReplace() && shouldHaveValues.isEmpty()) { + targetItemDelta.setValueToReplace(); + } + } else { + for (PrismPropertyValue hasValue: hasValues) { + if (!PrismValueCollectionsUtil.containsRealValue(shouldHaveValues, hasValue)) { + targetItemDelta.addValueToDelete(hasValue.clone()); + } + } + } + + if (!targetItemDelta.isEmpty()) { + LOGGER.trace("Reconciliation delta:\n{}", targetItemDelta.debugDumpLazily(1)); + projCtx.swallowToSecondaryDelta(targetItemDelta); + } + } + + } + + + + private ItemDeltaItem,PrismPropertyDefinition> getLegalIdi(LensProjectionContext accCtx) throws SchemaException { + Boolean legal = accCtx.isLegal(); + Boolean legalOld = accCtx.isLegalOld(); + return createBooleanIdi(LEGAL_PROPERTY_NAME, legalOld, legal); + } + + @NotNull + private ItemDeltaItem, PrismPropertyDefinition> createBooleanIdi( + QName propertyName, Boolean old, Boolean current) throws SchemaException { + MutablePrismPropertyDefinition definition = prismContext.definitionFactory().createPropertyDefinition(propertyName, DOMUtil.XSD_BOOLEAN); + definition.setMinOccurs(1); + definition.setMaxOccurs(1); + PrismProperty property = definition.instantiate(); + property.add(prismContext.itemFactory().createPropertyValue(current)); + + if (current == old) { + return new ItemDeltaItem<>(property); + } else { + PrismProperty propertyOld = property.clone(); + propertyOld.setRealValue(old); + PropertyDelta delta = propertyOld.createDelta(); + delta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(current)); + return new ItemDeltaItem<>(propertyOld, delta, property, definition); + } + } + + private ItemDeltaItem,PrismPropertyDefinition> getAssignedIdi(LensProjectionContext accCtx) throws SchemaException { + Boolean assigned = accCtx.isAssigned(); + Boolean assignedOld = accCtx.isAssignedOld(); + return createBooleanIdi(ASSIGNED_PROPERTY_NAME, assignedOld, assigned); + } + + private ItemDeltaItem,PrismPropertyDefinition> getFocusExistsIdi( + LensFocusContext lensFocusContext) throws SchemaException { + Boolean existsOld = null; + Boolean existsNew = null; + + if (lensFocusContext != null) { + if (lensFocusContext.isDelete()) { + existsOld = true; + existsNew = false; + } else if (lensFocusContext.isAdd()) { + existsOld = false; + existsNew = true; + } else { + existsOld = true; + existsNew = true; + } + } + + MutablePrismPropertyDefinition existsDef = prismContext.definitionFactory().createPropertyDefinition(FOCUS_EXISTS_PROPERTY_NAME, + DOMUtil.XSD_BOOLEAN); + existsDef.setMinOccurs(1); + existsDef.setMaxOccurs(1); + PrismProperty existsProp = existsDef.instantiate(); + + existsProp.add(prismContext.itemFactory().createPropertyValue(existsNew)); + + if (existsOld == existsNew) { + return new ItemDeltaItem<>(existsProp); + } else { + PrismProperty existsPropOld = existsProp.clone(); + existsPropOld.setRealValue(existsOld); + PropertyDelta existsDelta = existsPropOld.createDelta(); + existsDelta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(existsNew)); + return new ItemDeltaItem<>(existsPropOld, existsDelta, existsProp, existsDef); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void processLifecycle(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for focal object. + LOGGER.trace("Skipping lifecycle evaluation because focus is not FocusType"); + return; + } + + processLifecycleFocus((LensContext)context, projCtx, now, task, result); + } + + private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + LOGGER.trace("Skipping lifecycle evaluation because there is no focus"); + return; + } + + ResourceObjectLifecycleDefinitionType lifecycleDef = null; + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType != null) { + lifecycleDef = resourceAccountDefType.getLifecycle(); + } + ResourceBidirectionalMappingType lifecycleStateMappingType = null; + if (lifecycleDef != null) { + lifecycleStateMappingType = lifecycleDef.getLifecycleState(); + } + + if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { + + if (!projCtx.isAdd()) { + LOGGER.trace("Skipping lifecycle evaluation because this is not add operation (default expression)"); + return; + } + + PrismObject focusNew = focusContext.getObjectNew(); + if (focusNew == null) { + LOGGER.trace("Skipping lifecycle evaluation because there is no new focus (default expression)"); + return; + } + + PrismObject projectionNew = projCtx.getObjectNew(); + if (projectionNew == null) { + LOGGER.trace("Skipping lifecycle evaluation because there is no new projection (default expression)"); + return; + } + + String lifecycle = midpointFunctions.computeProjectionLifecycle( + focusNew.asObjectable(), projectionNew.asObjectable(), projCtx.getResource()); + + LOGGER.trace("Computed projection lifecycle (default expression): {}", lifecycle); + + if (lifecycle != null) { + PrismPropertyDefinition propDef = projCtx.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_LIFECYCLE_STATE); + PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); + PrismPropertyValue pval = prismContext.itemFactory().createPropertyValue(lifecycle); + pval.setOriginType(OriginType.OUTBOUND); + lifeCycleDelta.setValuesToReplace(pval); + projCtx.swallowToSecondaryDelta(lifeCycleDelta); + } + + } else { + + LOGGER.trace("Computing projection lifecycle (mapping): {}", lifecycleStateMappingType); + evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, + SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, + null, now, MappingTimeEval.CURRENT, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); + } + + } + + private PrismObjectDefinition getUserDefinition() { + if (userDefinition == null) { + userDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(UserType.class); + } + return userDefinition; + } + + private PrismContainerDefinition getActivationDefinition() { + if (activationDefinition == null) { + PrismObjectDefinition userDefinition = getUserDefinition(); + activationDefinition = userDefinition.findContainerDefinition(UserType.F_ACTIVATION); + } + return activationDefinition; + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java index 4c67fdd79c0..5a094acdc26 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java @@ -41,7 +41,7 @@ import com.evolveum.midpoint.model.impl.lens.LensObjectDeltaOperation; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.impl.lens.SynchronizationIntent; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; import com.evolveum.midpoint.model.impl.security.SecurityHelper; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.repo.api.RepositoryService; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java index a8a3520bbfc..b1dff7232c4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java @@ -1,193 +1,193 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.lens.projector.focus; - -import java.util.*; - -import javax.xml.datatype.XMLGregorianCalendar; - -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.LifecycleUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -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.PolicyViolationException; -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; - -/** - * @author Radovan Semancik - */ -@Component -public class FocusLifecycleProcessor { - - @Autowired private ExpressionFactory expressionFactory; - - private static final Trace LOGGER = TraceManager.getTrace(FocusLifecycleProcessor.class); - - public void processLifecycle(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for FocusType. - return; - } - - //noinspection unchecked - processLifecycleWithFocus((LensContext)context, now, task, result); - } - - private void processLifecycleWithFocus(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - ObjectDelta focusDelta = focusContext.getDelta(); - - if (focusDelta != null && focusDelta.isDelete()) { - LOGGER.trace("Skipping lifecycle processing because of focus delete"); - return; - } - - LifecycleStateModelType lifecycleStateModel = focusContext.getLifecycleModel(); - if (lifecycleStateModel == null) { - LOGGER.trace("Skipping lifecycle processing because there is no lifecycle state model for focus"); - return; - } - - PrismObject objectNew = focusContext.getObjectNew(); - String startLifecycleState = objectNew.asObjectable().getLifecycleState(); - if (startLifecycleState == null) { - startLifecycleState = SchemaConstants.LIFECYCLE_ACTIVE; - } - - LifecycleStateType startStateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, startLifecycleState); - if (startStateType == null) { - LOGGER.trace("Skipping lifecycle processing because there is no specification for lifecycle state {}", startLifecycleState); - return; - } - - for (LifecycleStateTransitionType transitionType : startStateType.getTransition()) { - String targetLifecycleState = transitionType.getTargetState(); - if (shouldTransition(context, transitionType, targetLifecycleState, task, result)) { - executeExitActions(context, lifecycleStateModel, startLifecycleState, now, task, result); - LOGGER.debug("Lifecycle state transition of {}: {} -> {}", objectNew, startLifecycleState, targetLifecycleState); - recordLifecycleTransitionDelta(focusContext, targetLifecycleState); - executeEntryActions(context, lifecycleStateModel, targetLifecycleState, now, task, result); - LOGGER.trace("Lifecycle state transition of {} from {} to {} done", objectNew, startLifecycleState, targetLifecycleState); - break; - } - } - } - - private boolean shouldTransition(LensContext context, LifecycleStateTransitionType transitionType, String targetLifecycleState, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType conditionExpressionType = transitionType.getCondition(); - if (conditionExpressionType == null) { - return false; - } - String desc = "condition for transition to state "+targetLifecycleState+" for "+context.getFocusContext().getHumanReadableName(); - - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_OBJECT, context.getFocusContext().getObjectNew(), context.getFocusContext().getObjectNew().getDefinition()); - // TODO: more variables? - - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( - conditionExpressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), - MiscSchemaUtil.getExpressionProfile(), desc, task, result); - ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); - ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); - PrismValueDeltaSetTriple> outputTriple = - ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); - PrismPropertyValue expressionOutputValue = ExpressionUtil.getExpressionOutputValue(outputTriple, desc); - return ExpressionUtil.getBooleanConditionOutput(expressionOutputValue); - } - - private void recordLifecycleTransitionDelta(LensFocusContext focusContext, String targetLifecycleState) throws SchemaException { - PropertyDelta lifecycleDelta = focusContext.getPrismContext().deltaFactory().property() - .createModificationReplaceProperty(ObjectType.F_LIFECYCLE_STATE, focusContext.getObjectDefinition(), - targetLifecycleState); - focusContext.swallowToSecondaryDelta(lifecycleDelta); - } - - private void executeEntryActions(LensContext context, LifecycleStateModelType lifecycleStateModel, - String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); - if (stateType == null) { - return; - } - executeStateActions(context, targetLifecycleState, stateType.getEntryAction(), "entry", now, task, result); - } - - private void executeExitActions(LensContext context, LifecycleStateModelType lifecycleStateModel, - String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); - if (stateType == null) { - return; - } - executeStateActions(context, targetLifecycleState, stateType.getExitAction(), "exit", now, task, result); - } - - private void executeStateActions(LensContext context, String targetLifecycleState, - List actions, String actionTypeDesc, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - for (LifecycleStateActionType action: actions) { - LOGGER.trace("Execute {} action {} for state {} of {}", actionTypeDesc, action.getName(), targetLifecycleState, context.getFocusContext().getObjectNew()); - executeDataReduction(context, action.getDataReduction(), now, task, result); - } - } - - private void executeDataReduction(LensContext context, LifecycleStateActionDataReductionType dataReduction, - XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - if (dataReduction == null) { - return; - } - LensFocusContext focusContext = context.getFocusContext(); - PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); - for (ItemPathType purgeItemPathType : dataReduction.getPurgeItem()) { - ItemPath purgeItemPath = purgeItemPathType.getItemPath(); - LOGGER.trace("Purging item {} from {}", purgeItemPath, focusContext.getObjectNew()); - ItemDefinition purgeItemDef = focusDefinition.findItemDefinition(purgeItemPath); - ItemDelta purgeItemDelta = purgeItemDef.createEmptyDelta(purgeItemPath); - purgeItemDelta.setValueToReplace(); - focusContext.swallowToSecondaryDelta(purgeItemDelta); - } - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.focus; + +import java.util.*; + +import javax.xml.datatype.XMLGregorianCalendar; + +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.LifecycleUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +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.PolicyViolationException; +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; + +/** + * @author Radovan Semancik + */ +@Component +public class FocusLifecycleProcessor { + + @Autowired private ExpressionFactory expressionFactory; + + private static final Trace LOGGER = TraceManager.getTrace(FocusLifecycleProcessor.class); + + public void processLifecycle(LensContext context, XMLGregorianCalendar now, + Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + return; + } + if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for FocusType. + return; + } + + //noinspection unchecked + processLifecycleWithFocus((LensContext)context, now, task, result); + } + + private void processLifecycleWithFocus(LensContext context, XMLGregorianCalendar now, + Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + ObjectDelta focusDelta = focusContext.getDelta(); + + if (focusDelta != null && focusDelta.isDelete()) { + LOGGER.trace("Skipping lifecycle processing because of focus delete"); + return; + } + + LifecycleStateModelType lifecycleStateModel = focusContext.getLifecycleModel(); + if (lifecycleStateModel == null) { + LOGGER.trace("Skipping lifecycle processing because there is no lifecycle state model for focus"); + return; + } + + PrismObject objectNew = focusContext.getObjectNew(); + String startLifecycleState = objectNew.asObjectable().getLifecycleState(); + if (startLifecycleState == null) { + startLifecycleState = SchemaConstants.LIFECYCLE_ACTIVE; + } + + LifecycleStateType startStateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, startLifecycleState); + if (startStateType == null) { + LOGGER.trace("Skipping lifecycle processing because there is no specification for lifecycle state {}", startLifecycleState); + return; + } + + for (LifecycleStateTransitionType transitionType : startStateType.getTransition()) { + String targetLifecycleState = transitionType.getTargetState(); + if (shouldTransition(context, transitionType, targetLifecycleState, task, result)) { + executeExitActions(context, lifecycleStateModel, startLifecycleState, now, task, result); + LOGGER.debug("Lifecycle state transition of {}: {} -> {}", objectNew, startLifecycleState, targetLifecycleState); + recordLifecycleTransitionDelta(focusContext, targetLifecycleState); + executeEntryActions(context, lifecycleStateModel, targetLifecycleState, now, task, result); + LOGGER.trace("Lifecycle state transition of {} from {} to {} done", objectNew, startLifecycleState, targetLifecycleState); + break; + } + } + } + + private boolean shouldTransition(LensContext context, LifecycleStateTransitionType transitionType, String targetLifecycleState, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + ExpressionType conditionExpressionType = transitionType.getCondition(); + if (conditionExpressionType == null) { + return false; + } + String desc = "condition for transition to state "+targetLifecycleState+" for "+context.getFocusContext().getHumanReadableName(); + + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_OBJECT, context.getFocusContext().getObjectNew(), context.getFocusContext().getObjectNew().getDefinition()); + // TODO: more variables? + + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( + conditionExpressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), + MiscSchemaUtil.getExpressionProfile(), desc, task, result); + ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); + ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); + PrismValueDeltaSetTriple> outputTriple = + ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); + PrismPropertyValue expressionOutputValue = ExpressionUtil.getExpressionOutputValue(outputTriple, desc); + return ExpressionUtil.getBooleanConditionOutput(expressionOutputValue); + } + + private void recordLifecycleTransitionDelta(LensFocusContext focusContext, String targetLifecycleState) throws SchemaException { + PropertyDelta lifecycleDelta = focusContext.getPrismContext().deltaFactory().property() + .createModificationReplaceProperty(ObjectType.F_LIFECYCLE_STATE, focusContext.getObjectDefinition(), + targetLifecycleState); + focusContext.swallowToSecondaryDelta(lifecycleDelta); + } + + private void executeEntryActions(LensContext context, LifecycleStateModelType lifecycleStateModel, + String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); + if (stateType == null) { + return; + } + executeStateActions(context, targetLifecycleState, stateType.getEntryAction(), "entry", now, task, result); + } + + private void executeExitActions(LensContext context, LifecycleStateModelType lifecycleStateModel, + String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); + if (stateType == null) { + return; + } + executeStateActions(context, targetLifecycleState, stateType.getExitAction(), "exit", now, task, result); + } + + private void executeStateActions(LensContext context, String targetLifecycleState, + List actions, String actionTypeDesc, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + for (LifecycleStateActionType action: actions) { + LOGGER.trace("Execute {} action {} for state {} of {}", actionTypeDesc, action.getName(), targetLifecycleState, context.getFocusContext().getObjectNew()); + executeDataReduction(context, action.getDataReduction(), now, task, result); + } + } + + private void executeDataReduction(LensContext context, LifecycleStateActionDataReductionType dataReduction, + XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + if (dataReduction == null) { + return; + } + LensFocusContext focusContext = context.getFocusContext(); + PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); + for (ItemPathType purgeItemPathType : dataReduction.getPurgeItem()) { + ItemPath purgeItemPath = purgeItemPathType.getItemPath(); + LOGGER.trace("Purging item {} from {}", purgeItemPath, focusContext.getObjectNew()); + ItemDefinition purgeItemDef = focusDefinition.findItemDefinition(purgeItemPath); + ItemDelta purgeItemDelta = purgeItemDef.createEmptyDelta(purgeItemPath); + purgeItemDelta.setValueToReplace(); + focusContext.swallowToSecondaryDelta(purgeItemDelta); + } + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingEvaluator.java index 89d532ffc1b..dc4fdfbe44b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/mappings/MappingEvaluator.java @@ -1,722 +1,722 @@ -/* - * Copyright (c) 2013-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens.projector.mappings; - -import java.util.*; -import java.util.Map.Entry; - -import javax.xml.bind.JAXBElement; -import javax.xml.datatype.DatatypeConstants; -import javax.xml.datatype.XMLGregorianCalendar; - -import com.evolveum.midpoint.model.impl.lens.*; -import com.evolveum.midpoint.model.impl.lens.projector.*; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.repo.common.expression.Source; -import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.path.UniformItemPath; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.expression.TypedValue; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.CommonException; -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.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.AssignmentHolderType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.GenerateExpressionEvaluatorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; -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.PasswordType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; - -/** - * @author Radovan Semancik - * - */ -@Component -public class MappingEvaluator { - - private static final Trace LOGGER = TraceManager.getTrace(MappingEvaluator.class); - - @Autowired private MappingFactory mappingFactory; - @Autowired private CredentialsProcessor credentialsProcessor; - @Autowired private ContextLoader contextLoader; - @Autowired private PrismContext prismContext; - @Autowired private ObjectResolver objectResolver; - - public PrismContext getPrismContext() { - return prismContext; - } - - static final List FOCUS_VARIABLE_NAMES = Arrays.asList(ExpressionConstants.VAR_FOCUS, ExpressionConstants.VAR_USER); - - public void evaluateMapping(MappingImpl mapping, - LensContext lensContext, Task task, OperationResult parentResult) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, CommunicationException { - evaluateMapping(mapping, lensContext, null, task, parentResult); - } - - public void evaluateMapping(MappingImpl mapping, - LensContext lensContext, LensProjectionContext projContext, Task task, OperationResult parentResult) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, CommunicationException { - - ExpressionEnvironment env = new ExpressionEnvironment<>(); - env.setLensContext(lensContext); - env.setProjectionContext(projContext); - env.setMapping(mapping); - env.setCurrentResult(parentResult); - env.setCurrentTask(task); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); - - ObjectType originObject = mapping.getOriginObject(); - String objectOid, objectName, objectTypeName; - if (originObject != null) { - objectOid = originObject.getOid(); - objectName = String.valueOf(originObject.getName()); - objectTypeName = originObject.getClass().getSimpleName(); - } else { - objectOid = objectName = objectTypeName = null; - } - String mappingName = mapping.getItemName() != null ? mapping.getItemName().getLocalPart() : null; - - long start = System.currentTimeMillis(); - try { - task.recordState("Started evaluation of mapping " + mapping.getMappingContextDescription() + "."); - mapping.evaluate(task, parentResult); - task.recordState("Successfully finished evaluation of mapping " + mapping.getMappingContextDescription() + " in " + (System.currentTimeMillis()-start) + " ms."); - } catch (IllegalArgumentException e) { - task.recordState("Evaluation of mapping " + mapping.getMappingContextDescription() + " finished with error in " + (System.currentTimeMillis()-start) + " ms."); - throw new IllegalArgumentException(e.getMessage()+" in "+mapping.getContextDescription(), e); - } finally { - task.recordMappingOperation(objectOid, objectName, objectTypeName, mappingName, System.currentTimeMillis() - start); - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - if (lensContext.getInspector() != null) { - lensContext.getInspector().afterMappingEvaluation(lensContext, mapping); - } - } - } - - // TODO: unify OutboundProcessor.evaluateMapping() with MappingEvaluator.evaluateOutboundMapping(...) - public void evaluateOutboundMapping(final LensContext context, - final LensProjectionContext projCtx, List outboundMappings, - final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, - final MappingInitializer,PrismPropertyDefinition> initializer, MappingOutputProcessor> processor, - XMLGregorianCalendar now, final MappingTimeEval evaluateCurrent, boolean evaluateWeak, - String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - String projCtxDesc = projCtx.toHumanReadableString(); - PrismObject shadowNew = projCtx.getObjectNew(); - - MappingInitializer,PrismPropertyDefinition> internalInitializer = - builder -> { - - builder.addVariableDefinitions(ModelImplUtils.getDefaultExpressionVariables(context, projCtx)); - - builder.originType(OriginType.OUTBOUND); - builder.originObject(projCtx.getResource()); - - initializer.initialize(builder); - - return builder; - }; - - MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); - params.setMappingTypes(outboundMappings); - params.setMappingDesc(desc + " in projection " + projCtxDesc); - params.setNow(now); - params.setInitializer(internalInitializer); - params.setProcessor(processor); - params.setTargetLoader(new ProjectionMappingLoader<>(context, projCtx, contextLoader)); - params.setAPrioriTargetObject(shadowNew); - params.setAPrioriTargetDelta(LensUtil.findAPrioriDelta(context, projCtx)); - params.setTargetContext(projCtx); - params.setDefaultTargetItemPath(projectionPropertyPath); - if (context.getFocusContext() != null) { - params.setSourceContext(context.getFocusContext().getObjectDeltaObject()); - } - params.setEvaluateCurrent(evaluateCurrent); - params.setEvaluateWeak(evaluateWeak); - params.setContext(context); - params.setHasFullTargetObject(projCtx.hasFullShadow()); - evaluateMappingSetProjection(params, task, result); - } - - public Map> evaluateMappingSetProjection( - MappingEvaluatorParams params, - Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - String mappingDesc = params.getMappingDesc(); - LensElementContext targetContext = params.getTargetContext(); - PrismObjectDefinition targetObjectDefinition = targetContext.getObjectDefinition(); - ItemPath defaultTargetItemPath = params.getDefaultTargetItemPath(); - - Map> outputTripleMap = new HashMap<>(); - XMLGregorianCalendar nextRecomputeTime = null; - String triggerOriginDescription = null; - Collection mappingTypes = params.getMappingTypes(); - Collection> mappings = new ArrayList<>(mappingTypes.size()); - - for (MappingType mappingType: mappingTypes) { - - MappingImpl.Builder mappingBuilder = mappingFactory.createMappingBuilder(mappingType, mappingDesc); - String mappingName = null; - if (mappingType.getName() != null) { - mappingName = mappingType.getName(); - } - - if (!mappingBuilder.isApplicableToChannel(params.getContext().getChannel())) { - LOGGER.trace("Mapping {} not applicable to channel, skipping {}", mappingName, params.getContext().getChannel()); - continue; - } - - mappingBuilder.now(params.getNow()); - if (defaultTargetItemPath != null && targetObjectDefinition != null) { - D defaultTargetItemDef = targetObjectDefinition.findItemDefinition(defaultTargetItemPath); - mappingBuilder.defaultTargetDefinition(defaultTargetItemDef); - } else { - mappingBuilder.defaultTargetDefinition(params.getTargetItemDefinition()); - } - mappingBuilder.defaultTargetPath(defaultTargetItemPath); - mappingBuilder.targetContext(targetObjectDefinition); - - if (params.getSourceContext() != null) { - mappingBuilder.sourceContext(params.getSourceContext()); - } - - // Initialize mapping (using Inversion of Control) - MappingImpl.Builder initializedMappingBuilder = params.getInitializer().initialize(mappingBuilder); - - MappingImpl mapping = initializedMappingBuilder.build(); - boolean timeConstraintValid = mapping.evaluateTimeConstraintValid(task, result); - - if (params.getEvaluateCurrent() == MappingTimeEval.CURRENT && !timeConstraintValid) { - LOGGER.trace("Mapping {} is non-current, but evaluating current mappings, skipping {}", mappingName, params.getContext().getChannel()); - } else if (params.getEvaluateCurrent() == MappingTimeEval.FUTURE && timeConstraintValid) { - LOGGER.trace("Mapping {} is current, but evaluating non-current mappings, skipping {}", mappingName, params.getContext().getChannel()); - } else { - mappings.add(mapping); - } - } - - boolean hasFullTargetObject = params.hasFullTargetObject(); - PrismObject aPrioriTargetObject = params.getAPrioriTargetObject(); - - LOGGER.trace("Going to process {} mappings for {}", mappings.size(), mappingDesc); - - for (MappingImpl mapping: mappings) { - - if (mapping.getStrength() == MappingStrengthType.WEAK) { - // Evaluate weak mappings in a second run. - continue; - } - - UniformItemPath mappingOutputPathUniform = prismContext.toUniformPathKeepNull(mapping.getOutputPath()); - if (params.isFixTarget() && mappingOutputPathUniform != null && defaultTargetItemPath != null && !mappingOutputPathUniform.equivalent(defaultTargetItemPath)) { - throw new ExpressionEvaluationException("Target cannot be overridden in "+mappingDesc); - } - - if (params.getAPrioriTargetDelta() != null && mappingOutputPathUniform != null) { - ItemDelta aPrioriItemDelta = params.getAPrioriTargetDelta().findItemDelta(mappingOutputPathUniform); - if (mapping.getStrength() != MappingStrengthType.STRONG) { - if (aPrioriItemDelta != null && !aPrioriItemDelta.isEmpty()) { - continue; - } - } - } - - evaluateMapping(mapping, params.getContext(), task, result); - - PrismValueDeltaSetTriple mappingOutputTriple = mapping.getOutputTriple(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Output triple of mapping {}\n{}", mapping.getContextDescription(), - mappingOutputTriple==null?null:mappingOutputTriple.debugDump(1)); - } - - if (isMeaningful(mappingOutputTriple)) { - - MappingOutputStruct mappingOutputStruct = outputTripleMap.get(mappingOutputPathUniform); - if (mappingOutputStruct == null) { - mappingOutputStruct = new MappingOutputStruct<>(); - outputTripleMap.put(mappingOutputPathUniform, mappingOutputStruct); - } - - if (mapping.getStrength() == MappingStrengthType.STRONG) { - mappingOutputStruct.setStrongMappingWasUsed(true); - - if (!hasFullTargetObject && params.getTargetLoader() != null && aPrioriTargetObject != null && aPrioriTargetObject.getOid() != null) { - if (!params.getTargetLoader().isLoaded()) { - aPrioriTargetObject = params.getTargetLoader().load("strong mapping", task, result); - LOGGER.trace("Loaded object because of strong mapping: {}", aPrioriTargetObject); - hasFullTargetObject = true; - } - } - } - - PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); - if (outputTriple == null) { - mappingOutputStruct.setOutputTriple(mappingOutputTriple); - } else { - outputTriple.merge(mappingOutputTriple); - } - - } else { - LOGGER.trace("Output triple of mapping {} is NOT meaningful", mapping.getContextDescription()); - } - - } - - if (params.isEvaluateWeak()) { - // Second pass, evaluate only weak mappings - for (MappingImpl mapping: mappings) { - - if (mapping.getStrength() != MappingStrengthType.WEAK) { - continue; - } - - UniformItemPath mappingOutputPath = prismContext.toUniformPathKeepNull(mapping.getOutputPath()); - if (params.isFixTarget() && mappingOutputPath != null && defaultTargetItemPath != null && !mappingOutputPath.equivalent(defaultTargetItemPath)) { - throw new ExpressionEvaluationException("Target cannot be overridden in "+mappingDesc); - } - - MappingOutputStruct mappingOutputStruct = outputTripleMap.get(mappingOutputPath); - if (mappingOutputStruct == null) { - mappingOutputStruct = new MappingOutputStruct<>(); - outputTripleMap.put(mappingOutputPath, mappingOutputStruct); - } - - PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); - if (outputTriple != null && !outputTriple.getNonNegativeValues().isEmpty()) { - // Previous mapping produced zero/positive output. We do not need to evaluate weak mapping. - // - // Note: this might or might not be correct. The idea is that if previous mapping produced no positive values - // (e.g. because of condition switched from true to false) we might apply the weak mapping. - // - // TODO (original) this is not entirely correct. Previous mapping might have deleted all - // values. Also we may need the output of the weak mapping to correctly process - // non-tolerant values (to avoid removing the value that weak mapping produces). - // MID-3847 - continue; - } - - Item aPrioriTargetItem = null; - if (aPrioriTargetObject != null && mappingOutputPath != null) { - aPrioriTargetItem = aPrioriTargetObject.findItem(mappingOutputPath); - } - if (hasNoValue(aPrioriTargetItem)) { - - mappingOutputStruct.setWeakMappingWasUsed(true); - - evaluateMapping(mapping, params.getContext(), task, result); - - PrismValueDeltaSetTriple mappingOutputTriple = mapping.getOutputTriple(); - if (mappingOutputTriple != null) { - - // This may be counter-intuitive to load object after the mapping is executed - // But the mapping may not be activated (e.g. condition is false). And in that - // case we really do not want to trigger object loading. - // This is all not right. See MID-3847 - if (!hasFullTargetObject && params.getTargetLoader() != null && aPrioriTargetObject != null && aPrioriTargetObject.getOid() != null) { - if (!params.getTargetLoader().isLoaded()) { - aPrioriTargetObject = params.getTargetLoader().load("weak mapping", task, result); - LOGGER.trace("Loaded object because of weak mapping: {}", aPrioriTargetObject); - hasFullTargetObject = true; - } - } - if (aPrioriTargetObject != null && mappingOutputPath != null) { - aPrioriTargetItem = aPrioriTargetObject.findItem(mappingOutputPath); - } - if (!hasNoValue(aPrioriTargetItem)) { - continue; - } - - //noinspection ConstantConditions - if (outputTriple == null) { // this is currently always true (see above) - mappingOutputStruct.setOutputTriple(mappingOutputTriple); - } else { - outputTriple.merge(mappingOutputTriple); - } - } - - } - } - } - - MappingOutputProcessor processor = params.getProcessor(); - for (Entry> outputTripleMapEntry: outputTripleMap.entrySet()) { - UniformItemPath mappingOutputPath = outputTripleMapEntry.getKey(); - MappingOutputStruct mappingOutputStruct = outputTripleMapEntry.getValue(); - PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); - - boolean defaultProcessing; - if (processor != null) { - LOGGER.trace("Executing processor to process mapping evaluation results: {}", processor); - defaultProcessing = processor.process(mappingOutputPath, mappingOutputStruct); - } else { - defaultProcessing = true; - } - - if (defaultProcessing) { - - if (outputTriple == null) { - LOGGER.trace("{} expression resulted in null triple for {}, skipping", mappingDesc, targetContext); - continue; - } - - ItemDefinition targetItemDefinition; - if (mappingOutputPath != null) { - targetItemDefinition = targetObjectDefinition.findItemDefinition(mappingOutputPath); - if (targetItemDefinition == null) { - throw new SchemaException("No definition for item "+mappingOutputPath+" in "+targetObjectDefinition); - } - } else { - targetItemDefinition = params.getTargetItemDefinition(); - } - //noinspection unchecked - ItemDelta targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath); - - Item aPrioriTargetItem; - if (aPrioriTargetObject != null) { - aPrioriTargetItem = aPrioriTargetObject.findItem(mappingOutputPath); - } else { - aPrioriTargetItem = null; - } - - // WARNING - // Following code seems to be wrong. It is not very relativistic. It seems to always - // go for replace. - // It seems that it is only used for activation mappings (outbound and inbound). As - // these are quite special single-value properties then it seems to work fine - // (with the exception of MID-3418). Todo: make it more relativistic: MID-3419 - - if (targetContext.isAdd()) { - - Collection nonNegativeValues = outputTriple.getNonNegativeValues(); - if (nonNegativeValues.isEmpty()) { - LOGGER.trace("{} resulted in null or empty value for {}, skipping", mappingDesc, targetContext); - continue; - } - targetItemDelta.setValuesToReplace(PrismValueCollectionsUtil.cloneCollection(nonNegativeValues)); - - } else { - - // if we have fresh information (full shadow) AND the mapping used to derive the information was strong, - // we will consider all values (zero & plus sets) -- otherwise, we take only the "plus" (i.e. changed) set - - // the first case is necessary, because in some situations (e.g. when mapping is changed) - // the evaluator sees no differences w.r.t. real state, even if there is a difference - // - and we must have a way to push new information onto the resource - - Collection valuesToReplace; - - if (hasFullTargetObject && mappingOutputStruct.isStrongMappingWasUsed()) { - valuesToReplace = outputTriple.getNonNegativeValues(); - } else { - valuesToReplace = outputTriple.getPlusSet(); - } - - LOGGER.trace("{}: hasFullTargetObject={}, isStrongMappingWasUsed={}, valuesToReplace={}", - mappingDesc, hasFullTargetObject, mappingOutputStruct.isStrongMappingWasUsed(), valuesToReplace); - - if (!valuesToReplace.isEmpty()) { - - // if what we want to set is the same as is already in the shadow, we skip that - // (we insist on having full shadow, to be sure we work with current data) - - if (hasFullTargetObject && targetContext.isFresh() && aPrioriTargetItem != null) { - Collection valuesPresent = aPrioriTargetItem.getValues(); - if (PrismValueCollectionsUtil.equalsRealValues(valuesPresent, valuesToReplace)) { - LOGGER.trace("{} resulted in existing values for {}, skipping creation of a delta", mappingDesc, targetContext); - continue; - } - } - targetItemDelta.setValuesToReplace(PrismValueCollectionsUtil.cloneCollection(valuesToReplace)); - - applyEstematedOldValueInReplaceCase(targetItemDelta, outputTriple); - - } else if (outputTriple.hasMinusSet()) { - LOGGER.trace("{} resulted in null or empty value for {} and there is a minus set, resetting it (replace with empty)", mappingDesc, targetContext); - targetItemDelta.setValueToReplace(); - applyEstematedOldValueInReplaceCase(targetItemDelta, outputTriple); - - } else { - LOGGER.trace("{} resulted in null or empty value for {}, skipping", mappingDesc, targetContext); - } - - } - - if (targetItemDelta.isEmpty()) { - continue; - } - - LOGGER.trace("{} adding new delta for {}: {}", mappingDesc, targetContext, targetItemDelta); - targetContext.swallowToSecondaryDelta(targetItemDelta); - } - - } - - // Figure out recompute time - - for (MappingImpl mapping: mappings) { - XMLGregorianCalendar mappingNextRecomputeTime = mapping.getNextRecomputeTime(); - if (mappingNextRecomputeTime != null) { - if (mapping.isSatisfyCondition() && (nextRecomputeTime == null || nextRecomputeTime.compare(mappingNextRecomputeTime) == DatatypeConstants.GREATER)) { - nextRecomputeTime = mappingNextRecomputeTime; - // TODO: maybe better description? But consider storage requirements. We do not want to store too much. - triggerOriginDescription = mapping.getIdentifier(); - } - } - } - - if (nextRecomputeTime != null) { - NextRecompute nextRecompute = new NextRecompute(nextRecomputeTime, triggerOriginDescription); - nextRecompute.createTrigger(params.getAPrioriTargetObject(), targetObjectDefinition, targetContext); - } - - return outputTripleMap; - } - - private void applyEstematedOldValueInReplaceCase(ItemDelta targetItemDelta, - PrismValueDeltaSetTriple outputTriple) { - Collection nonPositiveValues = outputTriple.getNonPositiveValues(); - if (nonPositiveValues.isEmpty()) { - return; - } - targetItemDelta.setEstimatedOldValues(PrismValueCollectionsUtil.cloneCollection(nonPositiveValues)); - } - - private boolean isMeaningful(PrismValueDeltaSetTriple mappingOutputTriple) { - if (mappingOutputTriple == null) { - // this means: mapping not applicable - return false; - } - if (mappingOutputTriple.isEmpty()) { - // this means: no value produced - return true; - } - if (mappingOutputTriple.getZeroSet().isEmpty() && mappingOutputTriple.getPlusSet().isEmpty()) { - // Minus deltas are always meaningful, even with hashing (see below) - // This may be used e.g. to remove existing password. - return true; - } - if (hasNoOrHashedValuesOnly(mappingOutputTriple.getMinusSet()) && hasNoOrHashedValuesOnly(mappingOutputTriple.getZeroSet()) && hasNoOrHashedValuesOnly(mappingOutputTriple.getPlusSet())) { - // Used to skip application of mapping that produces only hashed protected values. - // Those values are useless, e.g. to set new password. If we would consider them as - // meaningful then a normal mapping with such values may prohibit application of - // a weak mapping. We want weak mapping in this case, e.g. to set a randomly-generated password. - // Not entirely correct. Maybe we need to filter this out in some other way? - return false; - } - return true; - } - - // Not entirely correct. Maybe we need to filter this out in some other way? - private boolean hasNoOrHashedValuesOnly(Collection set) { - if (set == null) { - return true; - } - for (V pval: set) { - Object val = pval.getRealValue(); - if (val instanceof ProtectedStringType) { - if (!((ProtectedStringType)val).isHashed()) { - return false; - } - } else { - return false; - } - } - return true; - } - - private boolean hasNoValue(Item aPrioriTargetItem) { - return aPrioriTargetItem == null - || (aPrioriTargetItem.isEmpty() && !aPrioriTargetItem.isIncomplete()); - } - - public MappingImpl createFocusMapping(final MappingFactory mappingFactory, - final LensContext context, final MappingType mappingType, ObjectType originObject, - ObjectDeltaObject focusOdo, AssignmentPathVariables assignmentPathVariables, PrismObject configuration, - XMLGregorianCalendar now, String contextDesc, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - Integer iteration = null; - String iterationToken = null; - if (focusOdo.getNewObject() != null) { - AH focusNewType = focusOdo.getNewObject().asObjectable(); - iteration = focusNewType.getIteration(); - iterationToken = focusNewType.getIterationToken(); - } else if (focusOdo.getOldObject() != null) { - AH focusOldType = focusOdo.getOldObject().asObjectable(); - iteration = focusOldType.getIteration(); - iterationToken = focusOldType.getIterationToken(); - } - return createFocusMapping(mappingFactory, context, mappingType, originObject, focusOdo, null, focusOdo.getAnyObject(), assignmentPathVariables, - iteration, iterationToken, configuration, now, contextDesc, task, result); - } - - public MappingImpl createFocusMapping( - final MappingFactory mappingFactory, final LensContext context, final MappingType mappingType, ObjectType originObject, - ObjectDeltaObject focusOdo, Source defaultSource, PrismObject defaultTargetObject, AssignmentPathVariables assignmentPathVariables, - Integer iteration, String iterationToken, PrismObject configuration, - XMLGregorianCalendar now, String contextDesc, final Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (!MappingImpl.isApplicableToChannel(mappingType, context.getChannel())) { - LOGGER.trace("Mapping {} not applicable to channel {}, skipping.", mappingType, context.getChannel()); - return null; - } - - ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { - private ItemPath outputPath; - private ItemDefinition outputDefinition; - @Override - public void setOutputPath(ItemPath outputPath) { - this.outputPath = outputPath; - } - - @Override - public void setOutputDefinition(ItemDefinition outputDefinition) { - this.outputDefinition = outputDefinition; - } - - @Override - public ValuePolicyType resolve() { - // TODO need to switch to ObjectValuePolicyEvaluator - if (outputDefinition.getItemName().equals(PasswordType.F_VALUE)) { - return credentialsProcessor.determinePasswordPolicy(context.getFocusContext()); - } - if (mappingType.getExpression() != null){ - List> evaluators = mappingType.getExpression().getExpressionEvaluator(); - if (evaluators != null) { - for (JAXBElement jaxbEvaluator : evaluators) { - Object object = jaxbEvaluator.getValue(); - if (object instanceof GenerateExpressionEvaluatorType && ((GenerateExpressionEvaluatorType) object).getValuePolicyRef() != null) { - ObjectReferenceType ref = ((GenerateExpressionEvaluatorType) object).getValuePolicyRef(); - try { - ValuePolicyType valuePolicyType = mappingFactory.getObjectResolver().resolve(ref, ValuePolicyType.class, - null, "resolving value policy for generate attribute "+ outputDefinition.getItemName()+" value", task, new OperationResult("Resolving value policy")); - if (valuePolicyType != null) { - return valuePolicyType; - } - } catch (CommonException ex) { - throw new SystemException(ex.getMessage(), ex); - } - } - } - - } - } - return null; - - } - }; - - ExpressionVariables variables = new ExpressionVariables(); - FOCUS_VARIABLE_NAMES.forEach(name -> variables.addVariableDefinition(name, focusOdo, focusOdo.getDefinition())); - variables.put(ExpressionConstants.VAR_ITERATION, iteration, Integer.class); - variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken, String.class); - variables.put(ExpressionConstants.VAR_CONFIGURATION, configuration, SystemConfigurationType.class); - variables.put(ExpressionConstants.VAR_OPERATION, context.getFocusContext().getOperation().getValue(), String.class); - variables.put(ExpressionConstants.VAR_SOURCE, originObject, ObjectType.class); - - TypedValue> defaultTargetContext = new TypedValue<>(defaultTargetObject); - Collection targetValues = ExpressionUtil.computeTargetValues(mappingType.getTarget(), defaultTargetContext, variables, mappingFactory.getObjectResolver(), contextDesc, prismContext, task, result); - - MappingImpl.Builder mappingBuilder = mappingFactory.createMappingBuilder(mappingType, contextDesc) - .sourceContext(focusOdo) - .defaultSource(defaultSource) - .targetContext(defaultTargetObject.getDefinition()) - .variables(variables) - .originalTargetValues(targetValues) - .originType(OriginType.USER_POLICY) - .originObject(originObject) - .objectResolver(objectResolver) - .valuePolicyResolver(stringPolicyResolver) - .rootNode(focusOdo) - .now(now); - - mappingBuilder = LensUtil.addAssignmentPathVariables(mappingBuilder, assignmentPathVariables, prismContext); - - MappingImpl mapping = mappingBuilder.build(); - - ItemPath itemPath = mapping.getOutputPath(); - if (itemPath == null) { - // no output element, i.e. this is a "validation mapping" - return mapping; - } - - if (defaultTargetObject != null) { - Item existingTargetItem = (Item) defaultTargetObject.findItem(itemPath); - if (existingTargetItem != null && !existingTargetItem.isEmpty() - && mapping.getStrength() == MappingStrengthType.WEAK) { - LOGGER.trace("Mapping {} is weak and target already has a value {}, skipping.", mapping, existingTargetItem); - return null; - } - } - - return mapping; - } - -// private Collection computeTargetValues(VariableBindingDefinitionType target, -// Object defaultTargetContext, ExpressionVariables variables, ObjectResolver objectResolver, String contextDesc, -// Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { -// if (target == null) { -// // Is this correct? What about default targets? -// return null; -// } -// -// ItemPathType itemPathType = target.getPath(); -// if (itemPathType == null) { -// // Is this correct? What about default targets? -// return null; -// } -// ItemPath path = itemPathType.getItemPath(); -// -// Object object = ExpressionUtil.resolvePath(path, variables, defaultTargetContext, objectResolver, contextDesc, task, result); -// if (object == null) { -// return new ArrayList<>(); -// } else if (object instanceof Item) { -// return ((Item) object).getValues(); -// } else if (object instanceof PrismValue) { -// return (List) Collections.singletonList((PrismValue) object); -// } else if (object instanceof ItemDeltaItem) { -// ItemDeltaItem idi = (ItemDeltaItem) object; -// PrismValueDeltaSetTriple triple = idi.toDeltaSetTriple(); -// return triple != null ? triple.getNonNegativeValues() : new ArrayList(); -// } else { -// throw new IllegalStateException("Unsupported target value(s): " + object.getClass() + " (" + object + ")"); -// } -// } - -} +/* + * Copyright (c) 2013-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens.projector.mappings; + +import java.util.*; +import java.util.Map.Entry; + +import javax.xml.bind.JAXBElement; +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.XMLGregorianCalendar; + +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.projector.*; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.repo.common.expression.Source; +import com.evolveum.midpoint.repo.common.expression.ValuePolicyResolver; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.path.UniformItemPath; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.expression.TypedValue; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.CommonException; +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.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.AssignmentHolderType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GenerateExpressionEvaluatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; +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.PasswordType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; + +/** + * @author Radovan Semancik + * + */ +@Component +public class MappingEvaluator { + + private static final Trace LOGGER = TraceManager.getTrace(MappingEvaluator.class); + + @Autowired private MappingFactory mappingFactory; + @Autowired private CredentialsProcessor credentialsProcessor; + @Autowired private ContextLoader contextLoader; + @Autowired private PrismContext prismContext; + @Autowired private ObjectResolver objectResolver; + + public PrismContext getPrismContext() { + return prismContext; + } + + static final List FOCUS_VARIABLE_NAMES = Arrays.asList(ExpressionConstants.VAR_FOCUS, ExpressionConstants.VAR_USER); + + public void evaluateMapping(MappingImpl mapping, + LensContext lensContext, Task task, OperationResult parentResult) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, CommunicationException { + evaluateMapping(mapping, lensContext, null, task, parentResult); + } + + public void evaluateMapping(MappingImpl mapping, + LensContext lensContext, LensProjectionContext projContext, Task task, OperationResult parentResult) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, CommunicationException { + + ExpressionEnvironment env = new ExpressionEnvironment<>(); + env.setLensContext(lensContext); + env.setProjectionContext(projContext); + env.setMapping(mapping); + env.setCurrentResult(parentResult); + env.setCurrentTask(task); + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); + + ObjectType originObject = mapping.getOriginObject(); + String objectOid, objectName, objectTypeName; + if (originObject != null) { + objectOid = originObject.getOid(); + objectName = String.valueOf(originObject.getName()); + objectTypeName = originObject.getClass().getSimpleName(); + } else { + objectOid = objectName = objectTypeName = null; + } + String mappingName = mapping.getItemName() != null ? mapping.getItemName().getLocalPart() : null; + + long start = System.currentTimeMillis(); + try { + task.recordState("Started evaluation of mapping " + mapping.getMappingContextDescription() + "."); + mapping.evaluate(task, parentResult); + task.recordState("Successfully finished evaluation of mapping " + mapping.getMappingContextDescription() + " in " + (System.currentTimeMillis()-start) + " ms."); + } catch (IllegalArgumentException e) { + task.recordState("Evaluation of mapping " + mapping.getMappingContextDescription() + " finished with error in " + (System.currentTimeMillis()-start) + " ms."); + throw new IllegalArgumentException(e.getMessage()+" in "+mapping.getContextDescription(), e); + } finally { + task.recordMappingOperation(objectOid, objectName, objectTypeName, mappingName, System.currentTimeMillis() - start); + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + if (lensContext.getInspector() != null) { + lensContext.getInspector().afterMappingEvaluation(lensContext, mapping); + } + } + } + + // TODO: unify OutboundProcessor.evaluateMapping() with MappingEvaluator.evaluateOutboundMapping(...) + public void evaluateOutboundMapping(final LensContext context, + final LensProjectionContext projCtx, List outboundMappings, + final ItemPath focusPropertyPath, final ItemPath projectionPropertyPath, + final MappingInitializer,PrismPropertyDefinition> initializer, MappingOutputProcessor> processor, + XMLGregorianCalendar now, final MappingTimeEval evaluateCurrent, boolean evaluateWeak, + String desc, final Task task, final OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + String projCtxDesc = projCtx.toHumanReadableString(); + PrismObject shadowNew = projCtx.getObjectNew(); + + MappingInitializer,PrismPropertyDefinition> internalInitializer = + builder -> { + + builder.addVariableDefinitions(ModelImplUtils.getDefaultExpressionVariables(context, projCtx)); + + builder.originType(OriginType.OUTBOUND); + builder.originObject(projCtx.getResource()); + + initializer.initialize(builder); + + return builder; + }; + + MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); + params.setMappingTypes(outboundMappings); + params.setMappingDesc(desc + " in projection " + projCtxDesc); + params.setNow(now); + params.setInitializer(internalInitializer); + params.setProcessor(processor); + params.setTargetLoader(new ProjectionMappingLoader<>(context, projCtx, contextLoader)); + params.setAPrioriTargetObject(shadowNew); + params.setAPrioriTargetDelta(LensUtil.findAPrioriDelta(context, projCtx)); + params.setTargetContext(projCtx); + params.setDefaultTargetItemPath(projectionPropertyPath); + if (context.getFocusContext() != null) { + params.setSourceContext(context.getFocusContext().getObjectDeltaObject()); + } + params.setEvaluateCurrent(evaluateCurrent); + params.setEvaluateWeak(evaluateWeak); + params.setContext(context); + params.setHasFullTargetObject(projCtx.hasFullShadow()); + evaluateMappingSetProjection(params, task, result); + } + + public Map> evaluateMappingSetProjection( + MappingEvaluatorParams params, + Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + String mappingDesc = params.getMappingDesc(); + LensElementContext targetContext = params.getTargetContext(); + PrismObjectDefinition targetObjectDefinition = targetContext.getObjectDefinition(); + ItemPath defaultTargetItemPath = params.getDefaultTargetItemPath(); + + Map> outputTripleMap = new HashMap<>(); + XMLGregorianCalendar nextRecomputeTime = null; + String triggerOriginDescription = null; + Collection mappingTypes = params.getMappingTypes(); + Collection> mappings = new ArrayList<>(mappingTypes.size()); + + for (MappingType mappingType: mappingTypes) { + + MappingImpl.Builder mappingBuilder = mappingFactory.createMappingBuilder(mappingType, mappingDesc); + String mappingName = null; + if (mappingType.getName() != null) { + mappingName = mappingType.getName(); + } + + if (!mappingBuilder.isApplicableToChannel(params.getContext().getChannel())) { + LOGGER.trace("Mapping {} not applicable to channel, skipping {}", mappingName, params.getContext().getChannel()); + continue; + } + + mappingBuilder.now(params.getNow()); + if (defaultTargetItemPath != null && targetObjectDefinition != null) { + D defaultTargetItemDef = targetObjectDefinition.findItemDefinition(defaultTargetItemPath); + mappingBuilder.defaultTargetDefinition(defaultTargetItemDef); + } else { + mappingBuilder.defaultTargetDefinition(params.getTargetItemDefinition()); + } + mappingBuilder.defaultTargetPath(defaultTargetItemPath); + mappingBuilder.targetContext(targetObjectDefinition); + + if (params.getSourceContext() != null) { + mappingBuilder.sourceContext(params.getSourceContext()); + } + + // Initialize mapping (using Inversion of Control) + MappingImpl.Builder initializedMappingBuilder = params.getInitializer().initialize(mappingBuilder); + + MappingImpl mapping = initializedMappingBuilder.build(); + boolean timeConstraintValid = mapping.evaluateTimeConstraintValid(task, result); + + if (params.getEvaluateCurrent() == MappingTimeEval.CURRENT && !timeConstraintValid) { + LOGGER.trace("Mapping {} is non-current, but evaluating current mappings, skipping {}", mappingName, params.getContext().getChannel()); + } else if (params.getEvaluateCurrent() == MappingTimeEval.FUTURE && timeConstraintValid) { + LOGGER.trace("Mapping {} is current, but evaluating non-current mappings, skipping {}", mappingName, params.getContext().getChannel()); + } else { + mappings.add(mapping); + } + } + + boolean hasFullTargetObject = params.hasFullTargetObject(); + PrismObject aPrioriTargetObject = params.getAPrioriTargetObject(); + + LOGGER.trace("Going to process {} mappings for {}", mappings.size(), mappingDesc); + + for (MappingImpl mapping: mappings) { + + if (mapping.getStrength() == MappingStrengthType.WEAK) { + // Evaluate weak mappings in a second run. + continue; + } + + UniformItemPath mappingOutputPathUniform = prismContext.toUniformPathKeepNull(mapping.getOutputPath()); + if (params.isFixTarget() && mappingOutputPathUniform != null && defaultTargetItemPath != null && !mappingOutputPathUniform.equivalent(defaultTargetItemPath)) { + throw new ExpressionEvaluationException("Target cannot be overridden in "+mappingDesc); + } + + if (params.getAPrioriTargetDelta() != null && mappingOutputPathUniform != null) { + ItemDelta aPrioriItemDelta = params.getAPrioriTargetDelta().findItemDelta(mappingOutputPathUniform); + if (mapping.getStrength() != MappingStrengthType.STRONG) { + if (aPrioriItemDelta != null && !aPrioriItemDelta.isEmpty()) { + continue; + } + } + } + + evaluateMapping(mapping, params.getContext(), task, result); + + PrismValueDeltaSetTriple mappingOutputTriple = mapping.getOutputTriple(); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Output triple of mapping {}\n{}", mapping.getContextDescription(), + mappingOutputTriple==null?null:mappingOutputTriple.debugDump(1)); + } + + if (isMeaningful(mappingOutputTriple)) { + + MappingOutputStruct mappingOutputStruct = outputTripleMap.get(mappingOutputPathUniform); + if (mappingOutputStruct == null) { + mappingOutputStruct = new MappingOutputStruct<>(); + outputTripleMap.put(mappingOutputPathUniform, mappingOutputStruct); + } + + if (mapping.getStrength() == MappingStrengthType.STRONG) { + mappingOutputStruct.setStrongMappingWasUsed(true); + + if (!hasFullTargetObject && params.getTargetLoader() != null && aPrioriTargetObject != null && aPrioriTargetObject.getOid() != null) { + if (!params.getTargetLoader().isLoaded()) { + aPrioriTargetObject = params.getTargetLoader().load("strong mapping", task, result); + LOGGER.trace("Loaded object because of strong mapping: {}", aPrioriTargetObject); + hasFullTargetObject = true; + } + } + } + + PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); + if (outputTriple == null) { + mappingOutputStruct.setOutputTriple(mappingOutputTriple); + } else { + outputTriple.merge(mappingOutputTriple); + } + + } else { + LOGGER.trace("Output triple of mapping {} is NOT meaningful", mapping.getContextDescription()); + } + + } + + if (params.isEvaluateWeak()) { + // Second pass, evaluate only weak mappings + for (MappingImpl mapping: mappings) { + + if (mapping.getStrength() != MappingStrengthType.WEAK) { + continue; + } + + UniformItemPath mappingOutputPath = prismContext.toUniformPathKeepNull(mapping.getOutputPath()); + if (params.isFixTarget() && mappingOutputPath != null && defaultTargetItemPath != null && !mappingOutputPath.equivalent(defaultTargetItemPath)) { + throw new ExpressionEvaluationException("Target cannot be overridden in "+mappingDesc); + } + + MappingOutputStruct mappingOutputStruct = outputTripleMap.get(mappingOutputPath); + if (mappingOutputStruct == null) { + mappingOutputStruct = new MappingOutputStruct<>(); + outputTripleMap.put(mappingOutputPath, mappingOutputStruct); + } + + PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); + if (outputTriple != null && !outputTriple.getNonNegativeValues().isEmpty()) { + // Previous mapping produced zero/positive output. We do not need to evaluate weak mapping. + // + // Note: this might or might not be correct. The idea is that if previous mapping produced no positive values + // (e.g. because of condition switched from true to false) we might apply the weak mapping. + // + // TODO (original) this is not entirely correct. Previous mapping might have deleted all + // values. Also we may need the output of the weak mapping to correctly process + // non-tolerant values (to avoid removing the value that weak mapping produces). + // MID-3847 + continue; + } + + Item aPrioriTargetItem = null; + if (aPrioriTargetObject != null && mappingOutputPath != null) { + aPrioriTargetItem = aPrioriTargetObject.findItem(mappingOutputPath); + } + if (hasNoValue(aPrioriTargetItem)) { + + mappingOutputStruct.setWeakMappingWasUsed(true); + + evaluateMapping(mapping, params.getContext(), task, result); + + PrismValueDeltaSetTriple mappingOutputTriple = mapping.getOutputTriple(); + if (mappingOutputTriple != null) { + + // This may be counter-intuitive to load object after the mapping is executed + // But the mapping may not be activated (e.g. condition is false). And in that + // case we really do not want to trigger object loading. + // This is all not right. See MID-3847 + if (!hasFullTargetObject && params.getTargetLoader() != null && aPrioriTargetObject != null && aPrioriTargetObject.getOid() != null) { + if (!params.getTargetLoader().isLoaded()) { + aPrioriTargetObject = params.getTargetLoader().load("weak mapping", task, result); + LOGGER.trace("Loaded object because of weak mapping: {}", aPrioriTargetObject); + hasFullTargetObject = true; + } + } + if (aPrioriTargetObject != null && mappingOutputPath != null) { + aPrioriTargetItem = aPrioriTargetObject.findItem(mappingOutputPath); + } + if (!hasNoValue(aPrioriTargetItem)) { + continue; + } + + //noinspection ConstantConditions + if (outputTriple == null) { // this is currently always true (see above) + mappingOutputStruct.setOutputTriple(mappingOutputTriple); + } else { + outputTriple.merge(mappingOutputTriple); + } + } + + } + } + } + + MappingOutputProcessor processor = params.getProcessor(); + for (Entry> outputTripleMapEntry: outputTripleMap.entrySet()) { + UniformItemPath mappingOutputPath = outputTripleMapEntry.getKey(); + MappingOutputStruct mappingOutputStruct = outputTripleMapEntry.getValue(); + PrismValueDeltaSetTriple outputTriple = mappingOutputStruct.getOutputTriple(); + + boolean defaultProcessing; + if (processor != null) { + LOGGER.trace("Executing processor to process mapping evaluation results: {}", processor); + defaultProcessing = processor.process(mappingOutputPath, mappingOutputStruct); + } else { + defaultProcessing = true; + } + + if (defaultProcessing) { + + if (outputTriple == null) { + LOGGER.trace("{} expression resulted in null triple for {}, skipping", mappingDesc, targetContext); + continue; + } + + ItemDefinition targetItemDefinition; + if (mappingOutputPath != null) { + targetItemDefinition = targetObjectDefinition.findItemDefinition(mappingOutputPath); + if (targetItemDefinition == null) { + throw new SchemaException("No definition for item "+mappingOutputPath+" in "+targetObjectDefinition); + } + } else { + targetItemDefinition = params.getTargetItemDefinition(); + } + //noinspection unchecked + ItemDelta targetItemDelta = targetItemDefinition.createEmptyDelta(mappingOutputPath); + + Item aPrioriTargetItem; + if (aPrioriTargetObject != null) { + aPrioriTargetItem = aPrioriTargetObject.findItem(mappingOutputPath); + } else { + aPrioriTargetItem = null; + } + + // WARNING + // Following code seems to be wrong. It is not very relativistic. It seems to always + // go for replace. + // It seems that it is only used for activation mappings (outbound and inbound). As + // these are quite special single-value properties then it seems to work fine + // (with the exception of MID-3418). Todo: make it more relativistic: MID-3419 + + if (targetContext.isAdd()) { + + Collection nonNegativeValues = outputTriple.getNonNegativeValues(); + if (nonNegativeValues.isEmpty()) { + LOGGER.trace("{} resulted in null or empty value for {}, skipping", mappingDesc, targetContext); + continue; + } + targetItemDelta.setValuesToReplace(PrismValueCollectionsUtil.cloneCollection(nonNegativeValues)); + + } else { + + // if we have fresh information (full shadow) AND the mapping used to derive the information was strong, + // we will consider all values (zero & plus sets) -- otherwise, we take only the "plus" (i.e. changed) set + + // the first case is necessary, because in some situations (e.g. when mapping is changed) + // the evaluator sees no differences w.r.t. real state, even if there is a difference + // - and we must have a way to push new information onto the resource + + Collection valuesToReplace; + + if (hasFullTargetObject && mappingOutputStruct.isStrongMappingWasUsed()) { + valuesToReplace = outputTriple.getNonNegativeValues(); + } else { + valuesToReplace = outputTriple.getPlusSet(); + } + + LOGGER.trace("{}: hasFullTargetObject={}, isStrongMappingWasUsed={}, valuesToReplace={}", + mappingDesc, hasFullTargetObject, mappingOutputStruct.isStrongMappingWasUsed(), valuesToReplace); + + if (!valuesToReplace.isEmpty()) { + + // if what we want to set is the same as is already in the shadow, we skip that + // (we insist on having full shadow, to be sure we work with current data) + + if (hasFullTargetObject && targetContext.isFresh() && aPrioriTargetItem != null) { + Collection valuesPresent = aPrioriTargetItem.getValues(); + if (PrismValueCollectionsUtil.equalsRealValues(valuesPresent, valuesToReplace)) { + LOGGER.trace("{} resulted in existing values for {}, skipping creation of a delta", mappingDesc, targetContext); + continue; + } + } + targetItemDelta.setValuesToReplace(PrismValueCollectionsUtil.cloneCollection(valuesToReplace)); + + applyEstematedOldValueInReplaceCase(targetItemDelta, outputTriple); + + } else if (outputTriple.hasMinusSet()) { + LOGGER.trace("{} resulted in null or empty value for {} and there is a minus set, resetting it (replace with empty)", mappingDesc, targetContext); + targetItemDelta.setValueToReplace(); + applyEstematedOldValueInReplaceCase(targetItemDelta, outputTriple); + + } else { + LOGGER.trace("{} resulted in null or empty value for {}, skipping", mappingDesc, targetContext); + } + + } + + if (targetItemDelta.isEmpty()) { + continue; + } + + LOGGER.trace("{} adding new delta for {}: {}", mappingDesc, targetContext, targetItemDelta); + targetContext.swallowToSecondaryDelta(targetItemDelta); + } + + } + + // Figure out recompute time + + for (MappingImpl mapping: mappings) { + XMLGregorianCalendar mappingNextRecomputeTime = mapping.getNextRecomputeTime(); + if (mappingNextRecomputeTime != null) { + if (mapping.isSatisfyCondition() && (nextRecomputeTime == null || nextRecomputeTime.compare(mappingNextRecomputeTime) == DatatypeConstants.GREATER)) { + nextRecomputeTime = mappingNextRecomputeTime; + // TODO: maybe better description? But consider storage requirements. We do not want to store too much. + triggerOriginDescription = mapping.getIdentifier(); + } + } + } + + if (nextRecomputeTime != null) { + NextRecompute nextRecompute = new NextRecompute(nextRecomputeTime, triggerOriginDescription); + nextRecompute.createTrigger(params.getAPrioriTargetObject(), targetObjectDefinition, targetContext); + } + + return outputTripleMap; + } + + private void applyEstematedOldValueInReplaceCase(ItemDelta targetItemDelta, + PrismValueDeltaSetTriple outputTriple) { + Collection nonPositiveValues = outputTriple.getNonPositiveValues(); + if (nonPositiveValues.isEmpty()) { + return; + } + targetItemDelta.setEstimatedOldValues(PrismValueCollectionsUtil.cloneCollection(nonPositiveValues)); + } + + private boolean isMeaningful(PrismValueDeltaSetTriple mappingOutputTriple) { + if (mappingOutputTriple == null) { + // this means: mapping not applicable + return false; + } + if (mappingOutputTriple.isEmpty()) { + // this means: no value produced + return true; + } + if (mappingOutputTriple.getZeroSet().isEmpty() && mappingOutputTriple.getPlusSet().isEmpty()) { + // Minus deltas are always meaningful, even with hashing (see below) + // This may be used e.g. to remove existing password. + return true; + } + if (hasNoOrHashedValuesOnly(mappingOutputTriple.getMinusSet()) && hasNoOrHashedValuesOnly(mappingOutputTriple.getZeroSet()) && hasNoOrHashedValuesOnly(mappingOutputTriple.getPlusSet())) { + // Used to skip application of mapping that produces only hashed protected values. + // Those values are useless, e.g. to set new password. If we would consider them as + // meaningful then a normal mapping with such values may prohibit application of + // a weak mapping. We want weak mapping in this case, e.g. to set a randomly-generated password. + // Not entirely correct. Maybe we need to filter this out in some other way? + return false; + } + return true; + } + + // Not entirely correct. Maybe we need to filter this out in some other way? + private boolean hasNoOrHashedValuesOnly(Collection set) { + if (set == null) { + return true; + } + for (V pval: set) { + Object val = pval.getRealValue(); + if (val instanceof ProtectedStringType) { + if (!((ProtectedStringType)val).isHashed()) { + return false; + } + } else { + return false; + } + } + return true; + } + + private boolean hasNoValue(Item aPrioriTargetItem) { + return aPrioriTargetItem == null + || (aPrioriTargetItem.isEmpty() && !aPrioriTargetItem.isIncomplete()); + } + + public MappingImpl createFocusMapping(final MappingFactory mappingFactory, + final LensContext context, final MappingType mappingType, ObjectType originObject, + ObjectDeltaObject focusOdo, AssignmentPathVariables assignmentPathVariables, PrismObject configuration, + XMLGregorianCalendar now, String contextDesc, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + Integer iteration = null; + String iterationToken = null; + if (focusOdo.getNewObject() != null) { + AH focusNewType = focusOdo.getNewObject().asObjectable(); + iteration = focusNewType.getIteration(); + iterationToken = focusNewType.getIterationToken(); + } else if (focusOdo.getOldObject() != null) { + AH focusOldType = focusOdo.getOldObject().asObjectable(); + iteration = focusOldType.getIteration(); + iterationToken = focusOldType.getIterationToken(); + } + return createFocusMapping(mappingFactory, context, mappingType, originObject, focusOdo, null, focusOdo.getAnyObject(), assignmentPathVariables, + iteration, iterationToken, configuration, now, contextDesc, task, result); + } + + public MappingImpl createFocusMapping( + final MappingFactory mappingFactory, final LensContext context, final MappingType mappingType, ObjectType originObject, + ObjectDeltaObject focusOdo, Source defaultSource, PrismObject defaultTargetObject, AssignmentPathVariables assignmentPathVariables, + Integer iteration, String iterationToken, PrismObject configuration, + XMLGregorianCalendar now, String contextDesc, final Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (!MappingImpl.isApplicableToChannel(mappingType, context.getChannel())) { + LOGGER.trace("Mapping {} not applicable to channel {}, skipping.", mappingType, context.getChannel()); + return null; + } + + ValuePolicyResolver stringPolicyResolver = new ValuePolicyResolver() { + private ItemPath outputPath; + private ItemDefinition outputDefinition; + @Override + public void setOutputPath(ItemPath outputPath) { + this.outputPath = outputPath; + } + + @Override + public void setOutputDefinition(ItemDefinition outputDefinition) { + this.outputDefinition = outputDefinition; + } + + @Override + public ValuePolicyType resolve() { + // TODO need to switch to ObjectValuePolicyEvaluator + if (outputDefinition.getItemName().equals(PasswordType.F_VALUE)) { + return credentialsProcessor.determinePasswordPolicy(context.getFocusContext()); + } + if (mappingType.getExpression() != null){ + List> evaluators = mappingType.getExpression().getExpressionEvaluator(); + if (evaluators != null) { + for (JAXBElement jaxbEvaluator : evaluators) { + Object object = jaxbEvaluator.getValue(); + if (object instanceof GenerateExpressionEvaluatorType && ((GenerateExpressionEvaluatorType) object).getValuePolicyRef() != null) { + ObjectReferenceType ref = ((GenerateExpressionEvaluatorType) object).getValuePolicyRef(); + try { + ValuePolicyType valuePolicyType = mappingFactory.getObjectResolver().resolve(ref, ValuePolicyType.class, + null, "resolving value policy for generate attribute "+ outputDefinition.getItemName()+" value", task, new OperationResult("Resolving value policy")); + if (valuePolicyType != null) { + return valuePolicyType; + } + } catch (CommonException ex) { + throw new SystemException(ex.getMessage(), ex); + } + } + } + + } + } + return null; + + } + }; + + ExpressionVariables variables = new ExpressionVariables(); + FOCUS_VARIABLE_NAMES.forEach(name -> variables.addVariableDefinition(name, focusOdo, focusOdo.getDefinition())); + variables.put(ExpressionConstants.VAR_ITERATION, iteration, Integer.class); + variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken, String.class); + variables.put(ExpressionConstants.VAR_CONFIGURATION, configuration, SystemConfigurationType.class); + variables.put(ExpressionConstants.VAR_OPERATION, context.getFocusContext().getOperation().getValue(), String.class); + variables.put(ExpressionConstants.VAR_SOURCE, originObject, ObjectType.class); + + TypedValue> defaultTargetContext = new TypedValue<>(defaultTargetObject); + Collection targetValues = ExpressionUtil.computeTargetValues(mappingType.getTarget(), defaultTargetContext, variables, mappingFactory.getObjectResolver(), contextDesc, prismContext, task, result); + + MappingImpl.Builder mappingBuilder = mappingFactory.createMappingBuilder(mappingType, contextDesc) + .sourceContext(focusOdo) + .defaultSource(defaultSource) + .targetContext(defaultTargetObject.getDefinition()) + .variables(variables) + .originalTargetValues(targetValues) + .originType(OriginType.USER_POLICY) + .originObject(originObject) + .objectResolver(objectResolver) + .valuePolicyResolver(stringPolicyResolver) + .rootNode(focusOdo) + .now(now); + + mappingBuilder = LensUtil.addAssignmentPathVariables(mappingBuilder, assignmentPathVariables, prismContext); + + MappingImpl mapping = mappingBuilder.build(); + + ItemPath itemPath = mapping.getOutputPath(); + if (itemPath == null) { + // no output element, i.e. this is a "validation mapping" + return mapping; + } + + if (defaultTargetObject != null) { + Item existingTargetItem = (Item) defaultTargetObject.findItem(itemPath); + if (existingTargetItem != null && !existingTargetItem.isEmpty() + && mapping.getStrength() == MappingStrengthType.WEAK) { + LOGGER.trace("Mapping {} is weak and target already has a value {}, skipping.", mapping, existingTargetItem); + return null; + } + } + + return mapping; + } + +// private Collection computeTargetValues(VariableBindingDefinitionType target, +// Object defaultTargetContext, ExpressionVariables variables, ObjectResolver objectResolver, String contextDesc, +// Task task, OperationResult result) throws SchemaException, ObjectNotFoundException { +// if (target == null) { +// // Is this correct? What about default targets? +// return null; +// } +// +// ItemPathType itemPathType = target.getPath(); +// if (itemPathType == null) { +// // Is this correct? What about default targets? +// return null; +// } +// ItemPath path = itemPathType.getItemPath(); +// +// Object object = ExpressionUtil.resolvePath(path, variables, defaultTargetContext, objectResolver, contextDesc, task, result); +// if (object == null) { +// return new ArrayList<>(); +// } else if (object instanceof Item) { +// return ((Item) object).getValues(); +// } else if (object instanceof PrismValue) { +// return (List) Collections.singletonList((PrismValue) object); +// } else if (object instanceof ItemDeltaItem) { +// ItemDeltaItem idi = (ItemDeltaItem) object; +// PrismValueDeltaSetTriple triple = idi.toDeltaSetTriple(); +// return triple != null ? triple.getNonNegativeValues() : new ArrayList(); +// } else { +// throw new IllegalStateException("Unsupported target value(s): " + object.getClass() + " (" + object + ")"); +// } +// } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/VariablesUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/VariablesUtil.java index 3436b2c9b07..b11e4c79bdc 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/VariablesUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/scripting/VariablesUtil.java @@ -1,281 +1,281 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.scripting; - -import com.evolveum.midpoint.common.StaticExpressionUtil; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.util.CloneUtil; -import com.evolveum.midpoint.prism.xml.XsdTypeMapper; -import com.evolveum.midpoint.repo.common.ObjectResolver; -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.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.expression.TypedValue; -import com.evolveum.midpoint.schema.expression.VariablesMap; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.QNameUtil; -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.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingVariableDefinitionType; -import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingVariablesDefinitionType; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.xml.bind.JAXBElement; -import javax.xml.namespace.QName; -import java.util.*; - -/** - * @author mederly - */ -public class VariablesUtil { - - private static final Trace LOGGER = TraceManager.getTrace(VariablesUtil.class); - - static class VariableResolutionContext { - @NotNull final ExpressionFactory expressionFactory; - @NotNull final ObjectResolver objectResolver; - @NotNull final PrismContext prismContext; - final ExpressionProfile expressionProfile; - @NotNull final Task task; - VariableResolutionContext(@NotNull ExpressionFactory expressionFactory, - @NotNull ObjectResolver objectResolver, @NotNull PrismContext prismContext, ExpressionProfile expressionProfile, @NotNull Task task) { - this.expressionFactory = expressionFactory; - this.objectResolver = objectResolver; - this.prismContext = prismContext; - this.expressionProfile = expressionProfile; - this.task = task; - } - } - - // We create immutable versions of prism variables to avoid unnecessary downstream cloning - @NotNull - static VariablesMap initialPreparation(VariablesMap initialVariables, - ScriptingVariablesDefinitionType derivedVariables, ExpressionFactory expressionFactory, ObjectResolver objectResolver, - PrismContext prismContext, ExpressionProfile expressionProfile, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - VariablesMap rv = new VariablesMap(); - addProvidedVariables(rv, initialVariables, task); - addDerivedVariables(rv, derivedVariables, - new VariableResolutionContext(expressionFactory, objectResolver, prismContext, expressionProfile, task), result); - return rv; - } - - private static void addProvidedVariables(VariablesMap resultingVariables, VariablesMap initialVariables, Task task) { - TypedValue taskValAndDef = new TypedValue<>(task.getUpdatedOrClonedTaskObject().asObjectable(), task.getUpdatedOrClonedTaskObject().getDefinition()); - putImmutableValue(resultingVariables, ExpressionConstants.VAR_TASK, taskValAndDef); - if (initialVariables != null) { - initialVariables.forEach((key, value) -> putImmutableValue(resultingVariables, key, value)); - } - } - - private static void addDerivedVariables(VariablesMap resultingVariables, - ScriptingVariablesDefinitionType definitions, VariableResolutionContext ctx, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (definitions == null) { - return; - } - for (ScriptingVariableDefinitionType definition : definitions.getVariable()) { - if (definition.getExpression() == null) { - continue; // todo or throw an exception? - } - String shortDesc = "scripting variable " + definition.getName(); - TypedValue valueAndDef; - if (definition.getExpression().getExpressionEvaluator().size() == 1 && - QNameUtil.match(SchemaConstantsGenerated.C_PATH, definition.getExpression().getExpressionEvaluator().get(0).getName())) { - valueAndDef = variableFromPathExpression(resultingVariables, definition.getExpression().getExpressionEvaluator().get(0), ctx, shortDesc, result); - } else { - valueAndDef = variableFromOtherExpression(resultingVariables, definition, ctx, shortDesc, result); - } - putImmutableValue(resultingVariables, definition.getName(), valueAndDef); - } - } - - private static TypedValue variableFromPathExpression(VariablesMap resultingVariables, - JAXBElement expressionEvaluator, VariableResolutionContext ctx, String shortDesc, OperationResult result) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (!(expressionEvaluator.getValue() instanceof ItemPathType)) { - throw new IllegalArgumentException("Path expression: expected ItemPathType but got " + expressionEvaluator.getValue()); - } - ItemPath itemPath = ctx.prismContext.toPath((ItemPathType) expressionEvaluator.getValue()); - return ExpressionUtil.resolvePathGetTypedValue(itemPath, createVariables(resultingVariables), false, null, ctx.objectResolver, ctx.prismContext, shortDesc, ctx.task, result); - } - - private static ExpressionVariables createVariables(VariablesMap variableMap) { - ExpressionVariables rv = new ExpressionVariables(); - VariablesMap clonedVariableMap = cloneIfNecessary(variableMap); - clonedVariableMap.forEach((name, value) -> rv.put(name, value)); - return rv; - } - - private static TypedValue variableFromOtherExpression(VariablesMap resultingVariables, - ScriptingVariableDefinitionType definition, VariableResolutionContext ctx, String shortDesc, - OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - ItemDefinition outputDefinition = determineOutputDefinition(definition, ctx, shortDesc); - Expression> expression = ctx.expressionFactory - .makeExpression(definition.getExpression(), outputDefinition, ctx.expressionProfile, shortDesc, ctx.task, result); - ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, createVariables(resultingVariables), shortDesc, ctx.task); - PrismValueDeltaSetTriple triple = ModelExpressionThreadLocalHolder - .evaluateAnyExpressionInContext(expression, context, ctx.task, result); - Collection resultingValues = triple.getNonNegativeValues(); - Object value; - if (definition.getMaxOccurs() != null && outputDefinition.isSingleValue() // cardinality of outputDefinition is derived solely from definition.maxOccurs (if specified) - || definition.getMaxOccurs() == null || resultingValues.size() <= 1) { - value = MiscUtil.getSingleValue(resultingValues, null, shortDesc); // unwrapping will occur when the value is used - } else { - value = unwrapPrismValues(resultingValues); - } - return new TypedValue(value, outputDefinition); - } - - // TODO shouldn't we unwrap collections of prism values in the same way as in ExpressionUtil.convertVariableValue ? - private static Collection unwrapPrismValues(Collection prismValues) { - Collection rv = new ArrayList<>(prismValues.size()); - for (Object value : prismValues) { - if (value instanceof PrismValue) { - rv.add(((PrismValue) value).getRealValue()); - } else { - rv.add(value); - } - } - return rv; - } - - private static ItemDefinition determineOutputDefinition(ScriptingVariableDefinitionType variableDefinition, - VariableResolutionContext ctx, String shortDesc) throws SchemaException { - List> evaluators = variableDefinition.getExpression().getExpressionEvaluator(); - boolean isValue = !evaluators.isEmpty() && QNameUtil.match(evaluators.get(0).getName(), SchemaConstants.C_VALUE); - QName elementName = new QName(variableDefinition.getName()); - if (variableDefinition.getType() != null) { - Integer maxOccurs; - if (variableDefinition.getMaxOccurs() != null) { - maxOccurs = XsdTypeMapper.multiplicityToInteger(variableDefinition.getMaxOccurs()); - } else if (isValue) { // if we have constant values we can try to guess - maxOccurs = evaluators.size() > 1 ? -1 : 1; - } else { - maxOccurs = null; // no idea - } - if (maxOccurs == null) { - maxOccurs = -1; // to be safe - } - return ctx.prismContext.getSchemaRegistry().createAdHocDefinition(elementName, variableDefinition.getType(), 0, maxOccurs); - } - if (isValue) { - return StaticExpressionUtil.deriveOutputDefinitionFromValueElements(elementName, evaluators, shortDesc, ctx.prismContext); - } else { - throw new SchemaException("The type of scripting variable " + variableDefinition.getName() + " is not defined"); - } - } - - private static void putImmutableValue(VariablesMap map, String name, TypedValue valueAndDef) { - map.put(name, makeImmutable(valueAndDef)); - } - - @NotNull - public static VariablesMap cloneIfNecessary(@NotNull VariablesMap variables) { - VariablesMap rv = new VariablesMap(); - variables.forEach((key, value) -> rv.put(key, cloneIfNecessary(key, value))); - return rv; - } - - @Nullable - public static TypedValue cloneIfNecessary(String name, TypedValue valueAndDef) { - T valueClone = (T) cloneIfNecessary(name, valueAndDef.getValue()); - if (valueClone == valueAndDef.getValue()) { - return valueAndDef; - } else { - return valueAndDef.createTransformed(valueClone); - } - } - - @Nullable - public static T cloneIfNecessary(String name, T value) { - if (value == null) { - return null; - } - T immutableOrNull = tryMakingImmutable(value); - if (immutableOrNull != null) { - return immutableOrNull; - } else { - try { - return CloneUtil.clone(value); - } catch (Throwable t) { - LOGGER.warn("Scripting variable value {} of type {} couldn't be cloned. Using original.", name, value.getClass()); - return value; - } - } - } - - - public static TypedValue makeImmutable(TypedValue valueAndDef) { - T immutableValue = (T) makeImmutableValue(valueAndDef.getValue()); - if (immutableValue == valueAndDef.getValue()) { - return valueAndDef; - } else { - valueAndDef.setValue(immutableValue); - } - return valueAndDef; - } - - public static T makeImmutableValue(T value) { - T rv = tryMakingImmutable(value); - return rv != null ? rv : value; - } - - @SuppressWarnings("unchecked") - @Nullable - public static T tryMakingImmutable(T value) { - if (value instanceof Containerable) { - PrismContainerValue pcv = ((Containerable) value).asPrismContainerValue(); - if (!pcv.isImmutable()) { - return (T) pcv.createImmutableClone().asContainerable(); - } else { - return value; - } - } else if (value instanceof Referencable) { - PrismReferenceValue prv = ((Referencable) value).asReferenceValue(); - if (!prv.isImmutable()) { - return (T) prv.createImmutableClone().asReferencable(); - } else { - return value; - } - } else if (value instanceof PrismValue) { - PrismValue pval = (PrismValue) value; - if (!pval.isImmutable()) { - return (T) pval.createImmutableClone(); - } else { - return (T) pval; - } - } else if (value instanceof Item) { - Item item = (Item) value; - if (!item.isImmutable()) { - return (T) item.createImmutableClone(); - } else { - return (T) item; - } - } else { - return null; - } - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.scripting; + +import com.evolveum.midpoint.common.StaticExpressionUtil; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.prism.xml.XsdTypeMapper; +import com.evolveum.midpoint.repo.common.ObjectResolver; +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.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.expression.TypedValue; +import com.evolveum.midpoint.schema.expression.VariablesMap; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.QNameUtil; +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.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingVariableDefinitionType; +import com.evolveum.midpoint.xml.ns._public.model.scripting_3.ScriptingVariablesDefinitionType; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.xml.bind.JAXBElement; +import javax.xml.namespace.QName; +import java.util.*; + +/** + * @author mederly + */ +public class VariablesUtil { + + private static final Trace LOGGER = TraceManager.getTrace(VariablesUtil.class); + + static class VariableResolutionContext { + @NotNull final ExpressionFactory expressionFactory; + @NotNull final ObjectResolver objectResolver; + @NotNull final PrismContext prismContext; + final ExpressionProfile expressionProfile; + @NotNull final Task task; + VariableResolutionContext(@NotNull ExpressionFactory expressionFactory, + @NotNull ObjectResolver objectResolver, @NotNull PrismContext prismContext, ExpressionProfile expressionProfile, @NotNull Task task) { + this.expressionFactory = expressionFactory; + this.objectResolver = objectResolver; + this.prismContext = prismContext; + this.expressionProfile = expressionProfile; + this.task = task; + } + } + + // We create immutable versions of prism variables to avoid unnecessary downstream cloning + @NotNull + static VariablesMap initialPreparation(VariablesMap initialVariables, + ScriptingVariablesDefinitionType derivedVariables, ExpressionFactory expressionFactory, ObjectResolver objectResolver, + PrismContext prismContext, ExpressionProfile expressionProfile, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + VariablesMap rv = new VariablesMap(); + addProvidedVariables(rv, initialVariables, task); + addDerivedVariables(rv, derivedVariables, + new VariableResolutionContext(expressionFactory, objectResolver, prismContext, expressionProfile, task), result); + return rv; + } + + private static void addProvidedVariables(VariablesMap resultingVariables, VariablesMap initialVariables, Task task) { + TypedValue taskValAndDef = new TypedValue<>(task.getUpdatedOrClonedTaskObject().asObjectable(), task.getUpdatedOrClonedTaskObject().getDefinition()); + putImmutableValue(resultingVariables, ExpressionConstants.VAR_TASK, taskValAndDef); + if (initialVariables != null) { + initialVariables.forEach((key, value) -> putImmutableValue(resultingVariables, key, value)); + } + } + + private static void addDerivedVariables(VariablesMap resultingVariables, + ScriptingVariablesDefinitionType definitions, VariableResolutionContext ctx, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + if (definitions == null) { + return; + } + for (ScriptingVariableDefinitionType definition : definitions.getVariable()) { + if (definition.getExpression() == null) { + continue; // todo or throw an exception? + } + String shortDesc = "scripting variable " + definition.getName(); + TypedValue valueAndDef; + if (definition.getExpression().getExpressionEvaluator().size() == 1 && + QNameUtil.match(SchemaConstantsGenerated.C_PATH, definition.getExpression().getExpressionEvaluator().get(0).getName())) { + valueAndDef = variableFromPathExpression(resultingVariables, definition.getExpression().getExpressionEvaluator().get(0), ctx, shortDesc, result); + } else { + valueAndDef = variableFromOtherExpression(resultingVariables, definition, ctx, shortDesc, result); + } + putImmutableValue(resultingVariables, definition.getName(), valueAndDef); + } + } + + private static TypedValue variableFromPathExpression(VariablesMap resultingVariables, + JAXBElement expressionEvaluator, VariableResolutionContext ctx, String shortDesc, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (!(expressionEvaluator.getValue() instanceof ItemPathType)) { + throw new IllegalArgumentException("Path expression: expected ItemPathType but got " + expressionEvaluator.getValue()); + } + ItemPath itemPath = ctx.prismContext.toPath((ItemPathType) expressionEvaluator.getValue()); + return ExpressionUtil.resolvePathGetTypedValue(itemPath, createVariables(resultingVariables), false, null, ctx.objectResolver, ctx.prismContext, shortDesc, ctx.task, result); + } + + private static ExpressionVariables createVariables(VariablesMap variableMap) { + ExpressionVariables rv = new ExpressionVariables(); + VariablesMap clonedVariableMap = cloneIfNecessary(variableMap); + clonedVariableMap.forEach((name, value) -> rv.put(name, value)); + return rv; + } + + private static TypedValue variableFromOtherExpression(VariablesMap resultingVariables, + ScriptingVariableDefinitionType definition, VariableResolutionContext ctx, String shortDesc, + OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + ItemDefinition outputDefinition = determineOutputDefinition(definition, ctx, shortDesc); + Expression> expression = ctx.expressionFactory + .makeExpression(definition.getExpression(), outputDefinition, ctx.expressionProfile, shortDesc, ctx.task, result); + ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, createVariables(resultingVariables), shortDesc, ctx.task); + PrismValueDeltaSetTriple triple = ModelExpressionThreadLocalHolder + .evaluateAnyExpressionInContext(expression, context, ctx.task, result); + Collection resultingValues = triple.getNonNegativeValues(); + Object value; + if (definition.getMaxOccurs() != null && outputDefinition.isSingleValue() // cardinality of outputDefinition is derived solely from definition.maxOccurs (if specified) + || definition.getMaxOccurs() == null || resultingValues.size() <= 1) { + value = MiscUtil.getSingleValue(resultingValues, null, shortDesc); // unwrapping will occur when the value is used + } else { + value = unwrapPrismValues(resultingValues); + } + return new TypedValue(value, outputDefinition); + } + + // TODO shouldn't we unwrap collections of prism values in the same way as in ExpressionUtil.convertVariableValue ? + private static Collection unwrapPrismValues(Collection prismValues) { + Collection rv = new ArrayList<>(prismValues.size()); + for (Object value : prismValues) { + if (value instanceof PrismValue) { + rv.add(((PrismValue) value).getRealValue()); + } else { + rv.add(value); + } + } + return rv; + } + + private static ItemDefinition determineOutputDefinition(ScriptingVariableDefinitionType variableDefinition, + VariableResolutionContext ctx, String shortDesc) throws SchemaException { + List> evaluators = variableDefinition.getExpression().getExpressionEvaluator(); + boolean isValue = !evaluators.isEmpty() && QNameUtil.match(evaluators.get(0).getName(), SchemaConstants.C_VALUE); + QName elementName = new QName(variableDefinition.getName()); + if (variableDefinition.getType() != null) { + Integer maxOccurs; + if (variableDefinition.getMaxOccurs() != null) { + maxOccurs = XsdTypeMapper.multiplicityToInteger(variableDefinition.getMaxOccurs()); + } else if (isValue) { // if we have constant values we can try to guess + maxOccurs = evaluators.size() > 1 ? -1 : 1; + } else { + maxOccurs = null; // no idea + } + if (maxOccurs == null) { + maxOccurs = -1; // to be safe + } + return ctx.prismContext.getSchemaRegistry().createAdHocDefinition(elementName, variableDefinition.getType(), 0, maxOccurs); + } + if (isValue) { + return StaticExpressionUtil.deriveOutputDefinitionFromValueElements(elementName, evaluators, shortDesc, ctx.prismContext); + } else { + throw new SchemaException("The type of scripting variable " + variableDefinition.getName() + " is not defined"); + } + } + + private static void putImmutableValue(VariablesMap map, String name, TypedValue valueAndDef) { + map.put(name, makeImmutable(valueAndDef)); + } + + @NotNull + public static VariablesMap cloneIfNecessary(@NotNull VariablesMap variables) { + VariablesMap rv = new VariablesMap(); + variables.forEach((key, value) -> rv.put(key, cloneIfNecessary(key, value))); + return rv; + } + + @Nullable + public static TypedValue cloneIfNecessary(String name, TypedValue valueAndDef) { + T valueClone = (T) cloneIfNecessary(name, valueAndDef.getValue()); + if (valueClone == valueAndDef.getValue()) { + return valueAndDef; + } else { + return valueAndDef.createTransformed(valueClone); + } + } + + @Nullable + public static T cloneIfNecessary(String name, T value) { + if (value == null) { + return null; + } + T immutableOrNull = tryMakingImmutable(value); + if (immutableOrNull != null) { + return immutableOrNull; + } else { + try { + return CloneUtil.clone(value); + } catch (Throwable t) { + LOGGER.warn("Scripting variable value {} of type {} couldn't be cloned. Using original.", name, value.getClass()); + return value; + } + } + } + + + public static TypedValue makeImmutable(TypedValue valueAndDef) { + T immutableValue = (T) makeImmutableValue(valueAndDef.getValue()); + if (immutableValue == valueAndDef.getValue()) { + return valueAndDef; + } else { + valueAndDef.setValue(immutableValue); + } + return valueAndDef; + } + + public static T makeImmutableValue(T value) { + T rv = tryMakingImmutable(value); + return rv != null ? rv : value; + } + + @SuppressWarnings("unchecked") + @Nullable + public static T tryMakingImmutable(T value) { + if (value instanceof Containerable) { + PrismContainerValue pcv = ((Containerable) value).asPrismContainerValue(); + if (!pcv.isImmutable()) { + return (T) pcv.createImmutableClone().asContainerable(); + } else { + return value; + } + } else if (value instanceof Referencable) { + PrismReferenceValue prv = ((Referencable) value).asReferenceValue(); + if (!prv.isImmutable()) { + return (T) prv.createImmutableClone().asReferencable(); + } else { + return value; + } + } else if (value instanceof PrismValue) { + PrismValue pval = (PrismValue) value; + if (!pval.isImmutable()) { + return (T) pval.createImmutableClone(); + } else { + return (T) pval; + } + } else if (value instanceof Item) { + Item item = (Item) value; + if (!item.isImmutable()) { + return (T) item.createImmutableClone(); + } else { + return (T) item; + } + } else { + return null; + } + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java index 95eaed7fd8c..90b4d5079a8 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/GuiProfileCompiler.java @@ -1,558 +1,558 @@ -/* - * Copyright (c) 2018-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.security; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; -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.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; -import com.evolveum.midpoint.model.api.authentication.CompiledGuiProfile; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; -import com.evolveum.midpoint.model.api.context.EvaluatedAssignmentTarget; -import com.evolveum.midpoint.model.api.util.DeputyUtils; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.controller.CollectionProcessor; -import com.evolveum.midpoint.model.impl.lens.AssignmentCollector; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.security.api.Authorization; -import com.evolveum.midpoint.security.api.AuthorizationTransformer; -import com.evolveum.midpoint.security.api.DelegatorWithOtherPrivilegesLimitations; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.QNameUtil; -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; - -/** - * Compiles user interface profile for a particular user. The profile contains essential information needed to efficiently render - * user interface pages for specified user. - * - * This methods in this component may be quite costly to invoke. Therefore it should NOT be invoked for every request. - * The methods are supposed to be invoked once (or several times) during user's session. The result of this method should be - * cached in web session (in principal). - * - * @author Radovan semancik - */ -@Component -public class GuiProfileCompiler { - - private static final Trace LOGGER = TraceManager.getTrace(GuiProfileCompiler.class); - - @Autowired private SecurityHelper securityHelper; - @Autowired private SystemObjectCache systemObjectCache; - @Autowired private RelationRegistry relationRegistry; - @Autowired private PrismContext prismContext; - @Autowired private CollectionProcessor collectionProcessor; - @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; - - @Autowired private AssignmentCollector assignmentCollector; - - - - @Autowired - @Qualifier("cacheRepositoryService") - private RepositoryService repositoryService; - - public void compileUserProfile(GuiProfiledPrincipal principal, PrismObject systemConfiguration, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - - principal.setApplicableSecurityPolicy(securityHelper.locateSecurityPolicy(principal.getFocus().asPrismObject(), systemConfiguration, task, result)); - - List adminGuiConfigurations = new ArrayList<>(); - collect(adminGuiConfigurations, principal, authorizationTransformer, task, result); - - CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, result); - principal.setCompiledGuiProfile(compiledGuiProfile); - } - - private void collect(List adminGuiConfigurations, GuiProfiledPrincipal principal, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) throws SchemaException { - FocusType focusType = principal.getFocus(); - - Collection> evaluatedAssignments = assignmentCollector.collect(focusType.asPrismObject(), true, task, result); - Collection authorizations = principal.getAuthorities(); - for (EvaluatedAssignment assignment : evaluatedAssignments) { - if (assignment.isValid()) { - addAuthorizations(authorizations, assignment.getAuthorizations(), authorizationTransformer); - adminGuiConfigurations.addAll(assignment.getAdminGuiConfigurations()); - } - for (EvaluatedAssignmentTarget target : assignment.getRoles().getNonNegativeValues()) { - if (target.isValid() && target.getTarget() != null && target.getTarget().asObjectable() instanceof UserType - && DeputyUtils.isDelegationPath(target.getAssignmentPath(), relationRegistry)) { - List limitations = DeputyUtils.extractLimitations(target.getAssignmentPath()); - principal.addDelegatorWithOtherPrivilegesLimitations(new DelegatorWithOtherPrivilegesLimitations( - (UserType) target.getTarget().asObjectable(), limitations)); - } - } - } - - if (focusType instanceof UserType && ((UserType)focusType).getAdminGuiConfiguration() != null) { - // config from the user object should go last (to be applied as the last one) - adminGuiConfigurations.add(((UserType)focusType).getAdminGuiConfiguration()); - } - - } - - private void addAuthorizations(Collection targetCollection, Collection sourceCollection, AuthorizationTransformer authorizationTransformer) { - if (sourceCollection == null) { - return; - } - for (Authorization autz: sourceCollection) { - if (authorizationTransformer == null) { - targetCollection.add(autz); - } else { - Collection transformedAutzs = authorizationTransformer.transform(autz); - if (transformedAutzs != null) { - targetCollection.addAll(transformedAutzs); - } - } - } - } - - public CompiledGuiProfile compileUserProfile(@NotNull List adminGuiConfigurations, - PrismObject systemConfiguration, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - - AdminGuiConfigurationType globalAdminGuiConfig = null; - if (systemConfiguration != null) { - globalAdminGuiConfig = systemConfiguration.asObjectable().getAdminGuiConfiguration(); - } - // if there's no admin config at all, return null (to preserve original behavior) - if (adminGuiConfigurations.isEmpty() && globalAdminGuiConfig == null) { - return null; - } - - CompiledGuiProfile composite = new CompiledGuiProfile(); - if (globalAdminGuiConfig != null) { - applyAdminGuiConfiguration(composite, globalAdminGuiConfig, task, result); - } - for (AdminGuiConfigurationType adminGuiConfiguration: adminGuiConfigurations) { - applyAdminGuiConfiguration(composite, adminGuiConfiguration, task, result); - } - return composite; - } - - private void applyAdminGuiConfiguration(CompiledGuiProfile composite, AdminGuiConfigurationType adminGuiConfiguration, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - if (adminGuiConfiguration == null) { - return; - } - adminGuiConfiguration.getAdditionalMenuLink().forEach(additionalMenuLink -> composite.getAdditionalMenuLink().add(additionalMenuLink.clone())); - adminGuiConfiguration.getUserDashboardLink().forEach(userDashboardLink -> composite.getUserDashboardLink().add(userDashboardLink.clone())); - if (adminGuiConfiguration.getDefaultTimezone() != null) { - composite.setDefaultTimezone(adminGuiConfiguration.getDefaultTimezone()); - } - if (adminGuiConfiguration.getPreferredDataLanguage() != null) { - composite.setPreferredDataLanguage(adminGuiConfiguration.getPreferredDataLanguage()); - } - if (adminGuiConfiguration.isEnableExperimentalFeatures() != null) { - composite.setEnableExperimentalFeatures(adminGuiConfiguration.isEnableExperimentalFeatures()); - } - if (adminGuiConfiguration.getDefaultExportSettings() != null) { - composite.setDefaultExportSettings(adminGuiConfiguration.getDefaultExportSettings().clone()); - } - if (adminGuiConfiguration.getDisplayFormats() != null){ - composite.setDisplayFormats(adminGuiConfiguration.getDisplayFormats().clone()); - } - - applyViews(composite, adminGuiConfiguration.getObjectLists(), task, result); // Compatibility, deprecated - applyViews(composite, adminGuiConfiguration.getObjectCollectionViews(), task, result); - - if (adminGuiConfiguration.getObjectForms() != null) { - if (composite.getObjectForms() == null) { - composite.setObjectForms(adminGuiConfiguration.getObjectForms().clone()); - } else { - for (ObjectFormType objectForm: adminGuiConfiguration.getObjectForms().getObjectForm()) { - joinForms(composite.getObjectForms(), objectForm.clone()); - } - } - } - if (adminGuiConfiguration.getObjectDetails() != null) { - if (composite.getObjectDetails() == null) { - composite.setObjectDetails(adminGuiConfiguration.getObjectDetails().clone()); - } else { - for (GuiObjectDetailsPageType objectDetails: adminGuiConfiguration.getObjectDetails().getObjectDetailsPage()) { - joinObjectDetails(composite.getObjectDetails(), objectDetails); - } - } - } - if (adminGuiConfiguration.getUserDashboard() != null) { - if (composite.getUserDashboard() == null) { - composite.setUserDashboard(adminGuiConfiguration.getUserDashboard().clone()); - } else { - for (DashboardWidgetType widget: adminGuiConfiguration.getUserDashboard().getWidget()) { - mergeWidget(composite, widget); - } - } - } - for (UserInterfaceFeatureType feature: adminGuiConfiguration.getFeature()) { - mergeFeature(composite, feature.clone()); - } - - - if (adminGuiConfiguration.getFeedbackMessagesHook() != null) { - composite.setFeedbackMessagesHook(adminGuiConfiguration.getFeedbackMessagesHook().clone()); - } - - if (adminGuiConfiguration.getRoleManagement() != null && - adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { - if (composite.getRoleManagement() != null && composite.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { - // the greater value wins (so it is possible to give an exception to selected users) - Integer newValue = Math.max( - adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit(), - composite.getRoleManagement().getAssignmentApprovalRequestLimit()); - composite.getRoleManagement().setAssignmentApprovalRequestLimit(newValue); - } else { - if (composite.getRoleManagement() == null) { - composite.setRoleManagement(new AdminGuiConfigurationRoleManagementType()); - } - composite.getRoleManagement().setAssignmentApprovalRequestLimit( - adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit()); - } - } - } - - private void applyViews(CompiledGuiProfile composite, GuiObjectListViewsType viewsType, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - if (viewsType == null) { - return; - } - - if (viewsType.getDefault() != null) { - if (composite.getDefaultObjectCollectionView() == null) { - composite.setDefaultObjectCollectionView(new CompiledObjectCollectionView()); - } - compileView(composite.getDefaultObjectCollectionView(), viewsType.getDefault(), task, result); - } - - for (GuiObjectListViewType objectCollectionView : viewsType.getObjectList()) { // Compatibility, legacy - applyView(composite, objectCollectionView, task, result); - } - - for (GuiObjectListViewType objectCollectionView : viewsType.getObjectCollectionView()) { - applyView(composite, objectCollectionView, task, result); - } - } - - private void applyView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType, Task task, OperationResult result) { - try { - CompiledObjectCollectionView existingView = findOrCreateMatchingView(composite, objectListViewType); - compileView(existingView, objectListViewType, task, result); - } catch (Throwable e) { - // Do not let any error stop processing here. This code is used during user login. An error here can stop login procedure. We do not - // want that. E.g. wrong adminGuiConfig may prohibit login on administrator, therefore ruining any chance of fixing the situation. - // This is also handled somewhere up the call stack. But we want to handle it also here. Otherwise an error in one collection would - // mean that entire configuration processing will be stopped. We do not want that. We want to skip processing of just that one wrong view. - LOGGER.error("Error compiling user profile, view '{}': {}", determineViewIdentifier(objectListViewType), e.getMessage(), e); - } - } - - - private CompiledObjectCollectionView findOrCreateMatchingView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType) { - QName objectType = objectListViewType.getType(); - String viewIdentifier = determineViewIdentifier(objectListViewType); - CompiledObjectCollectionView existingView = composite.findObjectCollectionView(objectType, viewIdentifier); - if (existingView == null) { - existingView = new CompiledObjectCollectionView(objectType, viewIdentifier); - composite.getObjectCollectionViews().add(existingView); - } - return existingView; - } - - private String determineViewIdentifier(GuiObjectListViewType objectListViewType) { - String viewIdentifier = objectListViewType.getIdentifier(); - if (viewIdentifier != null) { - return viewIdentifier; - } - String viewName = objectListViewType.getName(); - if (viewName != null) { - // legacy, deprecated - return viewName; - } - CollectionRefSpecificationType collection = objectListViewType.getCollection(); - if (collection == null) { - return objectListViewType.getType().getLocalPart(); - } - ObjectReferenceType collectionRef = collection.getCollectionRef(); - if (collectionRef == null) { - return objectListViewType.getType().getLocalPart(); - } - return collectionRef.getOid(); - } - - private void compileView(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - compileActions(existingView, objectListViewType); - compileAdditionalPanels(existingView, objectListViewType); - compileColumns(existingView, objectListViewType); - compileDisplay(existingView, objectListViewType); - compileDistinct(existingView, objectListViewType); - compileSorting(existingView, objectListViewType); - compileCounting(existingView, objectListViewType); - compileDisplayOrder(existingView, objectListViewType); - compileSearchBox(existingView, objectListViewType); - compileCollection(existingView, objectListViewType, task, result); - compileRefreshInterval(existingView, objectListViewType); - } - - private void compileActions(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - List newActions = objectListViewType.getAction(); - for (GuiActionType newAction: newActions) { - // TODO: check for action duplication/override - existingView.getActions().add(newAction); // No need to clone, CompiledObjectCollectionView is not prism - } - - } - - private void compileAdditionalPanels(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - GuiObjectListViewAdditionalPanelsType newAdditionalPanels = objectListViewType.getAdditionalPanels(); - if (newAdditionalPanels == null) { - return; - } - // TODO: later: merge additional panel definitions - existingView.setAdditionalPanels(newAdditionalPanels); - } - - private void compileCollection(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - CollectionRefSpecificationType collectionSpec = objectListViewType.getCollection(); - if (collectionSpec == null) { - ObjectReferenceType collectionRef = objectListViewType.getCollectionRef(); - if (collectionRef == null) { - return; - } - // Legacy, deprecated - collectionSpec = new CollectionRefSpecificationType(); - collectionSpec.setCollectionRef(collectionRef.clone()); - } - if (existingView.getCollection() != null) { - LOGGER.debug("Redefining collection in view {}", existingView.getViewIdentifier()); - } - existingView.setCollection(collectionSpec); - - compileCollection(existingView, collectionSpec, task, result); - } - - private void compileCollection(CompiledObjectCollectionView existingView, CollectionRefSpecificationType collectionSpec, Task task, OperationResult result) - throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, - ExpressionEvaluationException, ObjectNotFoundException { - - QName targetObjectType = existingView.getObjectType(); - Class targetTypeClass = ObjectType.class; - if (targetObjectType != null) { - targetTypeClass = ObjectTypes.getObjectTypeFromTypeQName(targetObjectType).getClassDefinition(); - } - collectionProcessor.compileObjectCollectionView(existingView, collectionSpec, targetTypeClass, task, result); - } - - private void compileColumns(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - List newColumns = objectListViewType.getColumn(); - if (newColumns == null || newColumns.isEmpty()) { - return; - } - // Not very efficient algorithm. But must do for now. - List existingColumns = existingView.getColumns(); - existingColumns.addAll(newColumns); - List orderedList = ModelImplUtils.orderCustomColumns(existingColumns); - existingColumns.clear(); - existingColumns.addAll(orderedList); - } - - private void compileDisplay(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - DisplayType newDisplay = objectListViewType.getDisplay(); - if (newDisplay == null) { - return; - } - if (existingView.getDisplay() == null) { - existingView.setDisplay(newDisplay); - } - MiscSchemaUtil.mergeDisplay(existingView.getDisplay(), newDisplay); - } - - private void compileDistinct(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - DistinctSearchOptionType newDistinct = objectListViewType.getDistinct(); - if (newDistinct == null) { - return; - } - existingView.setDistinct(newDistinct); - } - - private void compileSorting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - Boolean newDisableSorting = objectListViewType.isDisableSorting(); - if (newDisableSorting != null) { - existingView.setDisableSorting(newDisableSorting); - } - } - - private void compileRefreshInterval(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - Integer refreshInterval = objectListViewType.getRefreshInterval(); - if (refreshInterval != null) { - existingView.setRefreshInterval(refreshInterval); - } - } - - private void compileCounting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - Boolean newDisableCounting = objectListViewType.isDisableCounting(); - if (newDisableCounting != null) { - existingView.setDisableCounting(newDisableCounting); - } - } - - private void compileDisplayOrder(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType){ - Integer newDisplayOrder = objectListViewType.getDisplayOrder(); - if (newDisplayOrder != null){ - existingView.setDisplayOrder(newDisplayOrder); - } - } - - private void compileSearchBox(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { - SearchBoxConfigurationType newSearchBoxConfig = objectListViewType.getSearchBoxConfiguration(); - if (newSearchBoxConfig == null) { - return; - } - // TODO: merge - existingView.setSearchBoxConfiguration(newSearchBoxConfig); - } - - private void joinForms(ObjectFormsType objectForms, ObjectFormType newForm) { - objectForms.getObjectForm().removeIf(currentForm -> isTheSameObjectForm(currentForm, newForm)); - objectForms.getObjectForm().add(newForm.clone().id(null)); - } - - private void joinObjectDetails(GuiObjectDetailsSetType objectDetailsSet, GuiObjectDetailsPageType newObjectDetails) { - objectDetailsSet.getObjectDetailsPage().removeIf(currentDetails -> isTheSameObjectType(currentDetails, newObjectDetails)); - objectDetailsSet.getObjectDetailsPage().add(newObjectDetails.clone()); - } - - private boolean isTheSameObjectType(AbstractObjectTypeConfigurationType oldConf, AbstractObjectTypeConfigurationType newConf) { - return QNameUtil.match(oldConf.getType(), newConf.getType()); - } - - private boolean isTheSameObjectForm(ObjectFormType oldForm, ObjectFormType newForm){ - if (!isTheSameObjectType(oldForm,newForm)) { - return false; - } - if (oldForm.isIncludeDefaultForms() != null && - newForm.isIncludeDefaultForms() != null){ - return true; - } - if (oldForm.getFormSpecification() == null && newForm.getFormSpecification() == null) { - String oldFormPanelUri = oldForm.getFormSpecification().getPanelUri(); - String newFormPanelUri = newForm.getFormSpecification().getPanelUri(); - if (oldFormPanelUri != null && oldFormPanelUri.equals(newFormPanelUri)) { - return true; - } - - String oldFormPanelClass = oldForm.getFormSpecification().getPanelClass(); - String newFormPanelClass = newForm.getFormSpecification().getPanelClass(); - if (oldFormPanelClass != null && oldFormPanelClass.equals(newFormPanelClass)) { - return true; - } - - String oldFormRefOid = oldForm.getFormSpecification().getFormRef() == null ? - null : oldForm.getFormSpecification().getFormRef().getOid(); - String newFormRefOid = newForm.getFormSpecification().getFormRef() == null ? - null : newForm.getFormSpecification().getFormRef().getOid(); - if (oldFormRefOid != null && oldFormRefOid.equals(newFormRefOid)) { - return true; - } - } - return false; - } - - private void mergeWidget(CompiledGuiProfile composite, DashboardWidgetType newWidget) { - String newWidgetIdentifier = newWidget.getIdentifier(); - DashboardWidgetType compositeWidget = composite.findUserDashboardWidget(newWidgetIdentifier); - if (compositeWidget == null) { - composite.getUserDashboard().getWidget().add(newWidget.clone()); - } else { - mergeWidget(compositeWidget, newWidget); - } - } - - private void mergeWidget(DashboardWidgetType compositeWidget, DashboardWidgetType newWidget) { - mergeFeature(compositeWidget, newWidget, UserInterfaceElementVisibilityType.VACANT); - // merge other widget properties (in the future) - } - - private void mergeFeature(CompiledGuiProfile composite, UserInterfaceFeatureType newFeature) { - String newIdentifier = newFeature.getIdentifier(); - UserInterfaceFeatureType compositeFeature = composite.findFeature(newIdentifier); - if (compositeFeature == null) { - composite.getFeatures().add(newFeature.clone()); - } else { - mergeFeature(compositeFeature, newFeature, UserInterfaceElementVisibilityType.AUTOMATIC); - } - } - - private void mergeFeature(T compositeFeature, T newFeature, UserInterfaceElementVisibilityType defaultVisibility) { - UserInterfaceElementVisibilityType newCompositeVisibility = mergeVisibility(compositeFeature.getVisibility(), newFeature.getVisibility(), defaultVisibility); - compositeFeature.setVisibility(newCompositeVisibility); - } - - private UserInterfaceElementVisibilityType mergeVisibility( - UserInterfaceElementVisibilityType compositeVisibility, UserInterfaceElementVisibilityType newVisibility, UserInterfaceElementVisibilityType defaultVisibility) { - if (compositeVisibility == null) { - compositeVisibility = defaultVisibility; - } - if (newVisibility == null) { - newVisibility = defaultVisibility; - } - if (compositeVisibility == UserInterfaceElementVisibilityType.HIDDEN || newVisibility == UserInterfaceElementVisibilityType.HIDDEN) { - return UserInterfaceElementVisibilityType.HIDDEN; - } - if (compositeVisibility == UserInterfaceElementVisibilityType.VISIBLE || newVisibility == UserInterfaceElementVisibilityType.VISIBLE) { - return UserInterfaceElementVisibilityType.VISIBLE; - } - if (compositeVisibility == UserInterfaceElementVisibilityType.AUTOMATIC || newVisibility == UserInterfaceElementVisibilityType.AUTOMATIC) { - return UserInterfaceElementVisibilityType.AUTOMATIC; - } - return UserInterfaceElementVisibilityType.VACANT; - } - - public CompiledGuiProfile getGlobalCompiledGuiProfile(Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(parentResult); - if (systemConfiguration == null) { - return null; - } - List adminGuiConfigurations = new ArrayList<>(); - CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, parentResult); - // TODO: cache compiled profile - return compiledGuiProfile; - } - - -} +/* + * Copyright (c) 2018-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.security; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; +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.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.model.api.authentication.CompiledGuiProfile; +import com.evolveum.midpoint.model.api.context.EvaluatedAssignment; +import com.evolveum.midpoint.model.api.context.EvaluatedAssignmentTarget; +import com.evolveum.midpoint.model.api.util.DeputyUtils; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.controller.CollectionProcessor; +import com.evolveum.midpoint.model.impl.lens.AssignmentCollector; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.security.api.Authorization; +import com.evolveum.midpoint.security.api.AuthorizationTransformer; +import com.evolveum.midpoint.security.api.DelegatorWithOtherPrivilegesLimitations; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.QNameUtil; +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; + +/** + * Compiles user interface profile for a particular user. The profile contains essential information needed to efficiently render + * user interface pages for specified user. + * + * This methods in this component may be quite costly to invoke. Therefore it should NOT be invoked for every request. + * The methods are supposed to be invoked once (or several times) during user's session. The result of this method should be + * cached in web session (in principal). + * + * @author Radovan semancik + */ +@Component +public class GuiProfileCompiler { + + private static final Trace LOGGER = TraceManager.getTrace(GuiProfileCompiler.class); + + @Autowired private SecurityHelper securityHelper; + @Autowired private SystemObjectCache systemObjectCache; + @Autowired private RelationRegistry relationRegistry; + @Autowired private PrismContext prismContext; + @Autowired private CollectionProcessor collectionProcessor; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; + + @Autowired private AssignmentCollector assignmentCollector; + + + + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; + + public void compileUserProfile(GuiProfiledPrincipal principal, PrismObject systemConfiguration, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + + principal.setApplicableSecurityPolicy(securityHelper.locateSecurityPolicy(principal.getFocus().asPrismObject(), systemConfiguration, task, result)); + + List adminGuiConfigurations = new ArrayList<>(); + collect(adminGuiConfigurations, principal, authorizationTransformer, task, result); + + CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, result); + principal.setCompiledGuiProfile(compiledGuiProfile); + } + + private void collect(List adminGuiConfigurations, GuiProfiledPrincipal principal, AuthorizationTransformer authorizationTransformer, Task task, OperationResult result) throws SchemaException { + FocusType focusType = principal.getFocus(); + + Collection> evaluatedAssignments = assignmentCollector.collect(focusType.asPrismObject(), true, task, result); + Collection authorizations = principal.getAuthorities(); + for (EvaluatedAssignment assignment : evaluatedAssignments) { + if (assignment.isValid()) { + addAuthorizations(authorizations, assignment.getAuthorizations(), authorizationTransformer); + adminGuiConfigurations.addAll(assignment.getAdminGuiConfigurations()); + } + for (EvaluatedAssignmentTarget target : assignment.getRoles().getNonNegativeValues()) { + if (target.isValid() && target.getTarget() != null && target.getTarget().asObjectable() instanceof UserType + && DeputyUtils.isDelegationPath(target.getAssignmentPath(), relationRegistry)) { + List limitations = DeputyUtils.extractLimitations(target.getAssignmentPath()); + principal.addDelegatorWithOtherPrivilegesLimitations(new DelegatorWithOtherPrivilegesLimitations( + (UserType) target.getTarget().asObjectable(), limitations)); + } + } + } + + if (focusType instanceof UserType && ((UserType)focusType).getAdminGuiConfiguration() != null) { + // config from the user object should go last (to be applied as the last one) + adminGuiConfigurations.add(((UserType)focusType).getAdminGuiConfiguration()); + } + + } + + private void addAuthorizations(Collection targetCollection, Collection sourceCollection, AuthorizationTransformer authorizationTransformer) { + if (sourceCollection == null) { + return; + } + for (Authorization autz: sourceCollection) { + if (authorizationTransformer == null) { + targetCollection.add(autz); + } else { + Collection transformedAutzs = authorizationTransformer.transform(autz); + if (transformedAutzs != null) { + targetCollection.addAll(transformedAutzs); + } + } + } + } + + public CompiledGuiProfile compileUserProfile(@NotNull List adminGuiConfigurations, + PrismObject systemConfiguration, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + + AdminGuiConfigurationType globalAdminGuiConfig = null; + if (systemConfiguration != null) { + globalAdminGuiConfig = systemConfiguration.asObjectable().getAdminGuiConfiguration(); + } + // if there's no admin config at all, return null (to preserve original behavior) + if (adminGuiConfigurations.isEmpty() && globalAdminGuiConfig == null) { + return null; + } + + CompiledGuiProfile composite = new CompiledGuiProfile(); + if (globalAdminGuiConfig != null) { + applyAdminGuiConfiguration(composite, globalAdminGuiConfig, task, result); + } + for (AdminGuiConfigurationType adminGuiConfiguration: adminGuiConfigurations) { + applyAdminGuiConfiguration(composite, adminGuiConfiguration, task, result); + } + return composite; + } + + private void applyAdminGuiConfiguration(CompiledGuiProfile composite, AdminGuiConfigurationType adminGuiConfiguration, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + if (adminGuiConfiguration == null) { + return; + } + adminGuiConfiguration.getAdditionalMenuLink().forEach(additionalMenuLink -> composite.getAdditionalMenuLink().add(additionalMenuLink.clone())); + adminGuiConfiguration.getUserDashboardLink().forEach(userDashboardLink -> composite.getUserDashboardLink().add(userDashboardLink.clone())); + if (adminGuiConfiguration.getDefaultTimezone() != null) { + composite.setDefaultTimezone(adminGuiConfiguration.getDefaultTimezone()); + } + if (adminGuiConfiguration.getPreferredDataLanguage() != null) { + composite.setPreferredDataLanguage(adminGuiConfiguration.getPreferredDataLanguage()); + } + if (adminGuiConfiguration.isEnableExperimentalFeatures() != null) { + composite.setEnableExperimentalFeatures(adminGuiConfiguration.isEnableExperimentalFeatures()); + } + if (adminGuiConfiguration.getDefaultExportSettings() != null) { + composite.setDefaultExportSettings(adminGuiConfiguration.getDefaultExportSettings().clone()); + } + if (adminGuiConfiguration.getDisplayFormats() != null){ + composite.setDisplayFormats(adminGuiConfiguration.getDisplayFormats().clone()); + } + + applyViews(composite, adminGuiConfiguration.getObjectLists(), task, result); // Compatibility, deprecated + applyViews(composite, adminGuiConfiguration.getObjectCollectionViews(), task, result); + + if (adminGuiConfiguration.getObjectForms() != null) { + if (composite.getObjectForms() == null) { + composite.setObjectForms(adminGuiConfiguration.getObjectForms().clone()); + } else { + for (ObjectFormType objectForm: adminGuiConfiguration.getObjectForms().getObjectForm()) { + joinForms(composite.getObjectForms(), objectForm.clone()); + } + } + } + if (adminGuiConfiguration.getObjectDetails() != null) { + if (composite.getObjectDetails() == null) { + composite.setObjectDetails(adminGuiConfiguration.getObjectDetails().clone()); + } else { + for (GuiObjectDetailsPageType objectDetails: adminGuiConfiguration.getObjectDetails().getObjectDetailsPage()) { + joinObjectDetails(composite.getObjectDetails(), objectDetails); + } + } + } + if (adminGuiConfiguration.getUserDashboard() != null) { + if (composite.getUserDashboard() == null) { + composite.setUserDashboard(adminGuiConfiguration.getUserDashboard().clone()); + } else { + for (DashboardWidgetType widget: adminGuiConfiguration.getUserDashboard().getWidget()) { + mergeWidget(composite, widget); + } + } + } + for (UserInterfaceFeatureType feature: adminGuiConfiguration.getFeature()) { + mergeFeature(composite, feature.clone()); + } + + + if (adminGuiConfiguration.getFeedbackMessagesHook() != null) { + composite.setFeedbackMessagesHook(adminGuiConfiguration.getFeedbackMessagesHook().clone()); + } + + if (adminGuiConfiguration.getRoleManagement() != null && + adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { + if (composite.getRoleManagement() != null && composite.getRoleManagement().getAssignmentApprovalRequestLimit() != null) { + // the greater value wins (so it is possible to give an exception to selected users) + Integer newValue = Math.max( + adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit(), + composite.getRoleManagement().getAssignmentApprovalRequestLimit()); + composite.getRoleManagement().setAssignmentApprovalRequestLimit(newValue); + } else { + if (composite.getRoleManagement() == null) { + composite.setRoleManagement(new AdminGuiConfigurationRoleManagementType()); + } + composite.getRoleManagement().setAssignmentApprovalRequestLimit( + adminGuiConfiguration.getRoleManagement().getAssignmentApprovalRequestLimit()); + } + } + } + + private void applyViews(CompiledGuiProfile composite, GuiObjectListViewsType viewsType, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + if (viewsType == null) { + return; + } + + if (viewsType.getDefault() != null) { + if (composite.getDefaultObjectCollectionView() == null) { + composite.setDefaultObjectCollectionView(new CompiledObjectCollectionView()); + } + compileView(composite.getDefaultObjectCollectionView(), viewsType.getDefault(), task, result); + } + + for (GuiObjectListViewType objectCollectionView : viewsType.getObjectList()) { // Compatibility, legacy + applyView(composite, objectCollectionView, task, result); + } + + for (GuiObjectListViewType objectCollectionView : viewsType.getObjectCollectionView()) { + applyView(composite, objectCollectionView, task, result); + } + } + + private void applyView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType, Task task, OperationResult result) { + try { + CompiledObjectCollectionView existingView = findOrCreateMatchingView(composite, objectListViewType); + compileView(existingView, objectListViewType, task, result); + } catch (Throwable e) { + // Do not let any error stop processing here. This code is used during user login. An error here can stop login procedure. We do not + // want that. E.g. wrong adminGuiConfig may prohibit login on administrator, therefore ruining any chance of fixing the situation. + // This is also handled somewhere up the call stack. But we want to handle it also here. Otherwise an error in one collection would + // mean that entire configuration processing will be stopped. We do not want that. We want to skip processing of just that one wrong view. + LOGGER.error("Error compiling user profile, view '{}': {}", determineViewIdentifier(objectListViewType), e.getMessage(), e); + } + } + + + private CompiledObjectCollectionView findOrCreateMatchingView(CompiledGuiProfile composite, GuiObjectListViewType objectListViewType) { + QName objectType = objectListViewType.getType(); + String viewIdentifier = determineViewIdentifier(objectListViewType); + CompiledObjectCollectionView existingView = composite.findObjectCollectionView(objectType, viewIdentifier); + if (existingView == null) { + existingView = new CompiledObjectCollectionView(objectType, viewIdentifier); + composite.getObjectCollectionViews().add(existingView); + } + return existingView; + } + + private String determineViewIdentifier(GuiObjectListViewType objectListViewType) { + String viewIdentifier = objectListViewType.getIdentifier(); + if (viewIdentifier != null) { + return viewIdentifier; + } + String viewName = objectListViewType.getName(); + if (viewName != null) { + // legacy, deprecated + return viewName; + } + CollectionRefSpecificationType collection = objectListViewType.getCollection(); + if (collection == null) { + return objectListViewType.getType().getLocalPart(); + } + ObjectReferenceType collectionRef = collection.getCollectionRef(); + if (collectionRef == null) { + return objectListViewType.getType().getLocalPart(); + } + return collectionRef.getOid(); + } + + private void compileView(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + compileActions(existingView, objectListViewType); + compileAdditionalPanels(existingView, objectListViewType); + compileColumns(existingView, objectListViewType); + compileDisplay(existingView, objectListViewType); + compileDistinct(existingView, objectListViewType); + compileSorting(existingView, objectListViewType); + compileCounting(existingView, objectListViewType); + compileDisplayOrder(existingView, objectListViewType); + compileSearchBox(existingView, objectListViewType); + compileCollection(existingView, objectListViewType, task, result); + compileRefreshInterval(existingView, objectListViewType); + } + + private void compileActions(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + List newActions = objectListViewType.getAction(); + for (GuiActionType newAction: newActions) { + // TODO: check for action duplication/override + existingView.getActions().add(newAction); // No need to clone, CompiledObjectCollectionView is not prism + } + + } + + private void compileAdditionalPanels(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + GuiObjectListViewAdditionalPanelsType newAdditionalPanels = objectListViewType.getAdditionalPanels(); + if (newAdditionalPanels == null) { + return; + } + // TODO: later: merge additional panel definitions + existingView.setAdditionalPanels(newAdditionalPanels); + } + + private void compileCollection(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + CollectionRefSpecificationType collectionSpec = objectListViewType.getCollection(); + if (collectionSpec == null) { + ObjectReferenceType collectionRef = objectListViewType.getCollectionRef(); + if (collectionRef == null) { + return; + } + // Legacy, deprecated + collectionSpec = new CollectionRefSpecificationType(); + collectionSpec.setCollectionRef(collectionRef.clone()); + } + if (existingView.getCollection() != null) { + LOGGER.debug("Redefining collection in view {}", existingView.getViewIdentifier()); + } + existingView.setCollection(collectionSpec); + + compileCollection(existingView, collectionSpec, task, result); + } + + private void compileCollection(CompiledObjectCollectionView existingView, CollectionRefSpecificationType collectionSpec, Task task, OperationResult result) + throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, ObjectNotFoundException { + + QName targetObjectType = existingView.getObjectType(); + Class targetTypeClass = ObjectType.class; + if (targetObjectType != null) { + targetTypeClass = ObjectTypes.getObjectTypeFromTypeQName(targetObjectType).getClassDefinition(); + } + collectionProcessor.compileObjectCollectionView(existingView, collectionSpec, targetTypeClass, task, result); + } + + private void compileColumns(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + List newColumns = objectListViewType.getColumn(); + if (newColumns == null || newColumns.isEmpty()) { + return; + } + // Not very efficient algorithm. But must do for now. + List existingColumns = existingView.getColumns(); + existingColumns.addAll(newColumns); + List orderedList = MiscSchemaUtil.orderCustomColumns(existingColumns); + existingColumns.clear(); + existingColumns.addAll(orderedList); + } + + private void compileDisplay(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + DisplayType newDisplay = objectListViewType.getDisplay(); + if (newDisplay == null) { + return; + } + if (existingView.getDisplay() == null) { + existingView.setDisplay(newDisplay); + } + MiscSchemaUtil.mergeDisplay(existingView.getDisplay(), newDisplay); + } + + private void compileDistinct(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + DistinctSearchOptionType newDistinct = objectListViewType.getDistinct(); + if (newDistinct == null) { + return; + } + existingView.setDistinct(newDistinct); + } + + private void compileSorting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + Boolean newDisableSorting = objectListViewType.isDisableSorting(); + if (newDisableSorting != null) { + existingView.setDisableSorting(newDisableSorting); + } + } + + private void compileRefreshInterval(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + Integer refreshInterval = objectListViewType.getRefreshInterval(); + if (refreshInterval != null) { + existingView.setRefreshInterval(refreshInterval); + } + } + + private void compileCounting(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + Boolean newDisableCounting = objectListViewType.isDisableCounting(); + if (newDisableCounting != null) { + existingView.setDisableCounting(newDisableCounting); + } + } + + private void compileDisplayOrder(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType){ + Integer newDisplayOrder = objectListViewType.getDisplayOrder(); + if (newDisplayOrder != null){ + existingView.setDisplayOrder(newDisplayOrder); + } + } + + private void compileSearchBox(CompiledObjectCollectionView existingView, GuiObjectListViewType objectListViewType) { + SearchBoxConfigurationType newSearchBoxConfig = objectListViewType.getSearchBoxConfiguration(); + if (newSearchBoxConfig == null) { + return; + } + // TODO: merge + existingView.setSearchBoxConfiguration(newSearchBoxConfig); + } + + private void joinForms(ObjectFormsType objectForms, ObjectFormType newForm) { + objectForms.getObjectForm().removeIf(currentForm -> isTheSameObjectForm(currentForm, newForm)); + objectForms.getObjectForm().add(newForm.clone().id(null)); + } + + private void joinObjectDetails(GuiObjectDetailsSetType objectDetailsSet, GuiObjectDetailsPageType newObjectDetails) { + objectDetailsSet.getObjectDetailsPage().removeIf(currentDetails -> isTheSameObjectType(currentDetails, newObjectDetails)); + objectDetailsSet.getObjectDetailsPage().add(newObjectDetails.clone()); + } + + private boolean isTheSameObjectType(AbstractObjectTypeConfigurationType oldConf, AbstractObjectTypeConfigurationType newConf) { + return QNameUtil.match(oldConf.getType(), newConf.getType()); + } + + private boolean isTheSameObjectForm(ObjectFormType oldForm, ObjectFormType newForm){ + if (!isTheSameObjectType(oldForm,newForm)) { + return false; + } + if (oldForm.isIncludeDefaultForms() != null && + newForm.isIncludeDefaultForms() != null){ + return true; + } + if (oldForm.getFormSpecification() == null && newForm.getFormSpecification() == null) { + String oldFormPanelUri = oldForm.getFormSpecification().getPanelUri(); + String newFormPanelUri = newForm.getFormSpecification().getPanelUri(); + if (oldFormPanelUri != null && oldFormPanelUri.equals(newFormPanelUri)) { + return true; + } + + String oldFormPanelClass = oldForm.getFormSpecification().getPanelClass(); + String newFormPanelClass = newForm.getFormSpecification().getPanelClass(); + if (oldFormPanelClass != null && oldFormPanelClass.equals(newFormPanelClass)) { + return true; + } + + String oldFormRefOid = oldForm.getFormSpecification().getFormRef() == null ? + null : oldForm.getFormSpecification().getFormRef().getOid(); + String newFormRefOid = newForm.getFormSpecification().getFormRef() == null ? + null : newForm.getFormSpecification().getFormRef().getOid(); + if (oldFormRefOid != null && oldFormRefOid.equals(newFormRefOid)) { + return true; + } + } + return false; + } + + private void mergeWidget(CompiledGuiProfile composite, DashboardWidgetType newWidget) { + String newWidgetIdentifier = newWidget.getIdentifier(); + DashboardWidgetType compositeWidget = composite.findUserDashboardWidget(newWidgetIdentifier); + if (compositeWidget == null) { + composite.getUserDashboard().getWidget().add(newWidget.clone()); + } else { + mergeWidget(compositeWidget, newWidget); + } + } + + private void mergeWidget(DashboardWidgetType compositeWidget, DashboardWidgetType newWidget) { + mergeFeature(compositeWidget, newWidget, UserInterfaceElementVisibilityType.VACANT); + // merge other widget properties (in the future) + } + + private void mergeFeature(CompiledGuiProfile composite, UserInterfaceFeatureType newFeature) { + String newIdentifier = newFeature.getIdentifier(); + UserInterfaceFeatureType compositeFeature = composite.findFeature(newIdentifier); + if (compositeFeature == null) { + composite.getFeatures().add(newFeature.clone()); + } else { + mergeFeature(compositeFeature, newFeature, UserInterfaceElementVisibilityType.AUTOMATIC); + } + } + + private void mergeFeature(T compositeFeature, T newFeature, UserInterfaceElementVisibilityType defaultVisibility) { + UserInterfaceElementVisibilityType newCompositeVisibility = mergeVisibility(compositeFeature.getVisibility(), newFeature.getVisibility(), defaultVisibility); + compositeFeature.setVisibility(newCompositeVisibility); + } + + private UserInterfaceElementVisibilityType mergeVisibility( + UserInterfaceElementVisibilityType compositeVisibility, UserInterfaceElementVisibilityType newVisibility, UserInterfaceElementVisibilityType defaultVisibility) { + if (compositeVisibility == null) { + compositeVisibility = defaultVisibility; + } + if (newVisibility == null) { + newVisibility = defaultVisibility; + } + if (compositeVisibility == UserInterfaceElementVisibilityType.HIDDEN || newVisibility == UserInterfaceElementVisibilityType.HIDDEN) { + return UserInterfaceElementVisibilityType.HIDDEN; + } + if (compositeVisibility == UserInterfaceElementVisibilityType.VISIBLE || newVisibility == UserInterfaceElementVisibilityType.VISIBLE) { + return UserInterfaceElementVisibilityType.VISIBLE; + } + if (compositeVisibility == UserInterfaceElementVisibilityType.AUTOMATIC || newVisibility == UserInterfaceElementVisibilityType.AUTOMATIC) { + return UserInterfaceElementVisibilityType.AUTOMATIC; + } + return UserInterfaceElementVisibilityType.VACANT; + } + + public CompiledGuiProfile getGlobalCompiledGuiProfile(Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(parentResult); + if (systemConfiguration == null) { + return null; + } + List adminGuiConfigurations = new ArrayList<>(); + CompiledGuiProfile compiledGuiProfile = compileUserProfile(adminGuiConfigurations, systemConfiguration, task, parentResult); + // TODO: cache compiled profile + return compiledGuiProfile; + } + + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java index fe60c96cb11..7fe86d92208 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java @@ -1,501 +1,501 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.sync; - -import java.util.List; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.lang.BooleanUtils; -import org.apache.commons.lang.StringUtils; - -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; -import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DebugDumpable; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import org.jetbrains.annotations.Nullable; - -public class SynchronizationContext implements DebugDumpable { - - private static final Trace LOGGER = TraceManager.getTrace(SynchronizationContext.class); - - private PrismObject applicableShadow; - private final PrismObject currentShadow; - - /** - * Original delta that triggered this synchronization. (If known.) - */ - private ObjectDelta resourceObjectDelta; - - private PrismObject resource; - private PrismObject systemConfiguration; - private String channel; - private ExpressionProfile expressionProfile; - - private Task task; - - private ObjectSynchronizationType objectSynchronization; - private Class focusClass; - private F currentOwner; - private F correlatedOwner; - private SynchronizationSituationType situation; - - private String intent; - - private String tag; - - private boolean reactionEvaluated = false; - private SynchronizationReactionType reaction; - - private boolean unrelatedChange = false; - - private boolean shadowExistsInRepo = true; - private boolean forceIntentChange; - - private PrismContext prismContext; - private ExpressionFactory expressionFactory; - - public SynchronizationContext(PrismObject applicableShadow, PrismObject currentShadow, - ObjectDelta resourceObjectDelta, PrismObject resource, String channel, - PrismContext prismContext, ExpressionFactory expressionFactory, Task task) { - this.applicableShadow = applicableShadow; - this.currentShadow = currentShadow; - this.resourceObjectDelta = resourceObjectDelta; - this.resource = resource; - this.channel = channel; - this.task = task; - this.prismContext = prismContext; - this.expressionFactory = expressionFactory; - this.expressionProfile = MiscSchemaUtil.getExpressionProfile(); - } - - public boolean isSynchronizationEnabled() { - return objectSynchronization != null && BooleanUtils.isNotFalse(objectSynchronization.isEnabled()); - } - - public boolean isProtected() { - if (applicableShadow == null) { - return false; - } - - ShadowType currentShadowType = applicableShadow.asObjectable(); - return BooleanUtils.isTrue(currentShadowType.isProtectedObject()); - } - -// public boolean isSatisfyTaskConstraints() throws SchemaException { -// -// ShadowKindType kind = getTaskPropertyValue(SchemaConstants.MODEL_EXTENSION_KIND); -// String intent = getTaskPropertyValue(SchemaConstants.MODEL_EXTENSION_INTENT); -// QName objectClass = getTaskPropertyValue(SchemaConstants.MODEL_EXTENSION_OBJECTCLASS); -// -// LOGGER.trace("checking task constraints: {}", task); -// -// boolean isApplicable = SynchronizationUtils.isPolicyApplicable(objectClass, kind, intent, objectSynchronization, resource, true); -// //this mean that kind/intent are null in the task..but this can be a case, so check if at least the objectClass is the same -// if (!isApplicable && objectClass != null) { -// return QNameUtil.matchAny(objectClass, objectSynchronization.getObjectClass()); -// } -// -// return isApplicable; -// } -// -// //TODO multi-threaded tasks? -// private T getTaskPropertyValue(QName propertyName) { -// PrismProperty prop = task.getExtensionPropertyOrClone(ItemName.fromQName(propertyName)); -// if (prop == null || prop.isEmpty()) { -// return null; -// } -// -// return prop.getRealValue(); -// } - - public ShadowKindType getKind() { - - if (!hasApplicablePolicy()) { - return ShadowKindType.UNKNOWN; - } - - if (objectSynchronization.getKind() == null) { - return ShadowKindType.ACCOUNT; - } - - return objectSynchronization.getKind(); - } - - public String getIntent() throws SchemaException { - if (!hasApplicablePolicy()) { - return SchemaConstants.INTENT_UNKNOWN; - } - - if (intent == null) { - RefinedResourceSchema schema = RefinedResourceSchemaImpl.getRefinedSchema(resource); - ObjectClassComplexTypeDefinition occtd = schema.findDefaultObjectClassDefinition(getKind()); - intent = occtd.getIntent(); - } - return intent; - } - - public String getTag() { - return tag; - } - - public void setTag(String tag) { - this.tag = tag; - } - - public List getCorrelation() { - return objectSynchronization.getCorrelation(); - } - - public ExpressionType getConfirmation() { - return objectSynchronization.getConfirmation(); - } - - public ObjectReferenceType getObjectTemplateRef() { - if (reaction.getObjectTemplateRef() != null) { - return reaction.getObjectTemplateRef(); - } - - return objectSynchronization.getObjectTemplateRef(); - } - - public SynchronizationReactionType getReaction(OperationResult result) - throws ConfigurationException, SchemaException, ObjectNotFoundException, CommunicationException, - SecurityViolationException, ExpressionEvaluationException { - if (reactionEvaluated) { - return reaction; - } - - SynchronizationReactionType defaultReaction = null; - for (SynchronizationReactionType reactionToConsider : objectSynchronization.getReaction()) { - SynchronizationSituationType reactionSituation = reactionToConsider.getSituation(); - if (reactionSituation == null) { - throw new ConfigurationException("No situation defined for a reaction in " + resource); - } - if (reactionSituation == situation) { - List channels = reactionToConsider.getChannel(); - // The second and third conditions are suspicious but let's keep them here for historical reasons. - if (channels.isEmpty() || channels.contains("") || channels.contains(null)) { - if (conditionMatches(reactionToConsider, result)) { - defaultReaction = reactionToConsider; - } - } else if (channels.contains(this.channel)) { - if (conditionMatches(reactionToConsider, result)) { - reaction = reactionToConsider; - reactionEvaluated = true; - return reaction; - } - } else { - LOGGER.trace("Skipping reaction {} because the channel does not match {}", reaction, this.channel); - } - } - } - LOGGER.trace("Using default reaction {}", defaultReaction); - reaction = defaultReaction; - reactionEvaluated = true; - return reaction; - } - - private boolean conditionMatches(SynchronizationReactionType reaction, OperationResult result) throws SchemaException, - ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException { - if (reaction.getCondition() != null) { - ExpressionType expression = reaction.getCondition(); - String desc = "condition in synchronization reaction on " + reaction.getSituation() - + (reaction.getName() != null ? " (" + reaction.getName() + ")" : ""); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(getFocus(), applicableShadow, null, - resource, systemConfiguration, null, prismContext); - variables.put(ExpressionConstants.VAR_RESOURCE_OBJECT_DELTA, resourceObjectDelta, ObjectDelta.class); - try { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - PrismPropertyValue evaluateCondition = ExpressionUtil.evaluateCondition(variables, expression, - expressionProfile, expressionFactory, desc, task, result); - boolean value = Boolean.TRUE.equals(evaluateCondition.getValue()); - if (!value) { - LOGGER.trace("Skipping reaction {} because the condition was evaluated to false", reaction); - } - return value; - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } else { - return true; - } - } - - @Nullable - private PrismObject getFocus() { - PrismObject focus; - if (currentOwner != null) { - focus = currentOwner.asPrismObject(); - } else if (correlatedOwner != null) { - focus = correlatedOwner.asPrismObject(); - } else { - focus = null; - } - return focus; - } - - public boolean hasApplicablePolicy() { - return objectSynchronization != null; - } - - public String getPolicyName() { - if (objectSynchronization == null) { - return null; - } - if (objectSynchronization.getName() != null) { - return objectSynchronization.getName(); - } - return objectSynchronization.toString(); - } - - public Boolean isDoReconciliation() { - if (reaction.isReconcile() != null) { - return reaction.isReconcile(); - } - if (objectSynchronization.isReconcile() != null) { - return objectSynchronization.isReconcile(); - } - return null; - } - - public Boolean isLimitPropagation() { - if (StringUtils.isNotBlank(channel)) { - QName channelQName = QNameUtil.uriToQName(channel); - // Discovery channel is used when compensating some inconsistent - // state. Therefore we do not want to propagate changes to other - // resources. We only want to resolve the problem and continue in - // previous provisioning/synchronization during which this - // compensation was triggered. - if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY.equals(channelQName) - && SynchronizationSituationType.DELETED != reaction.getSituation()) { - return true; - } - } - - if (reaction.isLimitPropagation() != null) { - return reaction.isLimitPropagation(); - } - if (objectSynchronization.isLimitPropagation() != null) { - return objectSynchronization.isLimitPropagation(); - } - return null; - } - - //TODO obejctClass??? -// public QName getObjectClass() { -// if (objectSynchronization.getObjectClass() != ) -// } - - - public PrismObject getApplicableShadow() { - return applicableShadow; - } - public PrismObject getCurrentShadow() { - return currentShadow; - } - public PrismObject getResource() { - return resource; - } - - public Class getFocusClass() throws SchemaException { - - if (focusClass != null) { - return focusClass; - } - - if (!hasApplicablePolicy()) { - throw new IllegalStateException("synchronizationPolicy is null"); - } - - QName focusTypeQName = objectSynchronization.getFocusType(); - if (focusTypeQName == null) { - //noinspection unchecked - this.focusClass = (Class) UserType.class; - return focusClass; - } - ObjectTypes objectType = ObjectTypes.getObjectTypeFromTypeQName(focusTypeQName); - if (objectType == null) { - throw new SchemaException("Unknown focus type " + focusTypeQName + " in synchronization policy in " + resource); - } - //noinspection unchecked - this.focusClass = (Class) objectType.getClassDefinition(); - return focusClass; - } - - public F getCurrentOwner() { - return currentOwner; - } - - public F getCorrelatedOwner() { - return correlatedOwner; - } - - public PrismContext getPrismContext() { - return prismContext; - } - - public SynchronizationSituationType getSituation() { - return situation; - } - - public void setObjectSynchronization(ObjectSynchronizationType objectSynchronization) { - this.intent = objectSynchronization.getIntent(); - this.objectSynchronization = objectSynchronization; - } - - public void setFocusClass(Class focusClass) { - this.focusClass = focusClass; - } - - public void setCurrentOwner(F owner) { - this.currentOwner = owner; - } - - public void setCorrelatedOwner(F correlatedFocus) { - this.correlatedOwner = correlatedFocus; - } - - public void setSituation(SynchronizationSituationType situation) { - this.situation = situation; - } - - public PrismObject getSystemConfiguration() { - return systemConfiguration; - } - public String getChannel() { - return channel; - } - public void setResource(PrismObject resource) { - this.resource = resource; - } - public void setSystemConfiguration(PrismObject systemConfiguration) { - this.systemConfiguration = systemConfiguration; - } - public void setChannel(String channel) { - this.channel = channel; - } - -// public SynchronizationReactionType getReaction() { -// return reaction; -// } - - public ExpressionProfile getExpressionProfile() { - return expressionProfile; - } - - public void setExpressionProfile(ExpressionProfile expressionProfile) { - this.expressionProfile = expressionProfile; - } - - public void setReaction(SynchronizationReactionType reaction) { - this.reaction = reaction; - } - - public boolean isUnrelatedChange() { - return unrelatedChange; - } - - public void setUnrelatedChange(boolean unrelatedChange) { - this.unrelatedChange = unrelatedChange; - } - - public Task getTask() { - return task; - } - - public void setTask(Task task) { - this.task = task; - } - - public boolean isShadowExistsInRepo() { - return shadowExistsInRepo; - } - - public void setShadowExistsInRepo(boolean shadowExistsInRepo) { - this.shadowExistsInRepo = shadowExistsInRepo; - } - - public boolean isForceIntentChange() { - return forceIntentChange; - } - - public void setForceIntentChange(boolean forceIntentChange) { - this.forceIntentChange = forceIntentChange; - } - - public RefinedObjectClassDefinition findRefinedObjectClassDefinition() throws SchemaException { - RefinedResourceSchema refinedResourceSchema = RefinedResourceSchema.getRefinedSchema(resource); - return refinedResourceSchema.getRefinedDefinition(getKind(), getIntent()); - } - - - @Override - public String toString() { - String policyDesc = null; - if (objectSynchronization != null) { - if (objectSynchronization.getName() == null) { - policyDesc = "(kind=" + objectSynchronization.getKind() + ", intent=" - + objectSynchronization.getIntent() + ", objectclass=" - + objectSynchronization.getObjectClass() + ")"; - } else { - policyDesc = objectSynchronization.getName(); - } - } - - return policyDesc; - } - - @Override - public String debugDump(int indent) { - StringBuilder sb = DebugUtil.createTitleStringBuilderLn(SynchronizationContext.class, indent); - DebugUtil.debugDumpWithLabelLn(sb, "applicableShadow", applicableShadow, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "currentShadow", currentShadow, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "resource", resource, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "systemConfiguration", systemConfiguration, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "channel", channel, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "expressionProfile", expressionProfile, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "objectSynchronization", objectSynchronization, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "focusClass", focusClass, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "currentOwner", currentOwner, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "correlatedOwner", correlatedOwner, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "situation", situation, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "intent", intent, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "tag", tag, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "reaction", reaction, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "unrelatedChange", unrelatedChange, indent + 1); - DebugUtil.debugDumpWithLabelLn(sb, "shadowExistsInRepo", shadowExistsInRepo, indent + 1); - DebugUtil.debugDumpWithLabel(sb, "forceIntentChange", forceIntentChange, indent + 1); - return sb.toString(); - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.sync; + +import java.util.List; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; + +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import org.jetbrains.annotations.Nullable; + +public class SynchronizationContext implements DebugDumpable { + + private static final Trace LOGGER = TraceManager.getTrace(SynchronizationContext.class); + + private PrismObject applicableShadow; + private final PrismObject currentShadow; + + /** + * Original delta that triggered this synchronization. (If known.) + */ + private ObjectDelta resourceObjectDelta; + + private PrismObject resource; + private PrismObject systemConfiguration; + private String channel; + private ExpressionProfile expressionProfile; + + private Task task; + + private ObjectSynchronizationType objectSynchronization; + private Class focusClass; + private F currentOwner; + private F correlatedOwner; + private SynchronizationSituationType situation; + + private String intent; + + private String tag; + + private boolean reactionEvaluated = false; + private SynchronizationReactionType reaction; + + private boolean unrelatedChange = false; + + private boolean shadowExistsInRepo = true; + private boolean forceIntentChange; + + private PrismContext prismContext; + private ExpressionFactory expressionFactory; + + public SynchronizationContext(PrismObject applicableShadow, PrismObject currentShadow, + ObjectDelta resourceObjectDelta, PrismObject resource, String channel, + PrismContext prismContext, ExpressionFactory expressionFactory, Task task) { + this.applicableShadow = applicableShadow; + this.currentShadow = currentShadow; + this.resourceObjectDelta = resourceObjectDelta; + this.resource = resource; + this.channel = channel; + this.task = task; + this.prismContext = prismContext; + this.expressionFactory = expressionFactory; + this.expressionProfile = MiscSchemaUtil.getExpressionProfile(); + } + + public boolean isSynchronizationEnabled() { + return objectSynchronization != null && BooleanUtils.isNotFalse(objectSynchronization.isEnabled()); + } + + public boolean isProtected() { + if (applicableShadow == null) { + return false; + } + + ShadowType currentShadowType = applicableShadow.asObjectable(); + return BooleanUtils.isTrue(currentShadowType.isProtectedObject()); + } + +// public boolean isSatisfyTaskConstraints() throws SchemaException { +// +// ShadowKindType kind = getTaskPropertyValue(SchemaConstants.MODEL_EXTENSION_KIND); +// String intent = getTaskPropertyValue(SchemaConstants.MODEL_EXTENSION_INTENT); +// QName objectClass = getTaskPropertyValue(SchemaConstants.MODEL_EXTENSION_OBJECTCLASS); +// +// LOGGER.trace("checking task constraints: {}", task); +// +// boolean isApplicable = SynchronizationUtils.isPolicyApplicable(objectClass, kind, intent, objectSynchronization, resource, true); +// //this mean that kind/intent are null in the task..but this can be a case, so check if at least the objectClass is the same +// if (!isApplicable && objectClass != null) { +// return QNameUtil.matchAny(objectClass, objectSynchronization.getObjectClass()); +// } +// +// return isApplicable; +// } +// +// //TODO multi-threaded tasks? +// private T getTaskPropertyValue(QName propertyName) { +// PrismProperty prop = task.getExtensionPropertyOrClone(ItemName.fromQName(propertyName)); +// if (prop == null || prop.isEmpty()) { +// return null; +// } +// +// return prop.getRealValue(); +// } + + public ShadowKindType getKind() { + + if (!hasApplicablePolicy()) { + return ShadowKindType.UNKNOWN; + } + + if (objectSynchronization.getKind() == null) { + return ShadowKindType.ACCOUNT; + } + + return objectSynchronization.getKind(); + } + + public String getIntent() throws SchemaException { + if (!hasApplicablePolicy()) { + return SchemaConstants.INTENT_UNKNOWN; + } + + if (intent == null) { + RefinedResourceSchema schema = RefinedResourceSchemaImpl.getRefinedSchema(resource); + ObjectClassComplexTypeDefinition occtd = schema.findDefaultObjectClassDefinition(getKind()); + intent = occtd.getIntent(); + } + return intent; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public List getCorrelation() { + return objectSynchronization.getCorrelation(); + } + + public ExpressionType getConfirmation() { + return objectSynchronization.getConfirmation(); + } + + public ObjectReferenceType getObjectTemplateRef() { + if (reaction.getObjectTemplateRef() != null) { + return reaction.getObjectTemplateRef(); + } + + return objectSynchronization.getObjectTemplateRef(); + } + + public SynchronizationReactionType getReaction(OperationResult result) + throws ConfigurationException, SchemaException, ObjectNotFoundException, CommunicationException, + SecurityViolationException, ExpressionEvaluationException { + if (reactionEvaluated) { + return reaction; + } + + SynchronizationReactionType defaultReaction = null; + for (SynchronizationReactionType reactionToConsider : objectSynchronization.getReaction()) { + SynchronizationSituationType reactionSituation = reactionToConsider.getSituation(); + if (reactionSituation == null) { + throw new ConfigurationException("No situation defined for a reaction in " + resource); + } + if (reactionSituation == situation) { + List channels = reactionToConsider.getChannel(); + // The second and third conditions are suspicious but let's keep them here for historical reasons. + if (channels.isEmpty() || channels.contains("") || channels.contains(null)) { + if (conditionMatches(reactionToConsider, result)) { + defaultReaction = reactionToConsider; + } + } else if (channels.contains(this.channel)) { + if (conditionMatches(reactionToConsider, result)) { + reaction = reactionToConsider; + reactionEvaluated = true; + return reaction; + } + } else { + LOGGER.trace("Skipping reaction {} because the channel does not match {}", reaction, this.channel); + } + } + } + LOGGER.trace("Using default reaction {}", defaultReaction); + reaction = defaultReaction; + reactionEvaluated = true; + return reaction; + } + + private boolean conditionMatches(SynchronizationReactionType reaction, OperationResult result) throws SchemaException, + ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException { + if (reaction.getCondition() != null) { + ExpressionType expression = reaction.getCondition(); + String desc = "condition in synchronization reaction on " + reaction.getSituation() + + (reaction.getName() != null ? " (" + reaction.getName() + ")" : ""); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(getFocus(), applicableShadow, null, + resource, systemConfiguration, null, prismContext); + variables.put(ExpressionConstants.VAR_RESOURCE_OBJECT_DELTA, resourceObjectDelta, ObjectDelta.class); + try { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + PrismPropertyValue evaluateCondition = ExpressionUtil.evaluateCondition(variables, expression, + expressionProfile, expressionFactory, desc, task, result); + boolean value = Boolean.TRUE.equals(evaluateCondition.getValue()); + if (!value) { + LOGGER.trace("Skipping reaction {} because the condition was evaluated to false", reaction); + } + return value; + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } else { + return true; + } + } + + @Nullable + private PrismObject getFocus() { + PrismObject focus; + if (currentOwner != null) { + focus = currentOwner.asPrismObject(); + } else if (correlatedOwner != null) { + focus = correlatedOwner.asPrismObject(); + } else { + focus = null; + } + return focus; + } + + public boolean hasApplicablePolicy() { + return objectSynchronization != null; + } + + public String getPolicyName() { + if (objectSynchronization == null) { + return null; + } + if (objectSynchronization.getName() != null) { + return objectSynchronization.getName(); + } + return objectSynchronization.toString(); + } + + public Boolean isDoReconciliation() { + if (reaction.isReconcile() != null) { + return reaction.isReconcile(); + } + if (objectSynchronization.isReconcile() != null) { + return objectSynchronization.isReconcile(); + } + return null; + } + + public Boolean isLimitPropagation() { + if (StringUtils.isNotBlank(channel)) { + QName channelQName = QNameUtil.uriToQName(channel); + // Discovery channel is used when compensating some inconsistent + // state. Therefore we do not want to propagate changes to other + // resources. We only want to resolve the problem and continue in + // previous provisioning/synchronization during which this + // compensation was triggered. + if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY.equals(channelQName) + && SynchronizationSituationType.DELETED != reaction.getSituation()) { + return true; + } + } + + if (reaction.isLimitPropagation() != null) { + return reaction.isLimitPropagation(); + } + if (objectSynchronization.isLimitPropagation() != null) { + return objectSynchronization.isLimitPropagation(); + } + return null; + } + + //TODO obejctClass??? +// public QName getObjectClass() { +// if (objectSynchronization.getObjectClass() != ) +// } + + + public PrismObject getApplicableShadow() { + return applicableShadow; + } + public PrismObject getCurrentShadow() { + return currentShadow; + } + public PrismObject getResource() { + return resource; + } + + public Class getFocusClass() throws SchemaException { + + if (focusClass != null) { + return focusClass; + } + + if (!hasApplicablePolicy()) { + throw new IllegalStateException("synchronizationPolicy is null"); + } + + QName focusTypeQName = objectSynchronization.getFocusType(); + if (focusTypeQName == null) { + //noinspection unchecked + this.focusClass = (Class) UserType.class; + return focusClass; + } + ObjectTypes objectType = ObjectTypes.getObjectTypeFromTypeQName(focusTypeQName); + if (objectType == null) { + throw new SchemaException("Unknown focus type " + focusTypeQName + " in synchronization policy in " + resource); + } + //noinspection unchecked + this.focusClass = (Class) objectType.getClassDefinition(); + return focusClass; + } + + public F getCurrentOwner() { + return currentOwner; + } + + public F getCorrelatedOwner() { + return correlatedOwner; + } + + public PrismContext getPrismContext() { + return prismContext; + } + + public SynchronizationSituationType getSituation() { + return situation; + } + + public void setObjectSynchronization(ObjectSynchronizationType objectSynchronization) { + this.intent = objectSynchronization.getIntent(); + this.objectSynchronization = objectSynchronization; + } + + public void setFocusClass(Class focusClass) { + this.focusClass = focusClass; + } + + public void setCurrentOwner(F owner) { + this.currentOwner = owner; + } + + public void setCorrelatedOwner(F correlatedFocus) { + this.correlatedOwner = correlatedFocus; + } + + public void setSituation(SynchronizationSituationType situation) { + this.situation = situation; + } + + public PrismObject getSystemConfiguration() { + return systemConfiguration; + } + public String getChannel() { + return channel; + } + public void setResource(PrismObject resource) { + this.resource = resource; + } + public void setSystemConfiguration(PrismObject systemConfiguration) { + this.systemConfiguration = systemConfiguration; + } + public void setChannel(String channel) { + this.channel = channel; + } + +// public SynchronizationReactionType getReaction() { +// return reaction; +// } + + public ExpressionProfile getExpressionProfile() { + return expressionProfile; + } + + public void setExpressionProfile(ExpressionProfile expressionProfile) { + this.expressionProfile = expressionProfile; + } + + public void setReaction(SynchronizationReactionType reaction) { + this.reaction = reaction; + } + + public boolean isUnrelatedChange() { + return unrelatedChange; + } + + public void setUnrelatedChange(boolean unrelatedChange) { + this.unrelatedChange = unrelatedChange; + } + + public Task getTask() { + return task; + } + + public void setTask(Task task) { + this.task = task; + } + + public boolean isShadowExistsInRepo() { + return shadowExistsInRepo; + } + + public void setShadowExistsInRepo(boolean shadowExistsInRepo) { + this.shadowExistsInRepo = shadowExistsInRepo; + } + + public boolean isForceIntentChange() { + return forceIntentChange; + } + + public void setForceIntentChange(boolean forceIntentChange) { + this.forceIntentChange = forceIntentChange; + } + + public RefinedObjectClassDefinition findRefinedObjectClassDefinition() throws SchemaException { + RefinedResourceSchema refinedResourceSchema = RefinedResourceSchema.getRefinedSchema(resource); + return refinedResourceSchema.getRefinedDefinition(getKind(), getIntent()); + } + + + @Override + public String toString() { + String policyDesc = null; + if (objectSynchronization != null) { + if (objectSynchronization.getName() == null) { + policyDesc = "(kind=" + objectSynchronization.getKind() + ", intent=" + + objectSynchronization.getIntent() + ", objectclass=" + + objectSynchronization.getObjectClass() + ")"; + } else { + policyDesc = objectSynchronization.getName(); + } + } + + return policyDesc; + } + + @Override + public String debugDump(int indent) { + StringBuilder sb = DebugUtil.createTitleStringBuilderLn(SynchronizationContext.class, indent); + DebugUtil.debugDumpWithLabelLn(sb, "applicableShadow", applicableShadow, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "currentShadow", currentShadow, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "resource", resource, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "systemConfiguration", systemConfiguration, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "channel", channel, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "expressionProfile", expressionProfile, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "objectSynchronization", objectSynchronization, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "focusClass", focusClass, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "currentOwner", currentOwner, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "correlatedOwner", correlatedOwner, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "situation", situation, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "intent", intent, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "tag", tag, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "reaction", reaction, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "unrelatedChange", unrelatedChange, indent + 1); + DebugUtil.debugDumpWithLabelLn(sb, "shadowExistsInRepo", shadowExistsInRepo, indent + 1); + DebugUtil.debugDumpWithLabel(sb, "forceIntentChange", forceIntentChange, indent + 1); + return sb.toString(); + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationExpressionsEvaluator.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationExpressionsEvaluator.java index de2b8feb347..7d938130145 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationExpressionsEvaluator.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationExpressionsEvaluator.java @@ -1,420 +1,420 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.sync; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -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.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.SchemaDebugUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.PrettyPrinter; -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.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.ConditionalSearchFilterType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectMultiplicityType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowTagSpecificationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; - -import java.util.HashSet; -import java.util.Set; - -@Component -public class SynchronizationExpressionsEvaluator { - - private static final Trace LOGGER = TraceManager.getTrace(SynchronizationExpressionsEvaluator.class); - - @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; - @Autowired private PrismContext prismContext; - @Autowired private RelationRegistry relationRegistry; - @Autowired private ExpressionFactory expressionFactory; - @Autowired private MatchingRuleRegistry matchingRuleRegistry; - - public List> findFocusesByCorrelationRule(Class focusType, ShadowType currentShadow, - List conditionalFilters, ResourceType resourceType, SystemConfigurationType configurationType, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (conditionalFilters == null || conditionalFilters.isEmpty()) { - LOGGER.warn("Correlation rule for resource '{}' doesn't contain query, " - + "returning empty list of users.", resourceType); - return null; - } - - // TODO: determine from the resource - ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); - - List> users = null; - for (ConditionalSearchFilterType conditionalFilter : conditionalFilters) { - // TODO: better description - if (satisfyCondition(currentShadow, conditionalFilter, expressionProfile, resourceType, configurationType, "Condition expression", task, - result)) { - LOGGER.trace("Condition {} in correlation expression evaluated to true", conditionalFilter.getCondition()); - List> foundUsers = findFocusesByCorrelationRule(focusType, currentShadow, conditionalFilter, - expressionProfile, resourceType, configurationType, task, result); - if (foundUsers == null && users == null) { - continue; - } - if (foundUsers != null && foundUsers.isEmpty() && users == null) { - users = new ArrayList<>(); - } - - if (users == null && foundUsers != null) { - users = foundUsers; - } - if (users != null && !users.isEmpty() && foundUsers != null && !foundUsers.isEmpty()) { - for (PrismObject foundUser : foundUsers) { - if (!contains(users, foundUser)) { - users.add(foundUser); - } - } - } - } - } - - if (users != null) { - LOGGER.debug( - "SYNCHRONIZATION: CORRELATION: expression for {} returned {} users: {}", currentShadow, users.size(), - PrettyPrinter.prettyPrint(users, 3)); - if (users.size() > 1) { - // remove duplicates - Set> usersWithoutDups = new HashSet<>(); - usersWithoutDups.addAll(users); - users.clear(); - users.addAll(usersWithoutDups); - LOGGER.debug("SYNCHRONIZATION: CORRELATION: found {} users without duplicates", users.size()); - } - } - return users; - } - - private boolean satisfyCondition(ShadowType currentShadow, ConditionalSearchFilterType conditionalFilter, - ExpressionProfile expressionProfile, ResourceType resourceType, SystemConfigurationType configurationType, String shortDesc, Task task, - OperationResult parentResult) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (conditionalFilter.getCondition() == null){ - return true; - } - - ExpressionType condition = conditionalFilter.getCondition(); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null,currentShadow, resourceType, configurationType, prismContext); - ItemDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition( - ExpressionConstants.OUTPUT_ELEMENT_NAME, PrimitiveType.BOOLEAN.getQname()); - PrismPropertyValue satisfy = (PrismPropertyValue) ExpressionUtil.evaluateExpression(variables, - outputDefinition, condition, expressionProfile, expressionFactory, shortDesc, task, parentResult); - if (satisfy.getValue() == null) { - return false; - } - - return satisfy.getValue(); - } - - private boolean contains(List> users, PrismObject foundUser){ - for (PrismObject user : users){ - if (user.getOid().equals(foundUser.getOid())){ - return true; - } - } - return false; - } - - - private List> findFocusesByCorrelationRule(Class focusType, - ShadowType currentShadow, ConditionalSearchFilterType conditionalFilter, ExpressionProfile expressionProfile, ResourceType resourceType, SystemConfigurationType configurationType, - Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ - if (!conditionalFilter.containsFilterClause()) { - LOGGER.warn("Correlation rule for resource '{}' doesn't contain filter clause, " - + "returning empty list of users.", resourceType); - return null; - } - - ObjectQuery q; - try { - q = prismContext.getQueryConverter().createObjectQuery(focusType, conditionalFilter); - q = updateFilterWithAccountValues(currentShadow, resourceType, configurationType, q, expressionProfile, "Correlation expression", task, result); - if (q == null) { - // Null is OK here, it means that the value in the filter - // evaluated - // to null and the processing should be skipped - return null; - } - - - } catch (SchemaException ex) { - LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, - SchemaDebugUtil.prettyPrint(conditionalFilter)); - throw new SchemaException("Couldn't convert query.", ex); - } catch (ObjectNotFoundException ex) { - LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, - SchemaDebugUtil.prettyPrint(conditionalFilter)); - throw new ObjectNotFoundException("Couldn't convert query.", ex); - } catch (ExpressionEvaluationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, - SchemaDebugUtil.prettyPrint(conditionalFilter)); - throw new ExpressionEvaluationException("Couldn't convert query.", ex); - } catch (CommunicationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, - SchemaDebugUtil.prettyPrint(conditionalFilter)); - throw new CommunicationException("Couldn't convert query.", ex); - } catch (ConfigurationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, - SchemaDebugUtil.prettyPrint(conditionalFilter)); - throw new ConfigurationException("Couldn't convert query.", ex); - } catch (SecurityViolationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, - SchemaDebugUtil.prettyPrint(conditionalFilter)); - throw new SecurityViolationException("Couldn't convert query.", ex); - } - - List> users; - try { - LOGGER.trace("SYNCHRONIZATION: CORRELATION: expression for results in filter\n{}", q.debugDumpLazily()); - users = repositoryService.searchObjects(focusType, q, null, result); - } catch (RuntimeException ex) { - LoggingUtils.logException(LOGGER, - "Couldn't search users in repository, based on filter (simplified)\n{}.", ex, q.debugDump()); - throw new SystemException( - "Couldn't search users in repository, based on filter (See logs).", ex); - } - return users; - } - - - private boolean matchUserCorrelationRule(Class focusType, PrismObject currentShadow, - ExpressionProfile expressionProfile, PrismObject userType, ResourceType resourceType, SystemConfigurationType configurationType, - ConditionalSearchFilterType conditionalFilter, Task task, OperationResult result) throws SchemaException { - if (conditionalFilter == null) { - LOGGER.warn("Correlation rule for resource '{}' doesn't contain query, " - + "returning empty list of users.", resourceType); - return false; - } - - if (!conditionalFilter.containsFilterClause()) { - LOGGER.warn("Correlation rule for resource '{}' doesn't contain a filter, " - + "returning empty list of users.", resourceType); - return false; - } - - // TODO evaluate condition here - - ObjectQuery q; - try { - q = prismContext.getQueryConverter().createObjectQuery(focusType, conditionalFilter); - q = updateFilterWithAccountValues(currentShadow.asObjectable(), resourceType, configurationType, q, expressionProfile, "Correlation expression", task, result); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Start matching user {} with correlation expression {}", userType, q != null ? q.debugDump() : "(null)"); - } - if (q == null) { - // Null is OK here, it means that the value in the filter evaluated - // to null and the processing should be skipped - return false; - } - } catch (Exception ex) { - LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, - SchemaDebugUtil.prettyPrint(conditionalFilter)); - throw new SystemException("Couldn't convert query.", ex); - } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("SYNCHRONIZATION: CORRELATION: expression for {} results in filter\n{}", currentShadow, q); - } - - // we assume userType is already normalized w.r.t. relations - ObjectTypeUtil.normalizeFilter(q.getFilter(), relationRegistry); - return ObjectQuery.match(userType, q.getFilter(), matchingRuleRegistry); - } - - - public boolean matchFocusByCorrelationRule(SynchronizationContext syncCtx, PrismObject focus, - OperationResult result) { - - if (!syncCtx.hasApplicablePolicy()) { - LOGGER.warn( - "Resource does not support synchronization. Skipping evaluation correlation/confirmation for {} and {}", - focus, syncCtx.getApplicableShadow()); - return false; - } - - List conditionalFilters = syncCtx.getCorrelation(); - - try { - for (ConditionalSearchFilterType conditionalFilter : conditionalFilters) { - - //TODO: can we expect that systemConfig and resource are always present? - if (matchUserCorrelationRule(syncCtx.getFocusClass(), syncCtx.getApplicableShadow(), syncCtx.getExpressionProfile(), focus, syncCtx.getResource().asObjectable(), - syncCtx.getSystemConfiguration().asObjectable(), conditionalFilter, syncCtx.getTask(), result)) { - LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} match user: {}", syncCtx.getApplicableShadow(), focus); - return true; - } - } - } catch (SchemaException ex) { - throw new SystemException("Failed to match user using correlation rule. " + ex.getMessage(), ex); - } - - LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} does not match user: {}", syncCtx.getApplicableShadow(), focus); - return false; - } - - public List> findUserByConfirmationRule(Class focusType, List> users, - ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, ExpressionType expression, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException - { - List> list = new ArrayList<>(); - for (PrismObject user : users) { - try { - F userType = user.asObjectable(); - boolean confirmedUser = evaluateConfirmationExpression(focusType, userType, - currentShadow, resource, configuration, expression, task, result); - if (confirmedUser) { - list.add(user); - } - } catch (RuntimeException ex) { - LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); - throw new SystemException("Couldn't confirm user " + user.getElementName(), ex); - } catch (ExpressionEvaluationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); - throw new ExpressionEvaluationException("Couldn't confirm user " + user.getElementName(), ex); - } catch (ObjectNotFoundException ex) { - LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); - throw new ObjectNotFoundException("Couldn't confirm user " + user.getElementName(), ex); - } catch (SchemaException ex) { - LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); - throw new SchemaException("Couldn't confirm user " + user.getElementName(), ex); - } catch (CommunicationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); - throw new CommunicationException("Couldn't confirm user " + user.getElementName(), ex); - } catch (ConfigurationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); - throw new ConfigurationException("Couldn't confirm user " + user.getElementName(), ex); - } catch (SecurityViolationException ex) { - LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); - throw new SecurityViolationException("Couldn't confirm user " + user.getElementName(), ex); - } - } - - LOGGER.debug("SYNCHRONIZATION: CONFIRMATION: expression for {} matched {} users.", new Object[] { - currentShadow, list.size() }); - return list; - } - - private ObjectQuery updateFilterWithAccountValues(ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, - ObjectQuery origQuery, ExpressionProfile expressionProfile, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (origQuery.getFilter() == null) { - LOGGER.trace("No filter provided, skipping updating filter"); - return origQuery; - } - - return evaluateQueryExpressions(origQuery, expressionProfile, currentShadow, resource, configuration, shortDesc, task, result); - } - - private ObjectQuery evaluateQueryExpressions(ObjectQuery query, ExpressionProfile expressionProfile, ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, - String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, currentShadow, resource, configuration, prismContext); - return ExpressionUtil.evaluateQueryExpressions(query, variables, expressionProfile, expressionFactory, prismContext, shortDesc, task, result); - } - - public boolean evaluateConfirmationExpression(Class focusType, F user, ShadowType shadow, ResourceType resource, - SystemConfigurationType configuration, ExpressionType expressionType, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - Validate.notNull(user, "User must not be null."); - Validate.notNull(shadow, "Resource object shadow must not be null."); - Validate.notNull(expressionType, "Expression must not be null."); - Validate.notNull(result, "Operation result must not be null."); - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, resource, configuration, prismContext); - String shortDesc = "confirmation expression for "+resource.asPrismObject(); - - PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.OUTPUT_ELEMENT_NAME, - DOMUtil.XSD_BOOLEAN); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, - outputDefinition, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, task, result); - Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); - } - if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Expression returned more than one value ("+nonNegativeValues.size()+") in "+shortDesc); - } - PrismPropertyValue resultpval = nonNegativeValues.iterator().next(); - if (resultpval == null) { - throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); - } - Boolean resultVal = resultpval.getValue(); - if (resultVal == null) { - throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); - } - return resultVal; - } - - // For now only used in sync service. but later can be used in outbound/assignments - public String generateTag(ResourceObjectMultiplicityType multiplicity, PrismObject shadow, PrismObject resource, PrismObject configuration, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - if (multiplicity == null) { - return null; - } - ShadowTagSpecificationType tagSpec = multiplicity.getTag(); - if (tagSpec == null) { - return shadow.getOid(); - } - ExpressionType expressionType = tagSpec.getExpression(); - if (expressionType == null) { - return shadow.getOid(); - } - - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, shadow, null, resource, configuration, null, prismContext); - ItemDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition( - ExpressionConstants.OUTPUT_ELEMENT_NAME, PrimitiveType.STRING.getQname()); - PrismPropertyValue tagProp = (PrismPropertyValue) ExpressionUtil.evaluateExpression(variables, - outputDefinition, expressionType, MiscSchemaUtil.getExpressionProfile(), expressionFactory, shortDesc, task, parentResult); - if (tagProp == null) { - return null; - } - return tagProp.getRealValue(); - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.sync; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +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.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.SchemaDebugUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.PrettyPrinter; +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.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.ConditionalSearchFilterType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectMultiplicityType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowTagSpecificationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; + +import java.util.HashSet; +import java.util.Set; + +@Component +public class SynchronizationExpressionsEvaluator { + + private static final Trace LOGGER = TraceManager.getTrace(SynchronizationExpressionsEvaluator.class); + + @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; + @Autowired private PrismContext prismContext; + @Autowired private RelationRegistry relationRegistry; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private MatchingRuleRegistry matchingRuleRegistry; + + public List> findFocusesByCorrelationRule(Class focusType, ShadowType currentShadow, + List conditionalFilters, ResourceType resourceType, SystemConfigurationType configurationType, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (conditionalFilters == null || conditionalFilters.isEmpty()) { + LOGGER.warn("Correlation rule for resource '{}' doesn't contain query, " + + "returning empty list of users.", resourceType); + return null; + } + + // TODO: determine from the resource + ExpressionProfile expressionProfile = MiscSchemaUtil.getExpressionProfile(); + + List> users = null; + for (ConditionalSearchFilterType conditionalFilter : conditionalFilters) { + // TODO: better description + if (satisfyCondition(currentShadow, conditionalFilter, expressionProfile, resourceType, configurationType, "Condition expression", task, + result)) { + LOGGER.trace("Condition {} in correlation expression evaluated to true", conditionalFilter.getCondition()); + List> foundUsers = findFocusesByCorrelationRule(focusType, currentShadow, conditionalFilter, + expressionProfile, resourceType, configurationType, task, result); + if (foundUsers == null && users == null) { + continue; + } + if (foundUsers != null && foundUsers.isEmpty() && users == null) { + users = new ArrayList<>(); + } + + if (users == null && foundUsers != null) { + users = foundUsers; + } + if (users != null && !users.isEmpty() && foundUsers != null && !foundUsers.isEmpty()) { + for (PrismObject foundUser : foundUsers) { + if (!contains(users, foundUser)) { + users.add(foundUser); + } + } + } + } + } + + if (users != null) { + LOGGER.debug( + "SYNCHRONIZATION: CORRELATION: expression for {} returned {} users: {}", currentShadow, users.size(), + PrettyPrinter.prettyPrint(users, 3)); + if (users.size() > 1) { + // remove duplicates + Set> usersWithoutDups = new HashSet<>(); + usersWithoutDups.addAll(users); + users.clear(); + users.addAll(usersWithoutDups); + LOGGER.debug("SYNCHRONIZATION: CORRELATION: found {} users without duplicates", users.size()); + } + } + return users; + } + + private boolean satisfyCondition(ShadowType currentShadow, ConditionalSearchFilterType conditionalFilter, + ExpressionProfile expressionProfile, ResourceType resourceType, SystemConfigurationType configurationType, String shortDesc, Task task, + OperationResult parentResult) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (conditionalFilter.getCondition() == null){ + return true; + } + + ExpressionType condition = conditionalFilter.getCondition(); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null,currentShadow, resourceType, configurationType, prismContext); + ItemDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition( + ExpressionConstants.OUTPUT_ELEMENT_NAME, PrimitiveType.BOOLEAN.getQname()); + PrismPropertyValue satisfy = (PrismPropertyValue) ExpressionUtil.evaluateExpression(variables, + outputDefinition, condition, expressionProfile, expressionFactory, shortDesc, task, parentResult); + if (satisfy.getValue() == null) { + return false; + } + + return satisfy.getValue(); + } + + private boolean contains(List> users, PrismObject foundUser){ + for (PrismObject user : users){ + if (user.getOid().equals(foundUser.getOid())){ + return true; + } + } + return false; + } + + + private List> findFocusesByCorrelationRule(Class focusType, + ShadowType currentShadow, ConditionalSearchFilterType conditionalFilter, ExpressionProfile expressionProfile, ResourceType resourceType, SystemConfigurationType configurationType, + Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException{ + if (!conditionalFilter.containsFilterClause()) { + LOGGER.warn("Correlation rule for resource '{}' doesn't contain filter clause, " + + "returning empty list of users.", resourceType); + return null; + } + + ObjectQuery q; + try { + q = prismContext.getQueryConverter().createObjectQuery(focusType, conditionalFilter); + q = updateFilterWithAccountValues(currentShadow, resourceType, configurationType, q, expressionProfile, "Correlation expression", task, result); + if (q == null) { + // Null is OK here, it means that the value in the filter + // evaluated + // to null and the processing should be skipped + return null; + } + + + } catch (SchemaException ex) { + LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, + SchemaDebugUtil.prettyPrint(conditionalFilter)); + throw new SchemaException("Couldn't convert query.", ex); + } catch (ObjectNotFoundException ex) { + LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, + SchemaDebugUtil.prettyPrint(conditionalFilter)); + throw new ObjectNotFoundException("Couldn't convert query.", ex); + } catch (ExpressionEvaluationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, + SchemaDebugUtil.prettyPrint(conditionalFilter)); + throw new ExpressionEvaluationException("Couldn't convert query.", ex); + } catch (CommunicationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, + SchemaDebugUtil.prettyPrint(conditionalFilter)); + throw new CommunicationException("Couldn't convert query.", ex); + } catch (ConfigurationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, + SchemaDebugUtil.prettyPrint(conditionalFilter)); + throw new ConfigurationException("Couldn't convert query.", ex); + } catch (SecurityViolationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, + SchemaDebugUtil.prettyPrint(conditionalFilter)); + throw new SecurityViolationException("Couldn't convert query.", ex); + } + + List> users; + try { + LOGGER.trace("SYNCHRONIZATION: CORRELATION: expression for results in filter\n{}", q.debugDumpLazily()); + users = repositoryService.searchObjects(focusType, q, null, result); + } catch (RuntimeException ex) { + LoggingUtils.logException(LOGGER, + "Couldn't search users in repository, based on filter (simplified)\n{}.", ex, q.debugDump()); + throw new SystemException( + "Couldn't search users in repository, based on filter (See logs).", ex); + } + return users; + } + + + private boolean matchUserCorrelationRule(Class focusType, PrismObject currentShadow, + ExpressionProfile expressionProfile, PrismObject userType, ResourceType resourceType, SystemConfigurationType configurationType, + ConditionalSearchFilterType conditionalFilter, Task task, OperationResult result) throws SchemaException { + if (conditionalFilter == null) { + LOGGER.warn("Correlation rule for resource '{}' doesn't contain query, " + + "returning empty list of users.", resourceType); + return false; + } + + if (!conditionalFilter.containsFilterClause()) { + LOGGER.warn("Correlation rule for resource '{}' doesn't contain a filter, " + + "returning empty list of users.", resourceType); + return false; + } + + // TODO evaluate condition here + + ObjectQuery q; + try { + q = prismContext.getQueryConverter().createObjectQuery(focusType, conditionalFilter); + q = updateFilterWithAccountValues(currentShadow.asObjectable(), resourceType, configurationType, q, expressionProfile, "Correlation expression", task, result); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Start matching user {} with correlation expression {}", userType, q != null ? q.debugDump() : "(null)"); + } + if (q == null) { + // Null is OK here, it means that the value in the filter evaluated + // to null and the processing should be skipped + return false; + } + } catch (Exception ex) { + LoggingUtils.logException(LOGGER, "Couldn't convert query (simplified)\n{}.", ex, + SchemaDebugUtil.prettyPrint(conditionalFilter)); + throw new SystemException("Couldn't convert query.", ex); + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("SYNCHRONIZATION: CORRELATION: expression for {} results in filter\n{}", currentShadow, q); + } + + // we assume userType is already normalized w.r.t. relations + ObjectTypeUtil.normalizeFilter(q.getFilter(), relationRegistry); + return ObjectQuery.match(userType, q.getFilter(), matchingRuleRegistry); + } + + + public boolean matchFocusByCorrelationRule(SynchronizationContext syncCtx, PrismObject focus, + OperationResult result) { + + if (!syncCtx.hasApplicablePolicy()) { + LOGGER.warn( + "Resource does not support synchronization. Skipping evaluation correlation/confirmation for {} and {}", + focus, syncCtx.getApplicableShadow()); + return false; + } + + List conditionalFilters = syncCtx.getCorrelation(); + + try { + for (ConditionalSearchFilterType conditionalFilter : conditionalFilters) { + + //TODO: can we expect that systemConfig and resource are always present? + if (matchUserCorrelationRule(syncCtx.getFocusClass(), syncCtx.getApplicableShadow(), syncCtx.getExpressionProfile(), focus, syncCtx.getResource().asObjectable(), + syncCtx.getSystemConfiguration().asObjectable(), conditionalFilter, syncCtx.getTask(), result)) { + LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} match user: {}", syncCtx.getApplicableShadow(), focus); + return true; + } + } + } catch (SchemaException ex) { + throw new SystemException("Failed to match user using correlation rule. " + ex.getMessage(), ex); + } + + LOGGER.debug("SYNCHRONIZATION: CORRELATION: expression for {} does not match user: {}", syncCtx.getApplicableShadow(), focus); + return false; + } + + public List> findUserByConfirmationRule(Class focusType, List> users, + ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, ExpressionType expression, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException + { + List> list = new ArrayList<>(); + for (PrismObject user : users) { + try { + F userType = user.asObjectable(); + boolean confirmedUser = evaluateConfirmationExpression(focusType, userType, + currentShadow, resource, configuration, expression, task, result); + if (confirmedUser) { + list.add(user); + } + } catch (RuntimeException ex) { + LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); + throw new SystemException("Couldn't confirm user " + user.getElementName(), ex); + } catch (ExpressionEvaluationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); + throw new ExpressionEvaluationException("Couldn't confirm user " + user.getElementName(), ex); + } catch (ObjectNotFoundException ex) { + LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); + throw new ObjectNotFoundException("Couldn't confirm user " + user.getElementName(), ex); + } catch (SchemaException ex) { + LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); + throw new SchemaException("Couldn't confirm user " + user.getElementName(), ex); + } catch (CommunicationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); + throw new CommunicationException("Couldn't confirm user " + user.getElementName(), ex); + } catch (ConfigurationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); + throw new ConfigurationException("Couldn't confirm user " + user.getElementName(), ex); + } catch (SecurityViolationException ex) { + LoggingUtils.logException(LOGGER, "Couldn't confirm user {}", ex, user.getElementName()); + throw new SecurityViolationException("Couldn't confirm user " + user.getElementName(), ex); + } + } + + LOGGER.debug("SYNCHRONIZATION: CONFIRMATION: expression for {} matched {} users.", new Object[] { + currentShadow, list.size() }); + return list; + } + + private ObjectQuery updateFilterWithAccountValues(ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, + ObjectQuery origQuery, ExpressionProfile expressionProfile, String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (origQuery.getFilter() == null) { + LOGGER.trace("No filter provided, skipping updating filter"); + return origQuery; + } + + return evaluateQueryExpressions(origQuery, expressionProfile, currentShadow, resource, configuration, shortDesc, task, result); + } + + private ObjectQuery evaluateQueryExpressions(ObjectQuery query, ExpressionProfile expressionProfile, ShadowType currentShadow, ResourceType resource, SystemConfigurationType configuration, + String shortDesc, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, currentShadow, resource, configuration, prismContext); + return ExpressionUtil.evaluateQueryExpressions(query, variables, expressionProfile, expressionFactory, prismContext, shortDesc, task, result); + } + + public boolean evaluateConfirmationExpression(Class focusType, F user, ShadowType shadow, ResourceType resource, + SystemConfigurationType configuration, ExpressionType expressionType, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + Validate.notNull(user, "User must not be null."); + Validate.notNull(shadow, "Resource object shadow must not be null."); + Validate.notNull(expressionType, "Expression must not be null."); + Validate.notNull(result, "Operation result must not be null."); + + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(user, shadow, resource, configuration, prismContext); + String shortDesc = "confirmation expression for "+resource.asPrismObject(); + + PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.OUTPUT_ELEMENT_NAME, + DOMUtil.XSD_BOOLEAN); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, + outputDefinition, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task); + PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, params, task, result); + Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); + if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { + throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); + } + if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Expression returned more than one value ("+nonNegativeValues.size()+") in "+shortDesc); + } + PrismPropertyValue resultpval = nonNegativeValues.iterator().next(); + if (resultpval == null) { + throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); + } + Boolean resultVal = resultpval.getValue(); + if (resultVal == null) { + throw new ExpressionEvaluationException("Expression returned no value ("+nonNegativeValues.size()+") in "+shortDesc); + } + return resultVal; + } + + // For now only used in sync service. but later can be used in outbound/assignments + public String generateTag(ResourceObjectMultiplicityType multiplicity, PrismObject shadow, PrismObject resource, PrismObject configuration, String shortDesc, Task task, OperationResult parentResult) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + if (multiplicity == null) { + return null; + } + ShadowTagSpecificationType tagSpec = multiplicity.getTag(); + if (tagSpec == null) { + return shadow.getOid(); + } + ExpressionType expressionType = tagSpec.getExpression(); + if (expressionType == null) { + return shadow.getOid(); + } + + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, shadow, null, resource, configuration, null, prismContext); + ItemDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition( + ExpressionConstants.OUTPUT_ELEMENT_NAME, PrimitiveType.STRING.getQname()); + PrismPropertyValue tagProp = (PrismPropertyValue) ExpressionUtil.evaluateExpression(variables, + outputDefinition, expressionType, MiscSchemaUtil.getExpressionProfile(), expressionFactory, shortDesc, task, parentResult); + if (tagProp == null) { + return null; + } + return tagProp.getRealValue(); + } + +} 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 739a9b679d5..7e3def773f7 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 @@ -1,1221 +1,1221 @@ -/* - - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.sync; - -import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.common.SynchronizationUtils; -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.*; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; -import com.evolveum.midpoint.repo.api.PreconditionViolationException; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -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.result.OperationResult; -import com.evolveum.midpoint.schema.result.OperationResultStatus; -import com.evolveum.midpoint.schema.statistics.StatisticsUtil; -import com.evolveum.midpoint.schema.statistics.SynchronizationInformation; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.lang.BooleanUtils; -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.stereotype.Service; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; -import java.util.*; - -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; - -/** - * Synchronization service receives change notifications from provisioning. It - * decides which synchronization policy to use and evaluates it (correlation, - * confirmation, situations, reaction, ...) - * - * @author lazyman - * @author Radovan Semancik - * - * Note: don't autowire this bean by implementing class, as it is - * proxied by Spring AOP. Use the interface instead. - */ -@Service(value = "synchronizationService") -public class SynchronizationServiceImpl implements SynchronizationService { - - private static final Trace LOGGER = TraceManager.getTrace(SynchronizationServiceImpl.class); - - private static final String CLASS_NAME_WITH_DOT = SynchronizationServiceImpl.class.getName() + "."; - private static final String NOTIFY_CHANGE = CLASS_NAME_WITH_DOT + "notifyChange"; - - @Autowired private ActionManager actionManager; - @Autowired private SynchronizationExpressionsEvaluator synchronizationExpressionsEvaluator; - @Autowired private ContextFactory contextFactory; - @Autowired private Clockwork clockwork; - @Autowired private ExpressionFactory expressionFactory; - @Autowired private SystemObjectCache systemObjectCache; - @Autowired private PrismContext prismContext; - @Autowired private Clock clock; - @Autowired private ClockworkMedic clockworkMedic; - - @Autowired - @Qualifier("cacheRepositoryService") - private RepositoryService repositoryService; - - @Override - public void notifyChange(ResourceObjectShadowChangeDescription change, Task task, OperationResult parentResult) { - validate(change); - Validate.notNull(parentResult, "Parent operation result must not be null."); - - boolean logDebug = isLogDebug(change); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("SYNCHRONIZATION: received change notification:\n{}", change.debugDump(1)); - } else if (logDebug) { - LOGGER.debug("SYNCHRONIZATION: received change notification {}", change); - } - - OperationResult subResult = parentResult.subresult(NOTIFY_CHANGE) - .addArbitraryObjectAsParam("change", change) - .addArbitraryObjectAsContext("task", task) - .build(); - - if (change.isCleanDeadShadow()) { - cleanDeadShadow(change, subResult); - subResult.computeStatus(); - return; - } - - PrismObject currentShadow = change.getCurrentShadow(); - PrismObject applicableShadow; - if (currentShadow != null) { - applicableShadow = currentShadow; - } else { - // We need this e.g. in case of delete - applicableShadow = change.getOldShadow(); - } - - XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); - SynchronizationEventInformation eventInfo = new SynchronizationEventInformation(applicableShadow); - - try { - PrismObject configuration = systemObjectCache.getSystemConfiguration(subResult); - SynchronizationContext syncCtx = loadSynchronizationContext(applicableShadow, currentShadow, - change.getObjectDelta(), change.getResource(), change.getSourceChannel(), configuration, task, subResult); - syncCtx.setUnrelatedChange(change.isUnrelatedChange()); - traceObjectSynchronization(syncCtx); - - if (!checkSynchronizationPolicy(syncCtx, eventInfo, subResult) || !checkProtected(syncCtx, eventInfo, subResult)) { - return; - } - - LOGGER.trace("Synchronization is enabled, focus class: {}, found applicable policy: {}", syncCtx.getFocusClass(), - syncCtx.getPolicyName()); - - setupSituation(syncCtx, change, subResult); - eventInfo.setOriginalSituation(syncCtx.getSituation()); - eventInfo.setNewSituation(syncCtx.getSituation()); // potentially overwritten later - - boolean isDryRun = TaskUtil.isDryRun(syncCtx.getTask()); - saveSyncMetadata(syncCtx, change, !isDryRun, now, subResult); - - if (isDryRun) { - LOGGER.debug("SYNCHRONIZATION: DONE (dry run) for {}", syncCtx.getApplicableShadow()); - subResult.recordSuccess(); - } else { - LOGGER.trace("Synchronization context:\n{}", syncCtx.debugDumpLazily(1)); - reactToChange(syncCtx, change, logDebug, eventInfo, subResult); - LOGGER.debug("SYNCHRONIZATION: DONE for {}", currentShadow); - subResult.computeStatus(); - } - } catch (SystemException ex) { - // avoid unnecessary re-wrap - eventInfo.setException(ex); - subResult.recordFatalError(ex); - throw ex; - } catch (Exception ex) { - eventInfo.setException(ex); - subResult.recordFatalError(ex); - throw new SystemException(ex); - } finally { - eventInfo.record(task); - task.markObjectActionExecutedBoundary(); - } - } - - private void cleanDeadShadow(ResourceObjectShadowChangeDescription change, OperationResult subResult) { - LOGGER.trace("Cleaning old dead shadows, checking for old links, cleaning them up"); - String shadowOid = getOidFromChange(change); - if (shadowOid == null) { - LOGGER.trace("No shadow oid, nothing to clean up."); - return; - } - - PrismObject currentOwner = repositoryService.searchShadowOwner(shadowOid, - SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), subResult); - if (currentOwner == null) { - LOGGER.trace("Nothing to do, shadow doesn't have any owner."); - return; - } - - try { - - F ownerType = currentOwner.asObjectable(); - for (ObjectReferenceType linkRef : ownerType.getLinkRef()) { - if (shadowOid.equals(linkRef.getOid())) { - Collection modifications = prismContext.deltaFactory().reference().createModificationDeleteCollection(FocusType.F_LINK_REF, currentOwner.getDefinition(), linkRef.asReferenceValue().clone()); - repositoryService.modifyObject(UserType.class, currentOwner.getOid(), modifications, subResult); - break; - } - } - } catch (ObjectNotFoundException | SchemaException | ObjectAlreadyExistsException e) { - LOGGER.error("SYNCHRONIZATION: Error in synchronization - clean up dead shadows. Change: {}", change, e); - subResult.recordFatalError("Error while cleaning dead shadow, " + e.getMessage(), e); - //nothing more to do. and we don't want to trow exception to not cancel the whole execution. - } - - subResult.computeStatus(); - } - - @Override - public SynchronizationContext loadSynchronizationContext(PrismObject applicableShadow, - PrismObject currentShadow, ObjectDelta resourceObjectDelta, - PrismObject resource, String sourceChanel, - PrismObject configuration, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, - CommunicationException, ConfigurationException, SecurityViolationException { - - SynchronizationContext syncCtx = new SynchronizationContext<>(applicableShadow, currentShadow, resourceObjectDelta, - resource, sourceChanel, prismContext, expressionFactory, task); - syncCtx.setSystemConfiguration(configuration); - - SynchronizationType synchronization = resource.asObjectable().getSynchronization(); - if (synchronization == null) { - return syncCtx; - } - - ObjectSynchronizationDiscriminatorType synchronizationDiscriminator = determineObjectSynchronizationDiscriminatorType(syncCtx, task, result); - if (synchronizationDiscriminator != null) { - syncCtx.setForceIntentChange(true); - LOGGER.trace("Setting synchronization situation to synchronization context: {}", synchronizationDiscriminator.getSynchronizationSituation()); - syncCtx.setSituation(synchronizationDiscriminator.getSynchronizationSituation()); - 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()); - } - - for (ObjectSynchronizationType objectSynchronization : synchronization.getObjectSynchronization()) { - if (isPolicyApplicable(objectSynchronization, synchronizationDiscriminator, syncCtx, result)) { - syncCtx.setObjectSynchronization(objectSynchronization); - break; - } - } - - processTag(syncCtx, result); - - return syncCtx; - } - - private void processTag(SynchronizationContext syncCtx, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, - ConfigurationException, SecurityViolationException { - PrismObject applicableShadow = syncCtx.getApplicableShadow(); - if (applicableShadow == null) { - return; - } - if (applicableShadow.asObjectable().getTag() != null) { - return; - } - RefinedObjectClassDefinition rOcd = syncCtx.findRefinedObjectClassDefinition(); - if (rOcd == null) { - // We probably do not have kind/intent yet. - return; - } - ResourceObjectMultiplicityType multiplicity = rOcd.getMultiplicity(); - if (multiplicity == null) { - return; - } - String maxOccurs = multiplicity.getMaxOccurs(); - if (maxOccurs == null || maxOccurs.equals("1")) { - return; - } - String tag = synchronizationExpressionsEvaluator.generateTag(multiplicity, applicableShadow, - syncCtx.getResource(), syncCtx.getSystemConfiguration(), "tag expression for "+applicableShadow, syncCtx.getTask(), result); - LOGGER.debug("SYNCHRONIZATION: TAG generated: {}", tag); - syncCtx.setTag(tag); - } - - private ObjectSynchronizationDiscriminatorType determineObjectSynchronizationDiscriminatorType(SynchronizationContext syncCtx, Task task, OperationResult subResult) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, - ConfigurationException, SecurityViolationException { - - SynchronizationType synchronizationType = syncCtx.getResource().asObjectable().getSynchronization(); - if (synchronizationType == null) { - return null; - } - - ObjectSynchronizationSorterType sorter = synchronizationType.getObjectSynchronizationSorter(); - if (sorter == null) { - return null; - } - - return evaluateSynchronizationSorter(sorter, syncCtx, task, subResult); - - } - - private boolean isPolicyApplicable(ObjectSynchronizationType synchronizationPolicy, - ObjectSynchronizationDiscriminatorType synchronizationDiscriminator, SynchronizationContext syncCtx, - OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return SynchronizationServiceUtils.isPolicyApplicable(synchronizationPolicy, synchronizationDiscriminator, expressionFactory, syncCtx, result); - } - - private ObjectSynchronizationDiscriminatorType evaluateSynchronizationSorter(ObjectSynchronizationSorterType synchronizationSorterType, - SynchronizationContext syncCtx, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - if (synchronizationSorterType.getExpression() == null) { - return null; - } - ExpressionType classificationExpression = synchronizationSorterType.getExpression(); - String desc = "synchronization divider type "; - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, syncCtx.getApplicableShadow(), null, - syncCtx.getResource(), syncCtx.getSystemConfiguration(), null, syncCtx.getPrismContext()); - variables.put(ExpressionConstants.VAR_CHANNEL, syncCtx.getChannel(), String.class); - 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, - classificationExpression, syncCtx.getExpressionProfile(), expressionFactory, desc, task, result); - if (evaluateDiscriminator == null) { - return null; - } - return evaluateDiscriminator.getValue(); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - private void traceObjectSynchronization(SynchronizationContext syncCtx) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("SYNCHRONIZATION determined policy: {}", syncCtx); - } - } - - private boolean checkSynchronizationPolicy(SynchronizationContext syncCtx, - SynchronizationEventInformation eventInfo, OperationResult result) throws SchemaException { - Task task = syncCtx.getTask(); - - if (syncCtx.isUnrelatedChange()) { - PrismObject applicableShadow = syncCtx.getApplicableShadow(); - Validate.notNull(applicableShadow, "No current nor old shadow present: "); - List> modifications = SynchronizationUtils.createSynchronizationTimestampsDelta(applicableShadow, prismContext); - ShadowType applicableShadowType = applicableShadow.asObjectable(); - if (applicableShadowType.getIntent() == null || SchemaConstants.INTENT_UNKNOWN.equals(applicableShadowType.getIntent())) { - PropertyDelta intentDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_INTENT, - syncCtx.getApplicableShadow().getDefinition(), syncCtx.getIntent()); - modifications.add(intentDelta); - } - if (applicableShadowType.getKind() == null || ShadowKindType.UNKNOWN == applicableShadowType.getKind()) { - PropertyDelta intentDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_KIND, - syncCtx.getApplicableShadow().getDefinition(), syncCtx.getKind()); - modifications.add(intentDelta); - } - if (applicableShadowType.getTag() == null && syncCtx.getTag() != null) { - PropertyDelta tagDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_TAG, - syncCtx.getApplicableShadow().getDefinition(), syncCtx.getTag()); - modifications.add(tagDelta); - } - - executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); - result.recordSuccess(); - LOGGER.debug("SYNCHRONIZATION: UNRELATED CHANGE for {}", syncCtx.getApplicableShadow()); - return false; - } - - if (!syncCtx.hasApplicablePolicy()) { - String message = "SYNCHRONIZATION no matching policy for " + syncCtx.getApplicableShadow() + " (" - + syncCtx.getApplicableShadow().asObjectable().getObjectClass() + ") " + " on " + syncCtx.getResource() - + ", ignoring change from channel " + syncCtx.getChannel(); - LOGGER.debug(message); - List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, false); - executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); - result.recordStatus(OperationResultStatus.NOT_APPLICABLE, message); - eventInfo.setSpecialSituation(SynchronizationEventInformation.SpecialSituation.NO_SYNCHRONIZATION_POLICY); - return false; - } - - if (!syncCtx.isSynchronizationEnabled()) { - String message = "SYNCHRONIZATION is not enabled for " + syncCtx.getResource() - + " ignoring change from channel " + syncCtx.getChannel(); - LOGGER.debug(message); - List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, true); - executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); - result.recordStatus(OperationResultStatus.NOT_APPLICABLE, message); - eventInfo.setSpecialSituation(SynchronizationEventInformation.SpecialSituation.SYNCHRONIZATION_NOT_ENABLED); - return false; - } - - return true; - } - - private boolean checkProtected(SynchronizationContext syncCtx, - SynchronizationEventInformation eventInfo, OperationResult result) throws SchemaException { - if (syncCtx.isProtected()) { - Task task = syncCtx.getTask(); - List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, true); - executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); - result.recordSuccess(); - eventInfo.setSpecialSituation(SynchronizationEventInformation.SpecialSituation.PROTECTED); - LOGGER.debug("SYNCHRONIZATION: DONE for protected shadow {}", syncCtx.getApplicableShadow()); - return false; - } - return true; - } - - private List> createShadowIntentAndSynchronizationTimestampDelta(SynchronizationContext syncCtx, boolean saveIntent) throws SchemaException { - Validate.notNull(syncCtx.getApplicableShadow(), "No current nor old shadow present: "); - ShadowType applicableShadowType = syncCtx.getApplicableShadow().asObjectable(); - List> modifications = SynchronizationUtils.createSynchronizationTimestampsDelta(syncCtx.getApplicableShadow(), - prismContext); - if (saveIntent) { - if (StringUtils.isNotBlank(syncCtx.getIntent()) && !syncCtx.getIntent().equals(applicableShadowType.getIntent())) { - PropertyDelta intentDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_INTENT, - syncCtx.getApplicableShadow().getDefinition(), syncCtx.getIntent()); - modifications.add(intentDelta); - } - if (StringUtils.isNotBlank(syncCtx.getTag()) && !syncCtx.getTag().equals(applicableShadowType.getTag())) { - PropertyDelta tagDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_TAG, - syncCtx.getApplicableShadow().getDefinition(), syncCtx.getTag()); - modifications.add(tagDelta); - } - } - return modifications; - } - - private void executeShadowModifications(PrismObject object, List> modifications, - Task task, OperationResult subResult) { - try { - repositoryService.modifyObject(ShadowType.class, object.getOid(), modifications, subResult); - task.recordObjectActionExecuted(object, ChangeType.MODIFY, null); - } catch (Throwable t) { - task.recordObjectActionExecuted(object, ChangeType.MODIFY, t); - } finally { - task.markObjectActionExecutedBoundary(); - } - } - - - private boolean alreadyLinked(F focus, PrismObject shadow) { - return focus.getLinkRef().stream().anyMatch(link -> link.getOid().equals(shadow.getOid())); - } - - private boolean isLogDebug(ResourceObjectShadowChangeDescription change) { - // Reconciliation changes are routine. Do not let them pollute the log files. - return !SchemaConstants.CHANGE_CHANNEL_RECON_URI.equals(change.getSourceChannel()); - } - - private void validate(ResourceObjectShadowChangeDescription change) { - Validate.notNull(change, "Resource object shadow change description must not be null."); - Validate.isTrue(change.getCurrentShadow() != null || change.getObjectDelta() != null, - "Object delta and current shadow are null. At least one must be provided."); - Validate.notNull(change.getResource(), "Resource in change must not be null."); - - if (consistencyChecks) { - if (change.getCurrentShadow() != null) { - change.getCurrentShadow().checkConsistence(); - ShadowUtil.checkConsistence(change.getCurrentShadow(), - "current shadow in change description"); - } - if (change.getObjectDelta() != null) { - change.getObjectDelta().checkConsistence(); - } - } - } - - // @Override - // public void notifyFailure(ResourceOperationFailureDescription - // failureDescription, - // Task task, OperationResult parentResult) { - // Validate.notNull(failureDescription, "Resource object shadow failure - // description must not be null."); - // Validate.notNull(failureDescription.getCurrentShadow(), "Current shadow - // in resource object shadow failure description must not be null."); - // Validate.notNull(failureDescription.getObjectDelta(), "Delta in resource - // object shadow failure description must not be null."); - // Validate.notNull(failureDescription.getResource(), "Resource in failure - // must not be null."); - // Validate.notNull(failureDescription.getResult(), "Result in failure - // description must not be null."); - // Validate.notNull(parentResult, "Parent operation result must not be - // null."); - // - // LOGGER.debug("SYNCHRONIZATION: received failure notifiation {}", - // failureDescription); - // - // LOGGER.error("Provisioning error: {}", - // failureDescription.getResult().getMessage()); - // - // // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO - // TODO TODO TODO TODO - // } - - /** - * XXX: in situation when one account belongs to two different idm users - * repository returns only first user, see method findShadowOwner. It - * should be changed because otherwise we can't find - * {@link SynchronizationSituationType#DISPUTED} situation - */ - private void setupSituation(SynchronizationContext syncCtx, - ResourceObjectShadowChangeDescription change, OperationResult result) { - - Task task = syncCtx.getTask(); - OperationResult subResult = result.subresult(CLASS_NAME_WITH_DOT + "setupSituation") - .setMinor() - .addArbitraryObjectAsParam("syncCtx", syncCtx) - .addArbitraryObjectAsParam("change", change) - .build(); - LOGGER.trace("Determining situation for resource object shadow."); - - try { - String shadowOid = getOidFromChange(change); - Validate.notEmpty(shadowOid, "Couldn't get resource object shadow oid from change."); - - F currentOwner; - if (syncCtx.getCurrentOwner() != null) { - currentOwner = syncCtx.getCurrentOwner(); - } else { - PrismObject currentOwnerObject = repositoryService.searchShadowOwner(shadowOid, - SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), subResult); - currentOwner = currentOwnerObject != null ? currentOwnerObject.asObjectable() : null; - } - - F correlatedOwner = syncCtx.getCorrelatedOwner(); - if (!isCorrelatedOwnerSameAsCurrentOwner(correlatedOwner, currentOwner)) { - LOGGER.error("Cannot synchronize {}, current owner and expected owner are not the same. Current owner: {}, expected owner: {}", syncCtx.getApplicableShadow(), currentOwner, correlatedOwner); - String msg = "Cannot synchronize " + syncCtx.getApplicableShadow() - + ", current owner and expected owner are not the same. Current owner: " + currentOwner - + ", expected owner: " + correlatedOwner; - result.recordFatalError(msg); - throw new ConfigurationException(msg); - } - - if (currentOwner != null) { - - LOGGER.trace("Shadow OID {} does have owner: {}", shadowOid, currentOwner.getName()); - - syncCtx.setCurrentOwner(currentOwner); - - if (syncCtx.getSituation() != null) { - return; - } - - SynchronizationSituationType state; - ChangeType changeType = getModificationType(change); - switch (changeType) { - case ADD: - case MODIFY: - // if user is found it means account/group is linked to - // resource - state = SynchronizationSituationType.LINKED; - break; - case DELETE: - state = SynchronizationSituationType.DELETED; - break; - default: - throw new AssertionError(changeType); - } - syncCtx.setSituation(state); - } else { - LOGGER.trace("Resource object shadow doesn't have owner."); - determineSituationWithCorrelation(syncCtx, change, task, result); - } - } catch (Exception ex) { - LOGGER.error("Error occurred during resource object shadow owner lookup."); - throw new SystemException( - "Error occurred during resource object shadow owner lookup, reason: " + ex.getMessage(), ex); - } finally { - subResult.computeStatus(); - String syncSituationValue = syncCtx.getSituation() != null ? syncCtx.getSituation().value() : null; - if (isLogDebug(change)) { - LOGGER.debug("SYNCHRONIZATION: SITUATION: '{}', currentOwner={}, correlatedOwner={}", - syncSituationValue, syncCtx.getCurrentOwner(), - syncCtx.getCorrelatedOwner()); - } else { - LOGGER.trace("SYNCHRONIZATION: SITUATION: '{}', currentOwner={}, correlatedOwner={}", - syncSituationValue, syncCtx.getCurrentOwner(), - syncCtx.getCorrelatedOwner()); - } - } - } - - private boolean isCorrelatedOwnerSameAsCurrentOwner(F expectedOwner, F currentOwnerType) { - return expectedOwner == null || currentOwnerType == null || expectedOwner.getOid().equals(currentOwnerType.getOid()); - } - - private String getOidFromChange(ResourceObjectShadowChangeDescription change) { - if (change.getCurrentShadow() != null && StringUtils.isNotEmpty(change.getCurrentShadow().getOid())) { - return change.getCurrentShadow().getOid(); - } - if (change.getOldShadow() != null && StringUtils.isNotEmpty(change.getOldShadow().getOid())) { - return change.getOldShadow().getOid(); - } - - if (change.getObjectDelta() == null || StringUtils.isEmpty(change.getObjectDelta().getOid())) { - throw new IllegalArgumentException( - "Oid was not defined in change (not in current, old shadow, delta)."); - } - - return change.getObjectDelta().getOid(); - } - - /** - * Tries to match specified focus and shadow. Return true if it matches, - * false otherwise. - */ - @Override - public boolean matchUserCorrelationRule(PrismObject shadow, - PrismObject focus, ResourceType resourceType, - PrismObject configuration, Task task, OperationResult result) - throws ConfigurationException, SchemaException, ObjectNotFoundException, - ExpressionEvaluationException, CommunicationException, SecurityViolationException { - - SynchronizationContext synchronizationContext = loadSynchronizationContext(shadow, shadow, null, - resourceType.asPrismObject(), task.getChannel(), configuration, task, result); - return synchronizationExpressionsEvaluator.matchFocusByCorrelationRule(synchronizationContext, focus, result); - } - - /** - * account is not linked to user. you have to use correlation and - * confirmation rule to be sure user for this account doesn't exists - * resourceShadow only contains the data that were in the repository before - * the change. But the correlation/confirmation should work on the updated - * data. Therefore let's apply the changes before running - * correlation/confirmation - */ - private void determineSituationWithCorrelation(SynchronizationContext syncCtx, ResourceObjectShadowChangeDescription change, - Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (ChangeType.DELETE.equals(getModificationType(change))) { - // account was deleted and we know it didn't have owner - if (syncCtx.getSituation() == null) { - syncCtx.setSituation(SynchronizationSituationType.DELETED); - } - return; - } - - F user = syncCtx.getCorrelatedOwner(); - LOGGER.trace("Correlated owner present in synchronization context: {}", user); - if (user != null) { - if (syncCtx.getSituation() != null) { - return; - } - syncCtx.setSituation(getSynchornizationSituationFromChange(change)); - return; - } - - - PrismObject resourceShadow = change.getCurrentShadow(); - - ObjectDelta syncDelta = change.getObjectDelta(); - if (resourceShadow == null && syncDelta != null && ChangeType.ADD.equals(syncDelta.getChangeType())) { - LOGGER.trace("Trying to compute current shadow from change delta add."); - PrismObject shadow = syncDelta.computeChangedObject(syncDelta.getObjectToAdd()); - resourceShadow = shadow; - change.setCurrentShadow(shadow); - } - Validate.notNull(resourceShadow, "Current shadow must not be null."); - - ResourceType resource = change.getResource().asObjectable(); - validateResourceInShadow(resourceShadow.asObjectable(), resource); - - SynchronizationSituationType state; - LOGGER.trace("SYNCHRONIZATION: CORRELATION: Looking for list of {} objects based on correlation rule.", - syncCtx.getFocusClass().getSimpleName()); - List> users = synchronizationExpressionsEvaluator.findFocusesByCorrelationRule(syncCtx.getFocusClass(), - resourceShadow.asObjectable(), syncCtx.getCorrelation(), resource, - syncCtx.getSystemConfiguration().asObjectable(), task, result); - if (users == null) { - users = new ArrayList<>(); - } - - if (users.size() > 1) { - if (syncCtx.getConfirmation() == null) { - LOGGER.trace("SYNCHRONIZATION: CONFIRMATION: no confirmation defined."); - } else { - LOGGER.debug("SYNCHRONIZATION: CONFIRMATION: Checking objects from correlation with confirmation rule."); - users = synchronizationExpressionsEvaluator.findUserByConfirmationRule(syncCtx.getFocusClass(), users, - resourceShadow.asObjectable(), resource, syncCtx.getSystemConfiguration().asObjectable(), - syncCtx.getConfirmation(), task, result); - } - } - - switch (users.size()) { - case 0: - state = SynchronizationSituationType.UNMATCHED; - break; - case 1: - state = getSynchornizationSituationFromChange(change); - - user = users.get(0).asObjectable(); - break; - default: - state = SynchronizationSituationType.DISPUTED; - } - - syncCtx.setCorrelatedOwner(user); - syncCtx.setSituation(state); - } - - private SynchronizationSituationType getSynchornizationSituationFromChange(ResourceObjectShadowChangeDescription change) { - switch (getModificationType(change)) { - case ADD: - case MODIFY: - return SynchronizationSituationType.UNLINKED; - case DELETE: - return SynchronizationSituationType.DELETED; - } - - return null; - } - - private void validateResourceInShadow(ShadowType shadow, ResourceType resource) { - if (shadow.getResourceRef() != null) { - return; - } - - ObjectReferenceType reference = new ObjectReferenceType(); - reference.setOid(resource.getOid()); - reference.setType(ObjectTypes.RESOURCE.getTypeQName()); - - shadow.setResourceRef(reference); - } - - /** - * @return method checks change type in object delta if available, otherwise - * returns {@link ChangeType#ADD} - */ - private ChangeType getModificationType(ResourceObjectShadowChangeDescription change) { - if (change.getObjectDelta() != null) { - return change.getObjectDelta().getChangeType(); - } else { - return ChangeType.ADD; - } - } - - private void reactToChange(SynchronizationContext syncCtx, - ResourceObjectShadowChangeDescription change, boolean logDebug, SynchronizationEventInformation eventInfo, - OperationResult parentResult) - throws ConfigurationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ExpressionEvaluationException, CommunicationException { - - SynchronizationReactionType reaction = syncCtx.getReaction(parentResult); - if (reaction == null) { - LOGGER.trace("No reaction is defined for situation {} in {}", syncCtx.getSituation(), syncCtx.getResource()); - return; - } - - 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. - if (change.getObjectDelta() == null) { - doReconciliation = true; - } - } - - ModelExecuteOptions options = new ModelExecuteOptions(); - options.setReconcile(doReconciliation); - options.setLimitPropagation(syncCtx.isLimitPropagation()); - - final boolean willSynchronize = isSynchronize(reaction); - LensContext lensContext; - - Task task = syncCtx.getTask(); - if (willSynchronize) { - lensContext = createLensContext(syncCtx, change, options, parentResult); - lensContext.setDoReconciliationForAllProjections(BooleanUtils.isTrue(reaction.isReconcileAll())); - LOGGER.trace("---[ SYNCHRONIZATION context before action execution ]-------------------------\n" - + "{}\n------------------------------------------", lensContext.debugDumpLazily()); - } else { - lensContext = null; - } - - if (willSynchronize) { - - // there's no point in calling executeAction without context - so - // the actions are executed only if synchronize == true - executeActions(syncCtx, lensContext, BeforeAfterType.BEFORE, logDebug, task, parentResult); - - Iterator iterator = lensContext.getProjectionContextsIterator(); - LensProjectionContext originalProjectionContext = iterator.hasNext() ? iterator.next() : null; - - if (originalProjectionContext != null) { - originalProjectionContext.setSynchronizationSource(true); - } - - try { - - clockworkMedic.enterModelMethod(false); - try { - if (change.isSimulate()) { - clockwork.previewChanges(lensContext, null, task, parentResult); - } else { - clockwork.run(lensContext, task, parentResult); - } - } finally { - clockworkMedic.exitModelMethod(false); - } - - } catch (ConfigurationException | ObjectNotFoundException | SchemaException | - PolicyViolationException | ExpressionEvaluationException | ObjectAlreadyExistsException | - CommunicationException | SecurityViolationException | PreconditionViolationException | RuntimeException e) { - LOGGER.error("SYNCHRONIZATION: Error in synchronization on {} for situation {}: {}: {}. Change was {}", - syncCtx.getResource(), syncCtx.getSituation(), e.getClass().getSimpleName(), e.getMessage(), change, e); -// parentResult.recordFatalError("Error during sync", e); - // what to do here? We cannot throw the error back. All that the notifyChange method - // could do is to convert it to SystemException. But that indicates an internal error and it will - // break whatever code called the notifyChange in the first place. We do not want that. - // If the clockwork could not do anything with the exception then perhaps nothing can be done at all. - // So just log the error (the error should be remembered in the result and task already) - // and then just go on. - } - - // note: actions "AFTER" seem to be useless here (basically they - // modify lens context - which is relevant only if followed by - // clockwork run) - executeActions(syncCtx, lensContext, BeforeAfterType.AFTER, logDebug, task, parentResult); - - if (originalProjectionContext != null) { - SynchronizationSituationType resolvedSituation = originalProjectionContext.getSynchronizationSituationResolved(); - if (eventInfo.getNewSituation() != resolvedSituation && resolvedSituation != null) { - // Resolved situation of null means it is unknown. So we stick with situation as originally determined. - LOGGER.trace("We have changed new situation: {} -> {}", eventInfo.getNewSituation(), resolvedSituation); - eventInfo.setNewSituation(resolvedSituation); - } - } - - } else { - LOGGER.trace("Skipping clockwork run on {} for situation {}, synchronize is set to false.", - syncCtx.getResource(), syncCtx.getSituation()); - } - } - - @NotNull - private LensContext createLensContext(SynchronizationContext syncCtx, - ResourceObjectShadowChangeDescription change, ModelExecuteOptions options, - OperationResult parentResult) throws ObjectNotFoundException, SchemaException { - - LensContext context = contextFactory.createSyncContext(syncCtx.getFocusClass(), change); - context.setLazyAuditRequest(true); - context.setSystemConfiguration(syncCtx.getSystemConfiguration()); - context.setOptions(options); - - ResourceType resource = change.getResource().asObjectable(); - if (ModelExecuteOptions.isLimitPropagation(options)) { - context.setTriggeredResource(resource); - } - - context.rememberResource(resource); - PrismObject shadow = getShadowFromChange(change); - 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 tombstone = isThombstone(change); - ResourceShadowDiscriminator discriminator = new ResourceShadowDiscriminator(resource.getOid(), kind, intent, shadow.asObjectable().getTag(), 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(); - if (delta != null) { - projectionContext.setSyncDelta(delta); - } else { - projectionContext.setSyncAbsoluteTrigger(true); - } - - // we insert account if available in change - projectionContext.setLoadedObject(shadow); - - if (!tombstone && !containsIncompleteItems(shadow)) { - projectionContext.setFullShadow(true); - } - projectionContext.setFresh(true); - - if (delta != null && delta.isDelete()) { - projectionContext.setExists(false); - } else { - projectionContext.setExists(true); - } - - projectionContext.setDoReconciliation(ModelExecuteOptions.isReconcile(options)); - - // Focus context - 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) - .asObjectable(); - context.setFocusTemplate(objectTemplate); - context.setFocusTemplateExternallySet(true); // we do not want to override this template e.g. when subtype changes - } - - return context; - } - - private boolean containsIncompleteItems(PrismObject shadow) { - ShadowAttributesType attributes = shadow.asObjectable().getAttributes(); - //noinspection SimplifiableIfStatement - if (attributes == null) { - return false; // strictly speaking this is right; but we perhaps should not consider this shadow as fully loaded :) - } else { - return ((PrismContainerValue) (attributes.asPrismContainerValue())).getItems().stream() - .anyMatch(Item::isIncomplete); - } - } - - private PrismObject getShadowFromChange(ResourceObjectShadowChangeDescription change) { - if (change.getCurrentShadow() != null) { - return change.getCurrentShadow(); - } - if (change.getOldShadow() != null) { - return change.getOldShadow(); - } - return null; - } - - private ShadowKindType getKind(PrismObject shadow, ShadowKindType objectSynchronizationKind) { - ShadowKindType shadowKind = shadow.asObjectable().getKind(); - if (shadowKind != null) { - return shadowKind; - } - return objectSynchronizationKind; - } - - private String getIntent(PrismObject shadow, - String objectSynchronizationIntent) { - String shadowIntent = shadow.asObjectable().getIntent(); - if (shadowIntent != null) { - return shadowIntent; - } - return objectSynchronizationIntent; - } - - private boolean isThombstone(ResourceObjectShadowChangeDescription change) { - PrismObject shadow = null; - if (change.getOldShadow() != null) { - shadow = change.getOldShadow(); - } else if (change.getCurrentShadow() != null) { - shadow = change.getCurrentShadow(); - } - if (shadow != null) { - if (shadow.asObjectable().isDead() != null) { - return shadow.asObjectable().isDead(); - } - } - ObjectDelta objectDelta = change.getObjectDelta(); - return objectDelta != null && objectDelta.isDelete(); - } - - private boolean isSynchronize(SynchronizationReactionType reactionDefinition) { - if (reactionDefinition.isSynchronize() != null) { - return reactionDefinition.isSynchronize(); - } - return !reactionDefinition.getAction().isEmpty(); - } - - /** - * Saves situation, timestamps, kind and intent (if needed) - */ - private void saveSyncMetadata(SynchronizationContext syncCtx, - ResourceObjectShadowChangeDescription change, boolean full, XMLGregorianCalendar now, OperationResult result) { - PrismObject shadow = syncCtx.getCurrentShadow(); - if (shadow == null) { - return; - } - - Task task = syncCtx.getTask(); - - try { - ShadowType shadowType = shadow.asObjectable(); - // new situation description - List> deltas = SynchronizationUtils - .createSynchronizationSituationAndDescriptionDelta(shadow, syncCtx.getSituation(), - change.getSourceChannel(), full, now, prismContext); - - if (shadowType.getKind() == null || ShadowKindType.UNKNOWN == shadowType.getKind()) { - PropertyDelta kindDelta = prismContext.deltaFactory().property().createReplaceDelta(shadow.getDefinition(), - ShadowType.F_KIND, syncCtx.getKind()); - deltas.add(kindDelta); - } - - if (shouldSaveIntent(syncCtx)) { - PropertyDelta intentDelta = prismContext.deltaFactory().property().createReplaceDelta(shadow.getDefinition(), - ShadowType.F_INTENT, syncCtx.getIntent()); - deltas.add(intentDelta); - } - - if (shadowType.getTag() == null && syncCtx.getTag() != null) { - PropertyDelta tagDelta = prismContext.deltaFactory().property().createReplaceDelta(shadow.getDefinition(), - ShadowType.F_TAG, syncCtx.getTag()); - deltas.add(tagDelta); - } - - repositoryService.modifyObject(shadowType.getClass(), shadow.getOid(), deltas, result); - ItemDeltaCollectionsUtil.applyTo(deltas, shadow); - task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, null); - } catch (ObjectNotFoundException ex) { - task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, ex); - // This may happen e.g. during some recon-livesync interactions. - // If the shadow is gone then it is gone. No point in recording the - // situation any more. - LOGGER.debug( - "Could not update situation in account, because shadow {} does not exist any more (this may be harmless)", - shadow.getOid()); - syncCtx.setShadowExistsInRepo(false); - result.getLastSubresult().setStatus(OperationResultStatus.HANDLED_ERROR); - } catch (ObjectAlreadyExistsException | SchemaException ex) { - task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, ex); - LoggingUtils.logException(LOGGER, - "### SYNCHRONIZATION # notifyChange(..): Save of synchronization situation failed: could not modify shadow " - + shadow.getOid() + ": " + ex.getMessage(), - ex); - result.recordFatalError("Save of synchronization situation failed: could not modify shadow " - + shadow.getOid() + ": " + ex.getMessage(), ex); - throw new SystemException("Save of synchronization situation failed: could not modify shadow " - + shadow.getOid() + ": " + ex.getMessage(), ex); - } catch (Throwable t) { - task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, t); - throw t; - } - } - - private boolean shouldSaveIntent(SynchronizationContext syncCtx) throws SchemaException { - ShadowType shadow = syncCtx.getCurrentShadow().asObjectable(); - if (shadow.getIntent() == null) { - return true; - } - - if (SchemaConstants.INTENT_UNKNOWN.equals(shadow.getIntent())) { - return true; - } - - if (syncCtx.isForceIntentChange()) { - String objectSyncIntent = syncCtx.getIntent(); - //noinspection RedundantIfStatement - if (!MiscSchemaUtil.equalsIntent(shadow.getIntent(), objectSyncIntent)) { - return true; - } - } - - return false; - } - - private void executeActions(SynchronizationContext syncCtx, LensContext context, - BeforeAfterType order, boolean logDebug, Task task, OperationResult parentResult) - throws ConfigurationException, SchemaException, ObjectNotFoundException, CommunicationException, - SecurityViolationException, ExpressionEvaluationException { - - SynchronizationReactionType reaction = syncCtx.getReaction(parentResult); - for (SynchronizationActionType actionDef : reaction.getAction()) { - if ((actionDef.getOrder() == null && order == BeforeAfterType.BEFORE) - || (actionDef.getOrder() != null && actionDef.getOrder() == order)) { - - String handlerUri = actionDef.getHandlerUri(); - if (handlerUri == null) { - LOGGER.error("Action definition in resource {} doesn't contain handler URI", syncCtx.getResource()); - throw new ConfigurationException( - "Action definition in resource " + syncCtx.getResource() + " doesn't contain handler URI"); - } - - Action action = actionManager.getActionInstance(handlerUri); - if (action == null) { - LOGGER.warn("Couldn't create action with uri '{}' in resource {}, skipping action.", handlerUri, - syncCtx.getResource()); - continue; - } - - if (logDebug) { - LOGGER.debug("SYNCHRONIZATION: ACTION: Executing: {}.", action.getClass()); - } else { - LOGGER.trace("SYNCHRONIZATION: ACTION: Executing: {}.", action.getClass()); - } - SynchronizationSituation situation = new SynchronizationSituation<>(syncCtx.getCurrentOwner(), syncCtx.getCorrelatedOwner(), syncCtx.getSituation()); - action.handle(context, situation, null, task, parentResult); - } - } - } - - @Override - public String getName() { - return "model synchronization service"; - } - - private static class SynchronizationEventInformation { - - private String objectName; - private String objectDisplayName; - private String objectOid; - private Throwable exception; - private long started; - - private boolean alreadySaved; - - private enum SpecialSituation { - NO_SYNCHRONIZATION_POLICY, SYNCHRONIZATION_NOT_ENABLED, PROTECTED - } - - private SynchronizationSituationType originalSituation; - private SynchronizationSituationType newSituation; - private SpecialSituation specialSituation; - - private SynchronizationEventInformation(PrismObject currentShadow) { - started = System.currentTimeMillis(); - if (currentShadow != null) { - final ShadowType shadow = currentShadow.asObjectable(); - objectName = PolyString.getOrig(shadow.getName()); - objectDisplayName = StatisticsUtil.getDisplayName(shadow); - objectOid = currentShadow.getOid(); - } - } - - private void setSituation(SynchronizationInformation.Record increment, - SynchronizationSituationType situation) { - if (situation != null) { - switch (situation) { - case LINKED: - increment.setCountLinked(1); - break; - case UNLINKED: - increment.setCountUnlinked(1); - break; - case DELETED: - increment.setCountDeleted(1); - break; - case DISPUTED: - increment.setCountDisputed(1); - break; - case UNMATCHED: - increment.setCountUnmatched(1); - break; - default: - throw new AssertionError(situation); - } - } - } - - private void setSpecialSituation(SpecialSituation situation) { - specialSituation = situation; - } - - private void setOriginalSituation(SynchronizationSituationType situation) { - originalSituation = situation; - } - - private SynchronizationSituationType getNewSituation() { - return newSituation; - } - - private void setNewSituation(SynchronizationSituationType situation) { - newSituation = situation; - } - - public void setException(Exception ex) { - exception = ex; - } - - private void record(Task task) { - SynchronizationInformation.Record originalStateIncrement = new SynchronizationInformation.Record(); - SynchronizationInformation.Record newStateIncrement = new SynchronizationInformation.Record(); - if (specialSituation != null) { - switch (specialSituation) { - case NO_SYNCHRONIZATION_POLICY: - originalStateIncrement.setCountNoSynchronizationPolicy(1); - newStateIncrement.setCountNoSynchronizationPolicy(1); - break; - case SYNCHRONIZATION_NOT_ENABLED: - originalStateIncrement.setCountSynchronizationDisabled(1); - newStateIncrement.setCountSynchronizationDisabled(1); - break; - case PROTECTED: - originalStateIncrement.setCountProtected(1); - newStateIncrement.setCountProtected(1); - break; - default: - throw new AssertionError(specialSituation); - } - } else { - setSituation(originalStateIncrement, originalSituation); - setSituation(newStateIncrement, newSituation); - } - saveToTask(task, originalStateIncrement, newStateIncrement); - } - - private void saveToTask(Task task, SynchronizationInformation.Record originalStateIncrement, - SynchronizationInformation.Record newStateIncrement) { - task.recordSynchronizationOperationEnd(objectName, objectDisplayName, ShadowType.COMPLEX_TYPE, - objectOid, started, exception, originalStateIncrement, newStateIncrement); - if (alreadySaved) { - throw new IllegalStateException("SynchronizationEventInformation already saved"); - } else { - alreadySaved = true; - } - } - } -} +/* + + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.sync; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.common.SynchronizationUtils; +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +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.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultStatus; +import com.evolveum.midpoint.schema.statistics.StatisticsUtil; +import com.evolveum.midpoint.schema.statistics.SynchronizationInformation; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.lang.BooleanUtils; +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.stereotype.Service; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; +import java.util.*; + +import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; + +/** + * Synchronization service receives change notifications from provisioning. It + * decides which synchronization policy to use and evaluates it (correlation, + * confirmation, situations, reaction, ...) + * + * @author lazyman + * @author Radovan Semancik + * + * Note: don't autowire this bean by implementing class, as it is + * proxied by Spring AOP. Use the interface instead. + */ +@Service(value = "synchronizationService") +public class SynchronizationServiceImpl implements SynchronizationService { + + private static final Trace LOGGER = TraceManager.getTrace(SynchronizationServiceImpl.class); + + private static final String CLASS_NAME_WITH_DOT = SynchronizationServiceImpl.class.getName() + "."; + private static final String NOTIFY_CHANGE = CLASS_NAME_WITH_DOT + "notifyChange"; + + @Autowired private ActionManager actionManager; + @Autowired private SynchronizationExpressionsEvaluator synchronizationExpressionsEvaluator; + @Autowired private ContextFactory contextFactory; + @Autowired private Clockwork clockwork; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private SystemObjectCache systemObjectCache; + @Autowired private PrismContext prismContext; + @Autowired private Clock clock; + @Autowired private ClockworkMedic clockworkMedic; + + @Autowired + @Qualifier("cacheRepositoryService") + private RepositoryService repositoryService; + + @Override + public void notifyChange(ResourceObjectShadowChangeDescription change, Task task, OperationResult parentResult) { + validate(change); + Validate.notNull(parentResult, "Parent operation result must not be null."); + + boolean logDebug = isLogDebug(change); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("SYNCHRONIZATION: received change notification:\n{}", change.debugDump(1)); + } else if (logDebug) { + LOGGER.debug("SYNCHRONIZATION: received change notification {}", change); + } + + OperationResult subResult = parentResult.subresult(NOTIFY_CHANGE) + .addArbitraryObjectAsParam("change", change) + .addArbitraryObjectAsContext("task", task) + .build(); + + if (change.isCleanDeadShadow()) { + cleanDeadShadow(change, subResult); + subResult.computeStatus(); + return; + } + + PrismObject currentShadow = change.getCurrentShadow(); + PrismObject applicableShadow; + if (currentShadow != null) { + applicableShadow = currentShadow; + } else { + // We need this e.g. in case of delete + applicableShadow = change.getOldShadow(); + } + + XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); + SynchronizationEventInformation eventInfo = new SynchronizationEventInformation(applicableShadow); + + try { + PrismObject configuration = systemObjectCache.getSystemConfiguration(subResult); + SynchronizationContext syncCtx = loadSynchronizationContext(applicableShadow, currentShadow, + change.getObjectDelta(), change.getResource(), change.getSourceChannel(), configuration, task, subResult); + syncCtx.setUnrelatedChange(change.isUnrelatedChange()); + traceObjectSynchronization(syncCtx); + + if (!checkSynchronizationPolicy(syncCtx, eventInfo, subResult) || !checkProtected(syncCtx, eventInfo, subResult)) { + return; + } + + LOGGER.trace("Synchronization is enabled, focus class: {}, found applicable policy: {}", syncCtx.getFocusClass(), + syncCtx.getPolicyName()); + + setupSituation(syncCtx, change, subResult); + eventInfo.setOriginalSituation(syncCtx.getSituation()); + eventInfo.setNewSituation(syncCtx.getSituation()); // potentially overwritten later + + boolean isDryRun = TaskUtil.isDryRun(syncCtx.getTask()); + saveSyncMetadata(syncCtx, change, !isDryRun, now, subResult); + + if (isDryRun) { + LOGGER.debug("SYNCHRONIZATION: DONE (dry run) for {}", syncCtx.getApplicableShadow()); + subResult.recordSuccess(); + } else { + LOGGER.trace("Synchronization context:\n{}", syncCtx.debugDumpLazily(1)); + reactToChange(syncCtx, change, logDebug, eventInfo, subResult); + LOGGER.debug("SYNCHRONIZATION: DONE for {}", currentShadow); + subResult.computeStatus(); + } + } catch (SystemException ex) { + // avoid unnecessary re-wrap + eventInfo.setException(ex); + subResult.recordFatalError(ex); + throw ex; + } catch (Exception ex) { + eventInfo.setException(ex); + subResult.recordFatalError(ex); + throw new SystemException(ex); + } finally { + eventInfo.record(task); + task.markObjectActionExecutedBoundary(); + } + } + + private void cleanDeadShadow(ResourceObjectShadowChangeDescription change, OperationResult subResult) { + LOGGER.trace("Cleaning old dead shadows, checking for old links, cleaning them up"); + String shadowOid = getOidFromChange(change); + if (shadowOid == null) { + LOGGER.trace("No shadow oid, nothing to clean up."); + return; + } + + PrismObject currentOwner = repositoryService.searchShadowOwner(shadowOid, + SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), subResult); + if (currentOwner == null) { + LOGGER.trace("Nothing to do, shadow doesn't have any owner."); + return; + } + + try { + + F ownerType = currentOwner.asObjectable(); + for (ObjectReferenceType linkRef : ownerType.getLinkRef()) { + if (shadowOid.equals(linkRef.getOid())) { + Collection modifications = prismContext.deltaFactory().reference().createModificationDeleteCollection(FocusType.F_LINK_REF, currentOwner.getDefinition(), linkRef.asReferenceValue().clone()); + repositoryService.modifyObject(UserType.class, currentOwner.getOid(), modifications, subResult); + break; + } + } + } catch (ObjectNotFoundException | SchemaException | ObjectAlreadyExistsException e) { + LOGGER.error("SYNCHRONIZATION: Error in synchronization - clean up dead shadows. Change: {}", change, e); + subResult.recordFatalError("Error while cleaning dead shadow, " + e.getMessage(), e); + //nothing more to do. and we don't want to trow exception to not cancel the whole execution. + } + + subResult.computeStatus(); + } + + @Override + public SynchronizationContext loadSynchronizationContext(PrismObject applicableShadow, + PrismObject currentShadow, ObjectDelta resourceObjectDelta, + PrismObject resource, String sourceChanel, + PrismObject configuration, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException, SecurityViolationException { + + SynchronizationContext syncCtx = new SynchronizationContext<>(applicableShadow, currentShadow, resourceObjectDelta, + resource, sourceChanel, prismContext, expressionFactory, task); + syncCtx.setSystemConfiguration(configuration); + + SynchronizationType synchronization = resource.asObjectable().getSynchronization(); + if (synchronization == null) { + return syncCtx; + } + + ObjectSynchronizationDiscriminatorType synchronizationDiscriminator = determineObjectSynchronizationDiscriminatorType(syncCtx, task, result); + if (synchronizationDiscriminator != null) { + syncCtx.setForceIntentChange(true); + LOGGER.trace("Setting synchronization situation to synchronization context: {}", synchronizationDiscriminator.getSynchronizationSituation()); + syncCtx.setSituation(synchronizationDiscriminator.getSynchronizationSituation()); + 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()); + } + + for (ObjectSynchronizationType objectSynchronization : synchronization.getObjectSynchronization()) { + if (isPolicyApplicable(objectSynchronization, synchronizationDiscriminator, syncCtx, result)) { + syncCtx.setObjectSynchronization(objectSynchronization); + break; + } + } + + processTag(syncCtx, result); + + return syncCtx; + } + + private void processTag(SynchronizationContext syncCtx, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, SecurityViolationException { + PrismObject applicableShadow = syncCtx.getApplicableShadow(); + if (applicableShadow == null) { + return; + } + if (applicableShadow.asObjectable().getTag() != null) { + return; + } + RefinedObjectClassDefinition rOcd = syncCtx.findRefinedObjectClassDefinition(); + if (rOcd == null) { + // We probably do not have kind/intent yet. + return; + } + ResourceObjectMultiplicityType multiplicity = rOcd.getMultiplicity(); + if (multiplicity == null) { + return; + } + String maxOccurs = multiplicity.getMaxOccurs(); + if (maxOccurs == null || maxOccurs.equals("1")) { + return; + } + String tag = synchronizationExpressionsEvaluator.generateTag(multiplicity, applicableShadow, + syncCtx.getResource(), syncCtx.getSystemConfiguration(), "tag expression for "+applicableShadow, syncCtx.getTask(), result); + LOGGER.debug("SYNCHRONIZATION: TAG generated: {}", tag); + syncCtx.setTag(tag); + } + + private ObjectSynchronizationDiscriminatorType determineObjectSynchronizationDiscriminatorType(SynchronizationContext syncCtx, Task task, OperationResult subResult) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, SecurityViolationException { + + SynchronizationType synchronizationType = syncCtx.getResource().asObjectable().getSynchronization(); + if (synchronizationType == null) { + return null; + } + + ObjectSynchronizationSorterType sorter = synchronizationType.getObjectSynchronizationSorter(); + if (sorter == null) { + return null; + } + + return evaluateSynchronizationSorter(sorter, syncCtx, task, subResult); + + } + + private boolean isPolicyApplicable(ObjectSynchronizationType synchronizationPolicy, + ObjectSynchronizationDiscriminatorType synchronizationDiscriminator, SynchronizationContext syncCtx, + OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + return SynchronizationServiceUtils.isPolicyApplicable(synchronizationPolicy, synchronizationDiscriminator, expressionFactory, syncCtx, result); + } + + private ObjectSynchronizationDiscriminatorType evaluateSynchronizationSorter(ObjectSynchronizationSorterType synchronizationSorterType, + SynchronizationContext syncCtx, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + if (synchronizationSorterType.getExpression() == null) { + return null; + } + ExpressionType classificationExpression = synchronizationSorterType.getExpression(); + String desc = "synchronization divider type "; + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, syncCtx.getApplicableShadow(), null, + syncCtx.getResource(), syncCtx.getSystemConfiguration(), null, syncCtx.getPrismContext()); + variables.put(ExpressionConstants.VAR_CHANNEL, syncCtx.getChannel(), String.class); + 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, + classificationExpression, syncCtx.getExpressionProfile(), expressionFactory, desc, task, result); + if (evaluateDiscriminator == null) { + return null; + } + return evaluateDiscriminator.getValue(); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + private void traceObjectSynchronization(SynchronizationContext syncCtx) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("SYNCHRONIZATION determined policy: {}", syncCtx); + } + } + + private boolean checkSynchronizationPolicy(SynchronizationContext syncCtx, + SynchronizationEventInformation eventInfo, OperationResult result) throws SchemaException { + Task task = syncCtx.getTask(); + + if (syncCtx.isUnrelatedChange()) { + PrismObject applicableShadow = syncCtx.getApplicableShadow(); + Validate.notNull(applicableShadow, "No current nor old shadow present: "); + List> modifications = SynchronizationUtils.createSynchronizationTimestampsDelta(applicableShadow, prismContext); + ShadowType applicableShadowType = applicableShadow.asObjectable(); + if (applicableShadowType.getIntent() == null || SchemaConstants.INTENT_UNKNOWN.equals(applicableShadowType.getIntent())) { + PropertyDelta intentDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_INTENT, + syncCtx.getApplicableShadow().getDefinition(), syncCtx.getIntent()); + modifications.add(intentDelta); + } + if (applicableShadowType.getKind() == null || ShadowKindType.UNKNOWN == applicableShadowType.getKind()) { + PropertyDelta intentDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_KIND, + syncCtx.getApplicableShadow().getDefinition(), syncCtx.getKind()); + modifications.add(intentDelta); + } + if (applicableShadowType.getTag() == null && syncCtx.getTag() != null) { + PropertyDelta tagDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_TAG, + syncCtx.getApplicableShadow().getDefinition(), syncCtx.getTag()); + modifications.add(tagDelta); + } + + executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); + result.recordSuccess(); + LOGGER.debug("SYNCHRONIZATION: UNRELATED CHANGE for {}", syncCtx.getApplicableShadow()); + return false; + } + + if (!syncCtx.hasApplicablePolicy()) { + String message = "SYNCHRONIZATION no matching policy for " + syncCtx.getApplicableShadow() + " (" + + syncCtx.getApplicableShadow().asObjectable().getObjectClass() + ") " + " on " + syncCtx.getResource() + + ", ignoring change from channel " + syncCtx.getChannel(); + LOGGER.debug(message); + List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, false); + executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); + result.recordStatus(OperationResultStatus.NOT_APPLICABLE, message); + eventInfo.setSpecialSituation(SynchronizationEventInformation.SpecialSituation.NO_SYNCHRONIZATION_POLICY); + return false; + } + + if (!syncCtx.isSynchronizationEnabled()) { + String message = "SYNCHRONIZATION is not enabled for " + syncCtx.getResource() + + " ignoring change from channel " + syncCtx.getChannel(); + LOGGER.debug(message); + List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, true); + executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); + result.recordStatus(OperationResultStatus.NOT_APPLICABLE, message); + eventInfo.setSpecialSituation(SynchronizationEventInformation.SpecialSituation.SYNCHRONIZATION_NOT_ENABLED); + return false; + } + + return true; + } + + private boolean checkProtected(SynchronizationContext syncCtx, + SynchronizationEventInformation eventInfo, OperationResult result) throws SchemaException { + if (syncCtx.isProtected()) { + Task task = syncCtx.getTask(); + List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, true); + executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, result); + result.recordSuccess(); + eventInfo.setSpecialSituation(SynchronizationEventInformation.SpecialSituation.PROTECTED); + LOGGER.debug("SYNCHRONIZATION: DONE for protected shadow {}", syncCtx.getApplicableShadow()); + return false; + } + return true; + } + + private List> createShadowIntentAndSynchronizationTimestampDelta(SynchronizationContext syncCtx, boolean saveIntent) throws SchemaException { + Validate.notNull(syncCtx.getApplicableShadow(), "No current nor old shadow present: "); + ShadowType applicableShadowType = syncCtx.getApplicableShadow().asObjectable(); + List> modifications = SynchronizationUtils.createSynchronizationTimestampsDelta(syncCtx.getApplicableShadow(), + prismContext); + if (saveIntent) { + if (StringUtils.isNotBlank(syncCtx.getIntent()) && !syncCtx.getIntent().equals(applicableShadowType.getIntent())) { + PropertyDelta intentDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_INTENT, + syncCtx.getApplicableShadow().getDefinition(), syncCtx.getIntent()); + modifications.add(intentDelta); + } + if (StringUtils.isNotBlank(syncCtx.getTag()) && !syncCtx.getTag().equals(applicableShadowType.getTag())) { + PropertyDelta tagDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_TAG, + syncCtx.getApplicableShadow().getDefinition(), syncCtx.getTag()); + modifications.add(tagDelta); + } + } + return modifications; + } + + private void executeShadowModifications(PrismObject object, List> modifications, + Task task, OperationResult subResult) { + try { + repositoryService.modifyObject(ShadowType.class, object.getOid(), modifications, subResult); + task.recordObjectActionExecuted(object, ChangeType.MODIFY, null); + } catch (Throwable t) { + task.recordObjectActionExecuted(object, ChangeType.MODIFY, t); + } finally { + task.markObjectActionExecutedBoundary(); + } + } + + + private boolean alreadyLinked(F focus, PrismObject shadow) { + return focus.getLinkRef().stream().anyMatch(link -> link.getOid().equals(shadow.getOid())); + } + + private boolean isLogDebug(ResourceObjectShadowChangeDescription change) { + // Reconciliation changes are routine. Do not let them pollute the log files. + return !SchemaConstants.CHANGE_CHANNEL_RECON_URI.equals(change.getSourceChannel()); + } + + private void validate(ResourceObjectShadowChangeDescription change) { + Validate.notNull(change, "Resource object shadow change description must not be null."); + Validate.isTrue(change.getCurrentShadow() != null || change.getObjectDelta() != null, + "Object delta and current shadow are null. At least one must be provided."); + Validate.notNull(change.getResource(), "Resource in change must not be null."); + + if (consistencyChecks) { + if (change.getCurrentShadow() != null) { + change.getCurrentShadow().checkConsistence(); + ShadowUtil.checkConsistence(change.getCurrentShadow(), + "current shadow in change description"); + } + if (change.getObjectDelta() != null) { + change.getObjectDelta().checkConsistence(); + } + } + } + + // @Override + // public void notifyFailure(ResourceOperationFailureDescription + // failureDescription, + // Task task, OperationResult parentResult) { + // Validate.notNull(failureDescription, "Resource object shadow failure + // description must not be null."); + // Validate.notNull(failureDescription.getCurrentShadow(), "Current shadow + // in resource object shadow failure description must not be null."); + // Validate.notNull(failureDescription.getObjectDelta(), "Delta in resource + // object shadow failure description must not be null."); + // Validate.notNull(failureDescription.getResource(), "Resource in failure + // must not be null."); + // Validate.notNull(failureDescription.getResult(), "Result in failure + // description must not be null."); + // Validate.notNull(parentResult, "Parent operation result must not be + // null."); + // + // LOGGER.debug("SYNCHRONIZATION: received failure notifiation {}", + // failureDescription); + // + // LOGGER.error("Provisioning error: {}", + // failureDescription.getResult().getMessage()); + // + // // TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO + // TODO TODO TODO TODO + // } + + /** + * XXX: in situation when one account belongs to two different idm users + * repository returns only first user, see method findShadowOwner. It + * should be changed because otherwise we can't find + * {@link SynchronizationSituationType#DISPUTED} situation + */ + private void setupSituation(SynchronizationContext syncCtx, + ResourceObjectShadowChangeDescription change, OperationResult result) { + + Task task = syncCtx.getTask(); + OperationResult subResult = result.subresult(CLASS_NAME_WITH_DOT + "setupSituation") + .setMinor() + .addArbitraryObjectAsParam("syncCtx", syncCtx) + .addArbitraryObjectAsParam("change", change) + .build(); + LOGGER.trace("Determining situation for resource object shadow."); + + try { + String shadowOid = getOidFromChange(change); + Validate.notEmpty(shadowOid, "Couldn't get resource object shadow oid from change."); + + F currentOwner; + if (syncCtx.getCurrentOwner() != null) { + currentOwner = syncCtx.getCurrentOwner(); + } else { + PrismObject currentOwnerObject = repositoryService.searchShadowOwner(shadowOid, + SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), subResult); + currentOwner = currentOwnerObject != null ? currentOwnerObject.asObjectable() : null; + } + + F correlatedOwner = syncCtx.getCorrelatedOwner(); + if (!isCorrelatedOwnerSameAsCurrentOwner(correlatedOwner, currentOwner)) { + LOGGER.error("Cannot synchronize {}, current owner and expected owner are not the same. Current owner: {}, expected owner: {}", syncCtx.getApplicableShadow(), currentOwner, correlatedOwner); + String msg = "Cannot synchronize " + syncCtx.getApplicableShadow() + + ", current owner and expected owner are not the same. Current owner: " + currentOwner + + ", expected owner: " + correlatedOwner; + result.recordFatalError(msg); + throw new ConfigurationException(msg); + } + + if (currentOwner != null) { + + LOGGER.trace("Shadow OID {} does have owner: {}", shadowOid, currentOwner.getName()); + + syncCtx.setCurrentOwner(currentOwner); + + if (syncCtx.getSituation() != null) { + return; + } + + SynchronizationSituationType state; + ChangeType changeType = getModificationType(change); + switch (changeType) { + case ADD: + case MODIFY: + // if user is found it means account/group is linked to + // resource + state = SynchronizationSituationType.LINKED; + break; + case DELETE: + state = SynchronizationSituationType.DELETED; + break; + default: + throw new AssertionError(changeType); + } + syncCtx.setSituation(state); + } else { + LOGGER.trace("Resource object shadow doesn't have owner."); + determineSituationWithCorrelation(syncCtx, change, task, result); + } + } catch (Exception ex) { + LOGGER.error("Error occurred during resource object shadow owner lookup."); + throw new SystemException( + "Error occurred during resource object shadow owner lookup, reason: " + ex.getMessage(), ex); + } finally { + subResult.computeStatus(); + String syncSituationValue = syncCtx.getSituation() != null ? syncCtx.getSituation().value() : null; + if (isLogDebug(change)) { + LOGGER.debug("SYNCHRONIZATION: SITUATION: '{}', currentOwner={}, correlatedOwner={}", + syncSituationValue, syncCtx.getCurrentOwner(), + syncCtx.getCorrelatedOwner()); + } else { + LOGGER.trace("SYNCHRONIZATION: SITUATION: '{}', currentOwner={}, correlatedOwner={}", + syncSituationValue, syncCtx.getCurrentOwner(), + syncCtx.getCorrelatedOwner()); + } + } + } + + private boolean isCorrelatedOwnerSameAsCurrentOwner(F expectedOwner, F currentOwnerType) { + return expectedOwner == null || currentOwnerType == null || expectedOwner.getOid().equals(currentOwnerType.getOid()); + } + + private String getOidFromChange(ResourceObjectShadowChangeDescription change) { + if (change.getCurrentShadow() != null && StringUtils.isNotEmpty(change.getCurrentShadow().getOid())) { + return change.getCurrentShadow().getOid(); + } + if (change.getOldShadow() != null && StringUtils.isNotEmpty(change.getOldShadow().getOid())) { + return change.getOldShadow().getOid(); + } + + if (change.getObjectDelta() == null || StringUtils.isEmpty(change.getObjectDelta().getOid())) { + throw new IllegalArgumentException( + "Oid was not defined in change (not in current, old shadow, delta)."); + } + + return change.getObjectDelta().getOid(); + } + + /** + * Tries to match specified focus and shadow. Return true if it matches, + * false otherwise. + */ + @Override + public boolean matchUserCorrelationRule(PrismObject shadow, + PrismObject focus, ResourceType resourceType, + PrismObject configuration, Task task, OperationResult result) + throws ConfigurationException, SchemaException, ObjectNotFoundException, + ExpressionEvaluationException, CommunicationException, SecurityViolationException { + + SynchronizationContext synchronizationContext = loadSynchronizationContext(shadow, shadow, null, + resourceType.asPrismObject(), task.getChannel(), configuration, task, result); + return synchronizationExpressionsEvaluator.matchFocusByCorrelationRule(synchronizationContext, focus, result); + } + + /** + * account is not linked to user. you have to use correlation and + * confirmation rule to be sure user for this account doesn't exists + * resourceShadow only contains the data that were in the repository before + * the change. But the correlation/confirmation should work on the updated + * data. Therefore let's apply the changes before running + * correlation/confirmation + */ + private void determineSituationWithCorrelation(SynchronizationContext syncCtx, ResourceObjectShadowChangeDescription change, + Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (ChangeType.DELETE.equals(getModificationType(change))) { + // account was deleted and we know it didn't have owner + if (syncCtx.getSituation() == null) { + syncCtx.setSituation(SynchronizationSituationType.DELETED); + } + return; + } + + F user = syncCtx.getCorrelatedOwner(); + LOGGER.trace("Correlated owner present in synchronization context: {}", user); + if (user != null) { + if (syncCtx.getSituation() != null) { + return; + } + syncCtx.setSituation(getSynchornizationSituationFromChange(change)); + return; + } + + + PrismObject resourceShadow = change.getCurrentShadow(); + + ObjectDelta syncDelta = change.getObjectDelta(); + if (resourceShadow == null && syncDelta != null && ChangeType.ADD.equals(syncDelta.getChangeType())) { + LOGGER.trace("Trying to compute current shadow from change delta add."); + PrismObject shadow = syncDelta.computeChangedObject(syncDelta.getObjectToAdd()); + resourceShadow = shadow; + change.setCurrentShadow(shadow); + } + Validate.notNull(resourceShadow, "Current shadow must not be null."); + + ResourceType resource = change.getResource().asObjectable(); + validateResourceInShadow(resourceShadow.asObjectable(), resource); + + SynchronizationSituationType state; + LOGGER.trace("SYNCHRONIZATION: CORRELATION: Looking for list of {} objects based on correlation rule.", + syncCtx.getFocusClass().getSimpleName()); + List> users = synchronizationExpressionsEvaluator.findFocusesByCorrelationRule(syncCtx.getFocusClass(), + resourceShadow.asObjectable(), syncCtx.getCorrelation(), resource, + syncCtx.getSystemConfiguration().asObjectable(), task, result); + if (users == null) { + users = new ArrayList<>(); + } + + if (users.size() > 1) { + if (syncCtx.getConfirmation() == null) { + LOGGER.trace("SYNCHRONIZATION: CONFIRMATION: no confirmation defined."); + } else { + LOGGER.debug("SYNCHRONIZATION: CONFIRMATION: Checking objects from correlation with confirmation rule."); + users = synchronizationExpressionsEvaluator.findUserByConfirmationRule(syncCtx.getFocusClass(), users, + resourceShadow.asObjectable(), resource, syncCtx.getSystemConfiguration().asObjectable(), + syncCtx.getConfirmation(), task, result); + } + } + + switch (users.size()) { + case 0: + state = SynchronizationSituationType.UNMATCHED; + break; + case 1: + state = getSynchornizationSituationFromChange(change); + + user = users.get(0).asObjectable(); + break; + default: + state = SynchronizationSituationType.DISPUTED; + } + + syncCtx.setCorrelatedOwner(user); + syncCtx.setSituation(state); + } + + private SynchronizationSituationType getSynchornizationSituationFromChange(ResourceObjectShadowChangeDescription change) { + switch (getModificationType(change)) { + case ADD: + case MODIFY: + return SynchronizationSituationType.UNLINKED; + case DELETE: + return SynchronizationSituationType.DELETED; + } + + return null; + } + + private void validateResourceInShadow(ShadowType shadow, ResourceType resource) { + if (shadow.getResourceRef() != null) { + return; + } + + ObjectReferenceType reference = new ObjectReferenceType(); + reference.setOid(resource.getOid()); + reference.setType(ObjectTypes.RESOURCE.getTypeQName()); + + shadow.setResourceRef(reference); + } + + /** + * @return method checks change type in object delta if available, otherwise + * returns {@link ChangeType#ADD} + */ + private ChangeType getModificationType(ResourceObjectShadowChangeDescription change) { + if (change.getObjectDelta() != null) { + return change.getObjectDelta().getChangeType(); + } else { + return ChangeType.ADD; + } + } + + private void reactToChange(SynchronizationContext syncCtx, + ResourceObjectShadowChangeDescription change, boolean logDebug, SynchronizationEventInformation eventInfo, + OperationResult parentResult) + throws ConfigurationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ExpressionEvaluationException, CommunicationException { + + SynchronizationReactionType reaction = syncCtx.getReaction(parentResult); + if (reaction == null) { + LOGGER.trace("No reaction is defined for situation {} in {}", syncCtx.getSituation(), syncCtx.getResource()); + return; + } + + 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. + if (change.getObjectDelta() == null) { + doReconciliation = true; + } + } + + ModelExecuteOptions options = new ModelExecuteOptions(); + options.setReconcile(doReconciliation); + options.setLimitPropagation(syncCtx.isLimitPropagation()); + + final boolean willSynchronize = isSynchronize(reaction); + LensContext lensContext; + + Task task = syncCtx.getTask(); + if (willSynchronize) { + lensContext = createLensContext(syncCtx, change, options, parentResult); + lensContext.setDoReconciliationForAllProjections(BooleanUtils.isTrue(reaction.isReconcileAll())); + LOGGER.trace("---[ SYNCHRONIZATION context before action execution ]-------------------------\n" + + "{}\n------------------------------------------", lensContext.debugDumpLazily()); + } else { + lensContext = null; + } + + if (willSynchronize) { + + // there's no point in calling executeAction without context - so + // the actions are executed only if synchronize == true + executeActions(syncCtx, lensContext, BeforeAfterType.BEFORE, logDebug, task, parentResult); + + Iterator iterator = lensContext.getProjectionContextsIterator(); + LensProjectionContext originalProjectionContext = iterator.hasNext() ? iterator.next() : null; + + if (originalProjectionContext != null) { + originalProjectionContext.setSynchronizationSource(true); + } + + try { + + clockworkMedic.enterModelMethod(false); + try { + if (change.isSimulate()) { + clockwork.previewChanges(lensContext, null, task, parentResult); + } else { + clockwork.run(lensContext, task, parentResult); + } + } finally { + clockworkMedic.exitModelMethod(false); + } + + } catch (ConfigurationException | ObjectNotFoundException | SchemaException | + PolicyViolationException | ExpressionEvaluationException | ObjectAlreadyExistsException | + CommunicationException | SecurityViolationException | PreconditionViolationException | RuntimeException e) { + LOGGER.error("SYNCHRONIZATION: Error in synchronization on {} for situation {}: {}: {}. Change was {}", + syncCtx.getResource(), syncCtx.getSituation(), e.getClass().getSimpleName(), e.getMessage(), change, e); +// parentResult.recordFatalError("Error during sync", e); + // what to do here? We cannot throw the error back. All that the notifyChange method + // could do is to convert it to SystemException. But that indicates an internal error and it will + // break whatever code called the notifyChange in the first place. We do not want that. + // If the clockwork could not do anything with the exception then perhaps nothing can be done at all. + // So just log the error (the error should be remembered in the result and task already) + // and then just go on. + } + + // note: actions "AFTER" seem to be useless here (basically they + // modify lens context - which is relevant only if followed by + // clockwork run) + executeActions(syncCtx, lensContext, BeforeAfterType.AFTER, logDebug, task, parentResult); + + if (originalProjectionContext != null) { + SynchronizationSituationType resolvedSituation = originalProjectionContext.getSynchronizationSituationResolved(); + if (eventInfo.getNewSituation() != resolvedSituation && resolvedSituation != null) { + // Resolved situation of null means it is unknown. So we stick with situation as originally determined. + LOGGER.trace("We have changed new situation: {} -> {}", eventInfo.getNewSituation(), resolvedSituation); + eventInfo.setNewSituation(resolvedSituation); + } + } + + } else { + LOGGER.trace("Skipping clockwork run on {} for situation {}, synchronize is set to false.", + syncCtx.getResource(), syncCtx.getSituation()); + } + } + + @NotNull + private LensContext createLensContext(SynchronizationContext syncCtx, + ResourceObjectShadowChangeDescription change, ModelExecuteOptions options, + OperationResult parentResult) throws ObjectNotFoundException, SchemaException { + + LensContext context = contextFactory.createSyncContext(syncCtx.getFocusClass(), change); + context.setLazyAuditRequest(true); + context.setSystemConfiguration(syncCtx.getSystemConfiguration()); + context.setOptions(options); + + ResourceType resource = change.getResource().asObjectable(); + if (ModelExecuteOptions.isLimitPropagation(options)) { + context.setTriggeredResource(resource); + } + + context.rememberResource(resource); + PrismObject shadow = getShadowFromChange(change); + 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 tombstone = isThombstone(change); + ResourceShadowDiscriminator discriminator = new ResourceShadowDiscriminator(resource.getOid(), kind, intent, shadow.asObjectable().getTag(), 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(); + if (delta != null) { + projectionContext.setSyncDelta(delta); + } else { + projectionContext.setSyncAbsoluteTrigger(true); + } + + // we insert account if available in change + projectionContext.setLoadedObject(shadow); + + if (!tombstone && !containsIncompleteItems(shadow)) { + projectionContext.setFullShadow(true); + } + projectionContext.setFresh(true); + + if (delta != null && delta.isDelete()) { + projectionContext.setExists(false); + } else { + projectionContext.setExists(true); + } + + projectionContext.setDoReconciliation(ModelExecuteOptions.isReconcile(options)); + + // Focus context + 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) + .asObjectable(); + context.setFocusTemplate(objectTemplate); + context.setFocusTemplateExternallySet(true); // we do not want to override this template e.g. when subtype changes + } + + return context; + } + + private boolean containsIncompleteItems(PrismObject shadow) { + ShadowAttributesType attributes = shadow.asObjectable().getAttributes(); + //noinspection SimplifiableIfStatement + if (attributes == null) { + return false; // strictly speaking this is right; but we perhaps should not consider this shadow as fully loaded :) + } else { + return ((PrismContainerValue) (attributes.asPrismContainerValue())).getItems().stream() + .anyMatch(Item::isIncomplete); + } + } + + private PrismObject getShadowFromChange(ResourceObjectShadowChangeDescription change) { + if (change.getCurrentShadow() != null) { + return change.getCurrentShadow(); + } + if (change.getOldShadow() != null) { + return change.getOldShadow(); + } + return null; + } + + private ShadowKindType getKind(PrismObject shadow, ShadowKindType objectSynchronizationKind) { + ShadowKindType shadowKind = shadow.asObjectable().getKind(); + if (shadowKind != null) { + return shadowKind; + } + return objectSynchronizationKind; + } + + private String getIntent(PrismObject shadow, + String objectSynchronizationIntent) { + String shadowIntent = shadow.asObjectable().getIntent(); + if (shadowIntent != null) { + return shadowIntent; + } + return objectSynchronizationIntent; + } + + private boolean isThombstone(ResourceObjectShadowChangeDescription change) { + PrismObject shadow = null; + if (change.getOldShadow() != null) { + shadow = change.getOldShadow(); + } else if (change.getCurrentShadow() != null) { + shadow = change.getCurrentShadow(); + } + if (shadow != null) { + if (shadow.asObjectable().isDead() != null) { + return shadow.asObjectable().isDead(); + } + } + ObjectDelta objectDelta = change.getObjectDelta(); + return objectDelta != null && objectDelta.isDelete(); + } + + private boolean isSynchronize(SynchronizationReactionType reactionDefinition) { + if (reactionDefinition.isSynchronize() != null) { + return reactionDefinition.isSynchronize(); + } + return !reactionDefinition.getAction().isEmpty(); + } + + /** + * Saves situation, timestamps, kind and intent (if needed) + */ + private void saveSyncMetadata(SynchronizationContext syncCtx, + ResourceObjectShadowChangeDescription change, boolean full, XMLGregorianCalendar now, OperationResult result) { + PrismObject shadow = syncCtx.getCurrentShadow(); + if (shadow == null) { + return; + } + + Task task = syncCtx.getTask(); + + try { + ShadowType shadowType = shadow.asObjectable(); + // new situation description + List> deltas = SynchronizationUtils + .createSynchronizationSituationAndDescriptionDelta(shadow, syncCtx.getSituation(), + change.getSourceChannel(), full, now, prismContext); + + if (shadowType.getKind() == null || ShadowKindType.UNKNOWN == shadowType.getKind()) { + PropertyDelta kindDelta = prismContext.deltaFactory().property().createReplaceDelta(shadow.getDefinition(), + ShadowType.F_KIND, syncCtx.getKind()); + deltas.add(kindDelta); + } + + if (shouldSaveIntent(syncCtx)) { + PropertyDelta intentDelta = prismContext.deltaFactory().property().createReplaceDelta(shadow.getDefinition(), + ShadowType.F_INTENT, syncCtx.getIntent()); + deltas.add(intentDelta); + } + + if (shadowType.getTag() == null && syncCtx.getTag() != null) { + PropertyDelta tagDelta = prismContext.deltaFactory().property().createReplaceDelta(shadow.getDefinition(), + ShadowType.F_TAG, syncCtx.getTag()); + deltas.add(tagDelta); + } + + repositoryService.modifyObject(shadowType.getClass(), shadow.getOid(), deltas, result); + ItemDeltaCollectionsUtil.applyTo(deltas, shadow); + task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, null); + } catch (ObjectNotFoundException ex) { + task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, ex); + // This may happen e.g. during some recon-livesync interactions. + // If the shadow is gone then it is gone. No point in recording the + // situation any more. + LOGGER.debug( + "Could not update situation in account, because shadow {} does not exist any more (this may be harmless)", + shadow.getOid()); + syncCtx.setShadowExistsInRepo(false); + result.getLastSubresult().setStatus(OperationResultStatus.HANDLED_ERROR); + } catch (ObjectAlreadyExistsException | SchemaException ex) { + task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, ex); + LoggingUtils.logException(LOGGER, + "### SYNCHRONIZATION # notifyChange(..): Save of synchronization situation failed: could not modify shadow " + + shadow.getOid() + ": " + ex.getMessage(), + ex); + result.recordFatalError("Save of synchronization situation failed: could not modify shadow " + + shadow.getOid() + ": " + ex.getMessage(), ex); + throw new SystemException("Save of synchronization situation failed: could not modify shadow " + + shadow.getOid() + ": " + ex.getMessage(), ex); + } catch (Throwable t) { + task.recordObjectActionExecuted(shadow, ChangeType.MODIFY, t); + throw t; + } + } + + private boolean shouldSaveIntent(SynchronizationContext syncCtx) throws SchemaException { + ShadowType shadow = syncCtx.getCurrentShadow().asObjectable(); + if (shadow.getIntent() == null) { + return true; + } + + if (SchemaConstants.INTENT_UNKNOWN.equals(shadow.getIntent())) { + return true; + } + + if (syncCtx.isForceIntentChange()) { + String objectSyncIntent = syncCtx.getIntent(); + //noinspection RedundantIfStatement + if (!MiscSchemaUtil.equalsIntent(shadow.getIntent(), objectSyncIntent)) { + return true; + } + } + + return false; + } + + private void executeActions(SynchronizationContext syncCtx, LensContext context, + BeforeAfterType order, boolean logDebug, Task task, OperationResult parentResult) + throws ConfigurationException, SchemaException, ObjectNotFoundException, CommunicationException, + SecurityViolationException, ExpressionEvaluationException { + + SynchronizationReactionType reaction = syncCtx.getReaction(parentResult); + for (SynchronizationActionType actionDef : reaction.getAction()) { + if ((actionDef.getOrder() == null && order == BeforeAfterType.BEFORE) + || (actionDef.getOrder() != null && actionDef.getOrder() == order)) { + + String handlerUri = actionDef.getHandlerUri(); + if (handlerUri == null) { + LOGGER.error("Action definition in resource {} doesn't contain handler URI", syncCtx.getResource()); + throw new ConfigurationException( + "Action definition in resource " + syncCtx.getResource() + " doesn't contain handler URI"); + } + + Action action = actionManager.getActionInstance(handlerUri); + if (action == null) { + LOGGER.warn("Couldn't create action with uri '{}' in resource {}, skipping action.", handlerUri, + syncCtx.getResource()); + continue; + } + + if (logDebug) { + LOGGER.debug("SYNCHRONIZATION: ACTION: Executing: {}.", action.getClass()); + } else { + LOGGER.trace("SYNCHRONIZATION: ACTION: Executing: {}.", action.getClass()); + } + SynchronizationSituation situation = new SynchronizationSituation<>(syncCtx.getCurrentOwner(), syncCtx.getCorrelatedOwner(), syncCtx.getSituation()); + action.handle(context, situation, null, task, parentResult); + } + } + } + + @Override + public String getName() { + return "model synchronization service"; + } + + private static class SynchronizationEventInformation { + + private String objectName; + private String objectDisplayName; + private String objectOid; + private Throwable exception; + private long started; + + private boolean alreadySaved; + + private enum SpecialSituation { + NO_SYNCHRONIZATION_POLICY, SYNCHRONIZATION_NOT_ENABLED, PROTECTED + } + + private SynchronizationSituationType originalSituation; + private SynchronizationSituationType newSituation; + private SpecialSituation specialSituation; + + private SynchronizationEventInformation(PrismObject currentShadow) { + started = System.currentTimeMillis(); + if (currentShadow != null) { + final ShadowType shadow = currentShadow.asObjectable(); + objectName = PolyString.getOrig(shadow.getName()); + objectDisplayName = StatisticsUtil.getDisplayName(shadow); + objectOid = currentShadow.getOid(); + } + } + + private void setSituation(SynchronizationInformation.Record increment, + SynchronizationSituationType situation) { + if (situation != null) { + switch (situation) { + case LINKED: + increment.setCountLinked(1); + break; + case UNLINKED: + increment.setCountUnlinked(1); + break; + case DELETED: + increment.setCountDeleted(1); + break; + case DISPUTED: + increment.setCountDisputed(1); + break; + case UNMATCHED: + increment.setCountUnmatched(1); + break; + default: + throw new AssertionError(situation); + } + } + } + + private void setSpecialSituation(SpecialSituation situation) { + specialSituation = situation; + } + + private void setOriginalSituation(SynchronizationSituationType situation) { + originalSituation = situation; + } + + private SynchronizationSituationType getNewSituation() { + return newSituation; + } + + private void setNewSituation(SynchronizationSituationType situation) { + newSituation = situation; + } + + public void setException(Exception ex) { + exception = ex; + } + + private void record(Task task) { + SynchronizationInformation.Record originalStateIncrement = new SynchronizationInformation.Record(); + SynchronizationInformation.Record newStateIncrement = new SynchronizationInformation.Record(); + if (specialSituation != null) { + switch (specialSituation) { + case NO_SYNCHRONIZATION_POLICY: + originalStateIncrement.setCountNoSynchronizationPolicy(1); + newStateIncrement.setCountNoSynchronizationPolicy(1); + break; + case SYNCHRONIZATION_NOT_ENABLED: + originalStateIncrement.setCountSynchronizationDisabled(1); + newStateIncrement.setCountSynchronizationDisabled(1); + break; + case PROTECTED: + originalStateIncrement.setCountProtected(1); + newStateIncrement.setCountProtected(1); + break; + default: + throw new AssertionError(specialSituation); + } + } else { + setSituation(originalStateIncrement, originalSituation); + setSituation(newStateIncrement, newSituation); + } + saveToTask(task, originalStateIncrement, newStateIncrement); + } + + private void saveToTask(Task task, SynchronizationInformation.Record originalStateIncrement, + SynchronizationInformation.Record newStateIncrement) { + task.recordSynchronizationOperationEnd(objectName, objectDisplayName, ShadowType.COMPLEX_TYPE, + objectOid, started, exception, originalStateIncrement, newStateIncrement); + if (alreadySaved) { + throw new IllegalStateException("SynchronizationEventInformation already saved"); + } else { + alreadySaved = true; + } + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceUtils.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceUtils.java index bcdbbef2ce7..467672755f9 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceUtils.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceUtils.java @@ -1,109 +1,109 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.sync; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.schema.result.OperationResult; -import org.apache.commons.lang.Validate; - -import com.evolveum.midpoint.common.SynchronizationUtils; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.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.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSynchronizationDiscriminatorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSynchronizationType; -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; - -public class SynchronizationServiceUtils { - - private static final Trace LOGGER = TraceManager.getTrace(SynchronizationServiceUtils.class); - - public static boolean isPolicyApplicable(ObjectSynchronizationType synchronizationPolicy, - ObjectSynchronizationDiscriminatorType discriminator, ExpressionFactory expressionFactory, - SynchronizationContext syncCtx, OperationResult result) throws SchemaException, ExpressionEvaluationException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - - boolean isApplicablePolicy; - if (discriminator != null) { - isApplicablePolicy = isPolicyApplicable(discriminator, synchronizationPolicy, syncCtx.getResource()); - } else { - isApplicablePolicy = isPolicyApplicable(synchronizationPolicy, syncCtx); - } - - if (isApplicablePolicy) { - Boolean conditionResult = evaluateSynchronizationPolicyCondition(synchronizationPolicy, syncCtx, expressionFactory, result); - return conditionResult != null ? conditionResult : true; - } else { - return false; - } - } - - private static boolean isPolicyApplicable(ObjectSynchronizationType synchronizationPolicy, SynchronizationContext syncCtx) - throws SchemaException { - ShadowType currentShadowType = syncCtx.getApplicableShadow().asObjectable(); - - // objectClass - QName shadowObjectClass = currentShadowType.getObjectClass(); - Validate.notNull(shadowObjectClass, "No objectClass in currentShadow"); - - return SynchronizationUtils.isPolicyApplicable(shadowObjectClass, currentShadowType.getKind(), currentShadowType.getIntent(), synchronizationPolicy, syncCtx.getResource()); - - } - - private static boolean isPolicyApplicable(ObjectSynchronizationDiscriminatorType synchronizationDiscriminator, - ObjectSynchronizationType synchronizationPolicy, PrismObject resource) - throws SchemaException { - ShadowKindType kind = synchronizationDiscriminator.getKind(); - String intent = synchronizationDiscriminator.getIntent(); - if (kind == null && intent == null) { - throw new SchemaException( - "Illegal state, object synchronization discriminator type must have kind/intent specified. Current values are: kind=" - + kind + ", intent=" + intent); - } - return SynchronizationUtils.isPolicyApplicable(null, kind, intent, synchronizationPolicy, resource); - } - - private static Boolean evaluateSynchronizationPolicyCondition( - ObjectSynchronizationType synchronizationPolicy, SynchronizationContext syncCtx, - ExpressionFactory expressionFactory, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, - ConfigurationException, SecurityViolationException { - if (synchronizationPolicy.getCondition() == null) { - return null; - } - ExpressionType conditionExpressionType = synchronizationPolicy.getCondition(); - String desc = "condition in object synchronization " + synchronizationPolicy.getName(); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, syncCtx.getApplicableShadow(), null, - syncCtx.getResource(), syncCtx.getSystemConfiguration(), null, syncCtx.getPrismContext()); - try { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(syncCtx.getTask(), result)); - PrismPropertyValue evaluateCondition = ExpressionUtil.evaluateCondition(variables, - conditionExpressionType, syncCtx.getExpressionProfile(), expressionFactory, desc, syncCtx.getTask(), result); - return evaluateCondition.getValue(); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.sync; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.schema.result.OperationResult; +import org.apache.commons.lang.Validate; + +import com.evolveum.midpoint.common.SynchronizationUtils; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.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.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSynchronizationDiscriminatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectSynchronizationType; +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; + +public class SynchronizationServiceUtils { + + private static final Trace LOGGER = TraceManager.getTrace(SynchronizationServiceUtils.class); + + public static boolean isPolicyApplicable(ObjectSynchronizationType synchronizationPolicy, + ObjectSynchronizationDiscriminatorType discriminator, ExpressionFactory expressionFactory, + SynchronizationContext syncCtx, OperationResult result) throws SchemaException, ExpressionEvaluationException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + boolean isApplicablePolicy; + if (discriminator != null) { + isApplicablePolicy = isPolicyApplicable(discriminator, synchronizationPolicy, syncCtx.getResource()); + } else { + isApplicablePolicy = isPolicyApplicable(synchronizationPolicy, syncCtx); + } + + if (isApplicablePolicy) { + Boolean conditionResult = evaluateSynchronizationPolicyCondition(synchronizationPolicy, syncCtx, expressionFactory, result); + return conditionResult != null ? conditionResult : true; + } else { + return false; + } + } + + private static boolean isPolicyApplicable(ObjectSynchronizationType synchronizationPolicy, SynchronizationContext syncCtx) + throws SchemaException { + ShadowType currentShadowType = syncCtx.getApplicableShadow().asObjectable(); + + // objectClass + QName shadowObjectClass = currentShadowType.getObjectClass(); + Validate.notNull(shadowObjectClass, "No objectClass in currentShadow"); + + return SynchronizationUtils.isPolicyApplicable(shadowObjectClass, currentShadowType.getKind(), currentShadowType.getIntent(), synchronizationPolicy, syncCtx.getResource()); + + } + + private static boolean isPolicyApplicable(ObjectSynchronizationDiscriminatorType synchronizationDiscriminator, + ObjectSynchronizationType synchronizationPolicy, PrismObject resource) + throws SchemaException { + ShadowKindType kind = synchronizationDiscriminator.getKind(); + String intent = synchronizationDiscriminator.getIntent(); + if (kind == null && intent == null) { + throw new SchemaException( + "Illegal state, object synchronization discriminator type must have kind/intent specified. Current values are: kind=" + + kind + ", intent=" + intent); + } + return SynchronizationUtils.isPolicyApplicable(null, kind, intent, synchronizationPolicy, resource); + } + + private static Boolean evaluateSynchronizationPolicyCondition( + ObjectSynchronizationType synchronizationPolicy, SynchronizationContext syncCtx, + ExpressionFactory expressionFactory, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, + ConfigurationException, SecurityViolationException { + if (synchronizationPolicy.getCondition() == null) { + return null; + } + ExpressionType conditionExpressionType = synchronizationPolicy.getCondition(); + String desc = "condition in object synchronization " + synchronizationPolicy.getName(); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, syncCtx.getApplicableShadow(), null, + syncCtx.getResource(), syncCtx.getSystemConfiguration(), null, syncCtx.getPrismContext()); + try { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(syncCtx.getTask(), result)); + PrismPropertyValue evaluateCondition = ExpressionUtil.evaluateCondition(variables, + conditionExpressionType, syncCtx.getExpressionProfile(), expressionFactory, desc, syncCtx.getTask(), result); + return evaluateCondition.getValue(); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/DeleteShadowAction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/DeleteShadowAction.java index 7be9f81c96d..e42dbceb47f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/DeleteShadowAction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/DeleteShadowAction.java @@ -1,38 +1,38 @@ -/* - * Copyright (c) 2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.sync.action; - -import java.util.Map; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.SynchronizationIntent; -import com.evolveum.midpoint.model.impl.sync.Action; -import com.evolveum.midpoint.model.impl.sync.SynchronizationSituation; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; - -/** - * @author semancik - * - */ -public class DeleteShadowAction implements Action { - - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.sync.Action#handle(com.evolveum.midpoint.model.lens.LensContext, com.evolveum.midpoint.model.sync.SynchronizationSituation, java.util.Map, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) - */ - @Override - public void handle(LensContext context, SynchronizationSituation situation, - Map parameters, Task task, OperationResult parentResult) { - LensProjectionContext projectionContext = context.getProjectionContextsIterator().next(); - projectionContext.setSynchronizationIntent(SynchronizationIntent.DELETE); - } - -} +/* + * Copyright (c) 2013 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.sync.action; + +import java.util.Map; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.sync.Action; +import com.evolveum.midpoint.model.impl.sync.SynchronizationSituation; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; + +/** + * @author semancik + * + */ +public class DeleteShadowAction implements Action { + + /* (non-Javadoc) + * @see com.evolveum.midpoint.model.sync.Action#handle(com.evolveum.midpoint.model.lens.LensContext, com.evolveum.midpoint.model.sync.SynchronizationSituation, java.util.Map, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) + */ + @Override + public void handle(LensContext context, SynchronizationSituation situation, + Map parameters, Task task, OperationResult parentResult) { + LensProjectionContext projectionContext = context.getProjectionContextsIterator().next(); + projectionContext.setSynchronizationIntent(SynchronizationIntent.DELETE); + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/UnlinkAction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/UnlinkAction.java index 39f9005608d..44c98491dc8 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/UnlinkAction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/action/UnlinkAction.java @@ -1,37 +1,37 @@ -/* - * Copyright (c) 2013 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.sync.action; - -import java.util.Map; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.SynchronizationIntent; -import com.evolveum.midpoint.model.impl.sync.Action; -import com.evolveum.midpoint.model.impl.sync.SynchronizationSituation; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; - -/** - * @author semancik - */ -public class UnlinkAction implements Action { - - /* (non-Javadoc) - * @see com.evolveum.midpoint.model.sync.Action#handle(com.evolveum.midpoint.model.lens.LensContext, com.evolveum.midpoint.model.sync.SynchronizationSituation, java.util.Map, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) - */ - @Override - public void handle(LensContext context, SynchronizationSituation situation, - Map parameters, Task task, OperationResult parentResult) { - LensProjectionContext projectionContext = context.getProjectionContextsIterator().next(); - projectionContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); - } - -} +/* + * Copyright (c) 2013 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.sync.action; + +import java.util.Map; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.sync.Action; +import com.evolveum.midpoint.model.impl.sync.SynchronizationSituation; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; + +/** + * @author semancik + */ +public class UnlinkAction implements Action { + + /* (non-Javadoc) + * @see com.evolveum.midpoint.model.sync.Action#handle(com.evolveum.midpoint.model.lens.LensContext, com.evolveum.midpoint.model.sync.SynchronizationSituation, java.util.Map, com.evolveum.midpoint.task.api.Task, com.evolveum.midpoint.schema.result.OperationResult) + */ + @Override + public void handle(LensContext context, SynchronizationSituation situation, + Map parameters, Task task, OperationResult parentResult) { + LensProjectionContext projectionContext = context.getProjectionContextsIterator().next(); + projectionContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeModelTaskHandler.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeModelTaskHandler.java index e839fef18ef..776f8a04a4a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeModelTaskHandler.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AbstractSearchIterativeModelTaskHandler.java @@ -1,159 +1,159 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.util; - -import java.util.Collection; - -import com.evolveum.midpoint.model.api.ModelAuthorizationAction; -import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; -import org.springframework.beans.factory.annotation.Autowired; - -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismProperty; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.repo.common.task.AbstractSearchIterativeResultHandler; -import com.evolveum.midpoint.repo.common.task.AbstractSearchIterativeTaskHandler; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.ResultHandler; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; -import com.evolveum.midpoint.task.api.Task; -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; -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.evolveum.midpoint.xml.ns._public.common.common_3.ModelExecuteOptionsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; - -/** - * @author semancik - * - */ -public abstract class AbstractSearchIterativeModelTaskHandler> extends AbstractSearchIterativeTaskHandler { - - // WARNING! This task handler is efficiently singleton! - // It is a spring bean and it is supposed to handle all search task instances - // Therefore it must not have task-specific fields. It can only contain fields specific to - // all tasks of a specified type - // If you need to store fields specific to task instance or task run the ResultHandler is a good place to do that. - - @Autowired protected ModelObjectResolver modelObjectResolver; - @Autowired protected SecurityEnforcer securityEnforcer; - @Autowired protected ExpressionFactory expressionFactory; - @Autowired protected SystemObjectCache systemObjectCache; - - private static final Trace LOGGER = TraceManager.getTrace(AbstractSearchIterativeModelTaskHandler.class); - - protected AbstractSearchIterativeModelTaskHandler(String taskName, String taskOperationPrefix) { - super(taskName, taskOperationPrefix); - } - - @Override - protected ObjectQuery preProcessQuery(ObjectQuery query, Task coordinatorTask, OperationResult opResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - // TODO consider which variables should go here (there's no focus, shadow, resource - only configuration) - if (ExpressionUtil.hasExpressions(query.getFilter())) { - PrismObject configuration = systemObjectCache.getSystemConfiguration(opResult); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, null, null, - configuration != null ? configuration.asObjectable() : null, prismContext); - try { - ExpressionEnvironment env = new ExpressionEnvironment<>(coordinatorTask, opResult); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); - query = ExpressionUtil.evaluateQueryExpressions(query, variables, getExpressionProfile(), expressionFactory, - prismContext, "evaluate query expressions", coordinatorTask, opResult); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - return query; - } - - @Override - protected Integer countObjects(Class type, ObjectQuery query, Collection> queryOptions, Task coordinatorTask, OperationResult opResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - return modelObjectResolver.countObjects(type, query, queryOptions, coordinatorTask, opResult); - } - - @Override - protected void searchIterative(Class type, ObjectQuery query, Collection> searchOptions, ResultHandler resultHandler, Task coordinatorTask, OperationResult opResult) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - modelObjectResolver.searchIterative(type, query, searchOptions, resultHandler, coordinatorTask, opResult); - } - - protected T resolveObjectRef(Class type, TaskRunResult runResult, Task task, OperationResult opResult) { - String typeName = type.getSimpleName(); - String objectOid = task.getObjectOid(); - if (objectOid == null) { - LOGGER.error("Import: No {} OID specified in the task", typeName); - opResult.recordFatalError("No "+typeName+" OID specified in the task"); - runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); - return null; - } - - try { - return modelObjectResolver.getObject(type, objectOid, null, task, opResult); - } catch (ObjectNotFoundException ex) { - LOGGER.error("Handler: {} {} not found: {}", typeName, objectOid, ex.getMessage(), ex); - // This is bad. The resource does not exist. Permanent problem. - opResult.recordFatalError(typeName+" not found " + objectOid, ex); - runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); - return null; - } catch (SchemaException ex) { - LOGGER.error("Handler: Error dealing with schema: {}", ex.getMessage(), ex); - // Not sure about this. But most likely it is a misconfigured resource or connector - // It may be worth to retry. Error is fatal, but may not be permanent. - opResult.recordFatalError("Error dealing with schema: " + ex.getMessage(), ex); - runResult.setRunResultStatus(TaskRunResultStatus.TEMPORARY_ERROR); - return null; - } catch (RuntimeException ex) { - LOGGER.error("Handler: Internal Error: {}", ex.getMessage(), ex); - // Can be anything ... but we can't recover from that. - // It is most likely a programming error. Does not make much sense to retry. - opResult.recordFatalError("Internal Error: " + ex.getMessage(), ex); - runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); - return null; - } catch (CommunicationException ex) { - LOGGER.error("Handler: Error getting {} {}: {}", typeName, objectOid, ex.getMessage(), ex); - opResult.recordFatalError("Error getting "+typeName+" " + objectOid+": "+ex.getMessage(), ex); - runResult.setRunResultStatus(TaskRunResultStatus.TEMPORARY_ERROR); - return null; - } catch (ConfigurationException | ExpressionEvaluationException | SecurityViolationException ex) { - LOGGER.error("Handler: Error getting {} {}: {}", typeName, objectOid, ex.getMessage(), ex); - opResult.recordFatalError("Error getting "+typeName+" " + objectOid+": "+ex.getMessage(), ex); - runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); - return null; - } - } - - protected ModelExecuteOptions getExecuteOptionsFromTask(Task task) { - PrismProperty property = task.getExtensionPropertyOrClone(SchemaConstants.MODEL_EXTENSION_EXECUTE_OPTIONS); - return property != null ? ModelExecuteOptions.fromModelExecutionOptionsType(property.getRealValue()) : null; - } - - @Override - protected void checkRawAuthorization(Task task, OperationResult result) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException { - securityEnforcer.authorize(ModelAuthorizationAction.RAW_OPERATION.getUrl(), null, AuthorizationParameters.EMPTY, null, task, result); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.util; + +import java.util.Collection; + +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import org.springframework.beans.factory.annotation.Autowired; + +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.ModelObjectResolver; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.repo.common.task.AbstractSearchIterativeResultHandler; +import com.evolveum.midpoint.repo.common.task.AbstractSearchIterativeTaskHandler; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ResultHandler; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +import com.evolveum.midpoint.task.api.Task; +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; +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.evolveum.midpoint.xml.ns._public.common.common_3.ModelExecuteOptionsType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; + +/** + * @author semancik + * + */ +public abstract class AbstractSearchIterativeModelTaskHandler> extends AbstractSearchIterativeTaskHandler { + + // WARNING! This task handler is efficiently singleton! + // It is a spring bean and it is supposed to handle all search task instances + // Therefore it must not have task-specific fields. It can only contain fields specific to + // all tasks of a specified type + // If you need to store fields specific to task instance or task run the ResultHandler is a good place to do that. + + @Autowired protected ModelObjectResolver modelObjectResolver; + @Autowired protected SecurityEnforcer securityEnforcer; + @Autowired protected ExpressionFactory expressionFactory; + @Autowired protected SystemObjectCache systemObjectCache; + + private static final Trace LOGGER = TraceManager.getTrace(AbstractSearchIterativeModelTaskHandler.class); + + protected AbstractSearchIterativeModelTaskHandler(String taskName, String taskOperationPrefix) { + super(taskName, taskOperationPrefix); + } + + @Override + protected ObjectQuery preProcessQuery(ObjectQuery query, Task coordinatorTask, OperationResult opResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + // TODO consider which variables should go here (there's no focus, shadow, resource - only configuration) + if (ExpressionUtil.hasExpressions(query.getFilter())) { + PrismObject configuration = systemObjectCache.getSystemConfiguration(opResult); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, null, null, + configuration != null ? configuration.asObjectable() : null, prismContext); + try { + ExpressionEnvironment env = new ExpressionEnvironment<>(coordinatorTask, opResult); + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); + query = ExpressionUtil.evaluateQueryExpressions(query, variables, getExpressionProfile(), expressionFactory, + prismContext, "evaluate query expressions", coordinatorTask, opResult); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + return query; + } + + @Override + protected Integer countObjects(Class type, ObjectQuery query, Collection> queryOptions, Task coordinatorTask, OperationResult opResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + return modelObjectResolver.countObjects(type, query, queryOptions, coordinatorTask, opResult); + } + + @Override + protected void searchIterative(Class type, ObjectQuery query, Collection> searchOptions, ResultHandler resultHandler, Task coordinatorTask, OperationResult opResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + modelObjectResolver.searchIterative(type, query, searchOptions, resultHandler, coordinatorTask, opResult); + } + + protected T resolveObjectRef(Class type, TaskRunResult runResult, Task task, OperationResult opResult) { + String typeName = type.getSimpleName(); + String objectOid = task.getObjectOid(); + if (objectOid == null) { + LOGGER.error("Import: No {} OID specified in the task", typeName); + opResult.recordFatalError("No "+typeName+" OID specified in the task"); + runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); + return null; + } + + try { + return modelObjectResolver.getObject(type, objectOid, null, task, opResult); + } catch (ObjectNotFoundException ex) { + LOGGER.error("Handler: {} {} not found: {}", typeName, objectOid, ex.getMessage(), ex); + // This is bad. The resource does not exist. Permanent problem. + opResult.recordFatalError(typeName+" not found " + objectOid, ex); + runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); + return null; + } catch (SchemaException ex) { + LOGGER.error("Handler: Error dealing with schema: {}", ex.getMessage(), ex); + // Not sure about this. But most likely it is a misconfigured resource or connector + // It may be worth to retry. Error is fatal, but may not be permanent. + opResult.recordFatalError("Error dealing with schema: " + ex.getMessage(), ex); + runResult.setRunResultStatus(TaskRunResultStatus.TEMPORARY_ERROR); + return null; + } catch (RuntimeException ex) { + LOGGER.error("Handler: Internal Error: {}", ex.getMessage(), ex); + // Can be anything ... but we can't recover from that. + // It is most likely a programming error. Does not make much sense to retry. + opResult.recordFatalError("Internal Error: " + ex.getMessage(), ex); + runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); + return null; + } catch (CommunicationException ex) { + LOGGER.error("Handler: Error getting {} {}: {}", typeName, objectOid, ex.getMessage(), ex); + opResult.recordFatalError("Error getting "+typeName+" " + objectOid+": "+ex.getMessage(), ex); + runResult.setRunResultStatus(TaskRunResultStatus.TEMPORARY_ERROR); + return null; + } catch (ConfigurationException | ExpressionEvaluationException | SecurityViolationException ex) { + LOGGER.error("Handler: Error getting {} {}: {}", typeName, objectOid, ex.getMessage(), ex); + opResult.recordFatalError("Error getting "+typeName+" " + objectOid+": "+ex.getMessage(), ex); + runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); + return null; + } + } + + protected ModelExecuteOptions getExecuteOptionsFromTask(Task task) { + PrismProperty property = task.getExtensionPropertyOrClone(SchemaConstants.MODEL_EXTENSION_EXECUTE_OPTIONS); + return property != null ? ModelExecuteOptions.fromModelExecutionOptionsType(property.getRealValue()) : null; + } + + @Override + protected void checkRawAuthorization(Task task, OperationResult result) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + securityEnforcer.authorize(ModelAuthorizationAction.RAW_OPERATION.getUrl(), null, AuthorizationParameters.EMPTY, null, task, result); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/ModelImplUtils.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/ModelImplUtils.java index d365520609a..2fb668e4563 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/ModelImplUtils.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/ModelImplUtils.java @@ -1,865 +1,811 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.util; - -import com.evolveum.midpoint.common.crypto.CryptoUtil; -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; -import com.evolveum.midpoint.model.api.ModelAuthorizationAction; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.context.AssignmentPath; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; -import com.evolveum.midpoint.model.impl.ModelConstants; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.importer.ObjectImporter; -import com.evolveum.midpoint.model.impl.lens.AssignmentPathVariables; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensElementContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.crypto.EncryptionException; -import com.evolveum.midpoint.prism.crypto.Protector; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.path.ItemName; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.query.FullTextFilter; -import com.evolveum.midpoint.prism.query.InOidFilter; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectPaging; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.ValueFilter; -import com.evolveum.midpoint.repo.api.PreconditionViolationException; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.repo.common.util.RepoCommonUtils; -import com.evolveum.midpoint.schema.ObjectDeltaOperation; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ExceptionUtil; -import com.evolveum.midpoint.schema.util.FocusTypeUtil; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.task.api.RunningTask; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.QNameUtil; -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.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; -import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; -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; -import org.jetbrains.annotations.Nullable; - -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * - * @author lazyman - * - */ -public class ModelImplUtils { - - private static final String OPERATION_RESOLVE_REFERENCE = ObjectImporter.class.getName() + ".resolveReference"; - - private static final Trace LOGGER = TraceManager.getTrace(ModelImplUtils.class); - - public static void validatePaging(ObjectPaging paging) { - if (paging == null) { - return; - } - - if (paging.getMaxSize() != null && paging.getMaxSize().longValue() < 0) { - throw new IllegalArgumentException("Paging max size must be more than 0."); - } - if (paging.getOffset() != null && paging.getOffset().longValue() < 0) { - throw new IllegalArgumentException("Paging offset index must be more than 0."); - } - } - - public static void recordFatalError(OperationResult result, Throwable e) { - recordFatalError(result, e.getMessage(), e); - } - - public static void recordFatalError(OperationResult result, String message, Throwable e) { - // Do not log at ERROR level. This is too harsh. Especially in object not found case. - // What model considers an error may be just a normal situation for the code is using model API. - // If this is really an error then it should be logged by the invoking code. - LoggingUtils.logExceptionOnDebugLevel(LOGGER, message, e); - result.recordFatalError(message, e); - result.cleanupResult(e); - } - - public static void recordPartialError(OperationResult result, Throwable e) { - recordPartialError(result, e.getMessage(), e); - } - - public static void recordPartialError(OperationResult result, String message, Throwable e) { - // Do not log at ERROR level. This is too harsh. Especially in object not found case. - // What model considers an error may be just a normal situation for the code is using model API. - // If this is really an error then it should be logged by the invoking code. - LoggingUtils.logExceptionOnDebugLevel(LOGGER, message, e); - result.recordPartialError(message, e); - result.cleanupResult(e); - } - - public static String getOperationUrlFromDelta(ObjectDelta delta) { - if (delta == null) { - return null; - } - if (delta.isAdd()) { - return ModelAuthorizationAction.ADD.getUrl(); - } - if (delta.isModify()) { - return ModelAuthorizationAction.MODIFY.getUrl(); - } - if (delta.isDelete()) { - return ModelAuthorizationAction.DELETE.getUrl(); - } - throw new IllegalArgumentException("Unknown delta type "+delta); - } - - - // from the most to least appropriate - @NotNull - public static List getApplicablePolicies( - @Nullable Class objectClass, List objectSubtypes, SystemConfigurationType systemConfigurationType) - throws ConfigurationException { - List rv = new ArrayList<>(); - List typeNoSubtype = new ArrayList<>(); - List typeWithSubtype = new ArrayList<>(); - List noTypeNoSubtype = new ArrayList<>(); - List noTypeWithSubtype = new ArrayList<>(); - List all = new ArrayList<>(); - - all.addAll(systemConfigurationType.getDefaultObjectPolicyConfiguration()); - - for (ObjectPolicyConfigurationType aPolicyConfigurationType: all) { - QName typeQName = aPolicyConfigurationType.getType(); - if (typeQName != null) { - ObjectTypes objectType = ObjectTypes.getObjectTypeFromTypeQName(typeQName); - if (objectType == null) { - throw new ConfigurationException( - "Unknown type " + typeQName + " in default object policy definition or object template definition in system configuration"); - } - if (objectType.getClassDefinition() == objectClass) { - String aSubType = aPolicyConfigurationType.getSubtype(); - if (aSubType == null) { - typeNoSubtype.add(aPolicyConfigurationType); - } else if (objectSubtypes != null && objectSubtypes.contains(aSubType)) { - typeWithSubtype.add(aPolicyConfigurationType); - } - } - } else { - String aSubType = aPolicyConfigurationType.getSubtype(); - if (aSubType == null) { - noTypeNoSubtype.add(aPolicyConfigurationType); - } else if (objectSubtypes != null && objectSubtypes.contains(aSubType)) { - noTypeWithSubtype.add(aPolicyConfigurationType); - } - } - } - rv.addAll(typeWithSubtype); - rv.addAll(typeNoSubtype); - rv.addAll(noTypeWithSubtype); - rv.addAll(noTypeNoSubtype); - return rv; - } - - @NotNull - public static List getApplicablePolicies(LensContext context) { - PrismObject config = context.getSystemConfiguration(); - if (config == null) { - return Collections.emptyList(); - } - LensFocusContext focusContext = context.getFocusContext(); - PrismObject focusObject = focusContext != null ? focusContext.getObjectAny() : null; - Class focusClass = focusContext != null ? focusContext.getObjectTypeClass() : null; - List subTypes = FocusTypeUtil.determineSubTypes(focusObject); - List relevantPolicies; - try { - relevantPolicies = ModelImplUtils.getApplicablePolicies(focusClass, subTypes, config.asObjectable()); - } catch (ConfigurationException e) { - throw new SystemException("Couldn't get relevant object policies", e); - } - LOGGER.trace("Relevant policies: {}", relevantPolicies); - return relevantPolicies; - } - - public static ConflictResolutionType getConflictResolution(LensContext context) { - for (ObjectPolicyConfigurationType p : ModelImplUtils.getApplicablePolicies(context)) { - if (p.getConflictResolution() != null) { - return p.getConflictResolution(); - } - } - return null; - } - - /** - * Resolves references contained in given PrismObject. - * - * @param enforceReferentialIntegrity If true, missing reference causes fatal error when processing (if false, only warning is issued). - * @param forceFilterReevaluation If true, references are reevaluated even if OID is present. (Given that filter is present as well, of course.) - */ - public static void resolveReferences(PrismObject object, RepositoryService repository, - boolean enforceReferentialIntegrity, boolean forceFilterReevaluation, EvaluationTimeType resolutionTime, - boolean throwExceptionOnFailure, - PrismContext prismContext, OperationResult result) { - - Visitor visitor = visitable -> { - if (!(visitable instanceof PrismReferenceValue)) { - return; - } - resolveRef((PrismReferenceValue)visitable, repository, enforceReferentialIntegrity, forceFilterReevaluation, - resolutionTime, prismContext, object.toString(), throwExceptionOnFailure, result); - }; - object.accept(visitor); - } - - /** - * Resolves references contained in ADD and REPLACE value sets for item modifications in a given ObjectDelta. - * (specially treats collisions with values to be deleted) - */ - - public static void resolveReferences(ObjectDelta objectDelta, RepositoryService repository, - boolean enforceReferentialIntegrity, boolean forceFilterReevaluation, - EvaluationTimeType resolutionTime, boolean throwExceptionOnFailure, - PrismContext prismContext, OperationResult result) { - - Visitor visitor = visitable -> { - if (!(visitable instanceof PrismReferenceValue)) { - return; - } - resolveRef((PrismReferenceValue)visitable, repository, enforceReferentialIntegrity, forceFilterReevaluation, - resolutionTime, prismContext, objectDelta.toString(), throwExceptionOnFailure, result); - }; - // We could use objectDelta.accept(visitor), but we want to visit only values to add and replace - // (NOT values to delete! - otherwise very strange effects could result) - - // Another problem is that it is possible that one of valuesToAdd became (after resolving) - // a value that is meant do be deleted. The result would be deletion of that value; definitely - // not what we would want or expect. So we have to check whether a value that was not among - // values to be deleted accidentally becomes one of values to be deleted. - if (objectDelta.isAdd()) { - objectDelta.getObjectToAdd().accept(visitor); - } else if (objectDelta.isModify()) { - for (ItemDelta delta : objectDelta.getModifications()) { - applyVisitorToValues(delta.getValuesToAdd(), delta, visitor); - applyVisitorToValues(delta.getValuesToReplace(), delta, visitor); - } - } - } - - // see description in caller - static void applyVisitorToValues(Collection values, ItemDelta delta, Visitor visitor) { - Collection valuesToDelete = delta.getValuesToDelete(); - if (valuesToDelete == null) { - valuesToDelete = new ArrayList<>(0); // just to simplify the code below - } - if (values != null) { - for (PrismValue pval : values) { - boolean isToBeDeleted = valuesToDelete.contains(pval); - pval.accept(visitor); - if (!isToBeDeleted && valuesToDelete.contains(pval)) { - // value becomes 'to be deleted' -> we remove it from toBeDeleted list - delta.removeValueToDelete(pval); - } - } - } - } - - private static void resolveRef(PrismReferenceValue refVal, RepositoryService repository, - boolean enforceReferentialIntegrity, boolean forceFilterReevaluation, EvaluationTimeType evaluationTimeType, - PrismContext prismContext, String contextDesc, boolean throwExceptionOnFailure, OperationResult parentResult) { - String refName = refVal.getParent() != null ? - refVal.getParent().getElementName().toString() : "(unnamed)"; - - if ((refVal.getResolutionTime() != null && refVal.getResolutionTime() != evaluationTimeType) || - (refVal.getResolutionTime() == null && evaluationTimeType != EvaluationTimeType.IMPORT)) { - LOGGER.trace("Skipping resolution of reference {} in {} because the resolutionTime is set to {}", refName, contextDesc, refVal.getResolutionTime()); - return; - } - - OperationResult result = parentResult.createMinorSubresult(OPERATION_RESOLVE_REFERENCE); - result.addContext(OperationResult.CONTEXT_ITEM, refName); - - QName typeQName = null; - if (refVal.getTargetType() != null) { - typeQName = refVal.getTargetType(); - } - if (typeQName == null) { - PrismReferenceDefinition definition = (PrismReferenceDefinition) refVal.getParent().getDefinition(); - if (definition != null) { - typeQName = definition.getTargetTypeName(); - } - } - Class type = ObjectType.class; - if (typeQName != null) { - type = prismContext.getSchemaRegistry().determineCompileTimeClass(typeQName); - if (type == null) { - result.recordWarning("Unknown type specified in reference or definition of reference " + refName + ": " - + typeQName); - type = ObjectType.class; - } - } - SearchFilterType filter = refVal.getFilter(); - - if (!StringUtils.isBlank(refVal.getOid()) && (!forceFilterReevaluation || filter == null)) { - // We have OID (and "force filter reevaluation" is not requested or not possible) - if (filter != null) { - // We have both filter and OID. We will choose OID, but let's at - // least log a warning - LOGGER.debug("Both OID and filter for property {} in {}, OID takes precedence", refName, contextDesc); - } - // Nothing to resolve, but let's check if the OID exists - PrismObject object = null; - try { - object = repository.getObject(type, refVal.getOid(), null, result); - } catch (ObjectNotFoundException e) { - String message = "Reference " + refName + " refers to a non-existing object " + refVal.getOid(); - if (enforceReferentialIntegrity) { - LOGGER.error(message); - result.recordFatalError(message); - if (throwExceptionOnFailure) { - throw new SystemException(message, e); - } - } else { - LOGGER.warn(message); - result.recordWarning(message); - } - } catch (SchemaException e) { - String message = "Schema error while trying to retrieve object " + refVal.getOid() + " : " + e.getMessage(); - result.recordPartialError(message, e); - LOGGER.error(message, e); - // But continue otherwise - } - if (object != null && refVal.getOriginType() != null) { - // Check if declared and actual type matches - if (!object.getClass().equals(type)) { - result.recordWarning("Type mismatch on property " + refName + ": declared:" - + refVal.getOriginType() + ", actual: " + object.getClass()); - } - } - result.recordSuccessIfUnknown(); - parentResult.computeStatus(); - return; - } - - if (filter == null) { - if (refVal.getObject() != null) { - LOGGER.trace("Skipping resolution of reference {} in {} because the object is present and the filter is not", refName, contextDesc); - result.recordNotApplicableIfUnknown(); - return; - } - // No OID and no filter. We are lost. - String message = "Neither OID nor filter for property " + refName + ": cannot resolve reference"; - result.recordFatalError(message); - if (throwExceptionOnFailure) { - throw new SystemException(message); - } - return; - } - // No OID and we have filter. Let's check the filter a bit - ObjectFilter objFilter; - try{ - PrismObjectDefinition objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type); - objFilter = prismContext.getQueryConverter().parseFilter(filter, objDef); - } catch (SchemaException ex){ - LOGGER.error("Failed to convert object filter from filter because of: "+ ex.getMessage() + "; filter: " + filter.debugDump(), ex); - throw new SystemException("Failed to convert object filter from filter. Reason: " + ex.getMessage(), ex); - } - - LOGGER.trace("Resolving using filter {}", objFilter.debugDump()); - List> objects; - QName objectType = refVal.getTargetType(); - if (objectType == null) { - String message = "Missing definition of type of reference " + refName; - result.recordFatalError(message); - if (throwExceptionOnFailure) { - throw new SystemException(message); - } - return; - } - - if (containExpression(objFilter)){ - result.recordSuccessIfUnknown(); - return; - } - - try { - ObjectQuery query = prismContext.queryFactory().createQuery(objFilter); - objects = (List)repository.searchObjects(type, query, null, result); - - } catch (SchemaException e) { - // This is unexpected, but may happen. Record fatal error - String message = "Repository schema error during resolution of reference " + refName; - result.recordFatalError(message, e); - if (throwExceptionOnFailure) { - throw new SystemException(message, e); - } - return; - } catch (SystemException e) { - // We don't want this to tear down entire import. - String message = "Repository system error during resolution of reference " + refName; - result.recordFatalError(message, e); - if (throwExceptionOnFailure) { - throw new SystemException(message, e); - } - return; - } - if (objects.isEmpty()) { - String message = "Repository reference " + refName + " cannot be resolved: filter matches no object"; - result.recordFatalError(message); - if (throwExceptionOnFailure) { - throw new SystemException(message); - } - return; - } - if (objects.size() > 1) { - String message = "Repository reference " + refName - + " cannot be resolved: filter matches " + objects.size() + " objects"; - result.recordFatalError(message); - if (throwExceptionOnFailure) { - throw new SystemException(message); - } - return; - } - // Bingo. We have exactly one object. - String oid = objects.get(0).getOid(); - refVal.setOid(oid); - result.recordSuccessIfUnknown(); - } - - private static boolean containExpression(ObjectFilter filter) { - if (filter == null) { - return false; - } - if (filter instanceof InOidFilter && ((InOidFilter) filter).getExpression() != null) { - return true; - } - if (filter instanceof FullTextFilter && ((FullTextFilter) filter).getExpression() != null) { - return true; - } - if (filter instanceof ValueFilter && ((ValueFilter) filter).getExpression() != null) { - return true; - } - return false; - } - - public static ObjectClassComplexTypeDefinition determineObjectClass(RefinedResourceSchema refinedSchema, Task task) - throws SchemaException { - QName objectclass = getTaskExtensionPropertyValue(task, ModelConstants.OBJECTCLASS_PROPERTY_NAME); - ShadowKindType kind = getTaskExtensionPropertyValue(task, ModelConstants.KIND_PROPERTY_NAME); - String intent = getTaskExtensionPropertyValue(task, ModelConstants.INTENT_PROPERTY_NAME); - - return determineObjectClassInternal(refinedSchema, objectclass, kind, intent, task); - } - - private static T getTaskExtensionPropertyValue(Task task, ItemName propertyName) { - PrismProperty property = task.getExtensionPropertyOrClone(propertyName); - if (property != null) { - return property.getValue().getValue(); - } else { - return null; - } - } - - public static ObjectClassComplexTypeDefinition determineObjectClass(RefinedResourceSchema refinedSchema, PrismObject shadow) throws SchemaException { - ShadowType s = shadow.asObjectable(); - return determineObjectClassInternal(refinedSchema, s.getObjectClass(), s.getKind(), s.getIntent(), s); - } - - private static ObjectClassComplexTypeDefinition determineObjectClassInternal( - RefinedResourceSchema refinedSchema, QName objectclass, ShadowKindType kind, String intent, Object source) throws SchemaException { - - if (kind == null && intent == null && objectclass != null) { - // Return generic object class definition from resource schema. No kind/intent means that we want - // to process all kinds and intents in the object class. - ObjectClassComplexTypeDefinition objectClassDefinition = refinedSchema.getOriginalResourceSchema().findObjectClassDefinition(objectclass); - if (objectClassDefinition == null) { - throw new SchemaException("No object class "+objectclass+" in the schema for "+source); - } - return objectClassDefinition; - } - - RefinedObjectClassDefinition refinedObjectClassDefinition; - - if (kind != null) { - refinedObjectClassDefinition = refinedSchema.getRefinedDefinition(kind, intent); - LOGGER.trace("Determined refined object class {} by using kind={}, intent={}", - refinedObjectClassDefinition, kind, intent); - } else if (objectclass != null) { - refinedObjectClassDefinition = refinedSchema.getRefinedDefinition(objectclass); - LOGGER.trace("Determined refined object class {} by using objectClass={}", refinedObjectClassDefinition, objectclass); - } else { - refinedObjectClassDefinition = null; - LOGGER.debug("No kind or objectclass specified in {}, assuming null object class", source); - } - - return refinedObjectClassDefinition; - } - - public static void encrypt(Collection> deltas, Protector protector, ModelExecuteOptions options, - OperationResult result) { - // Encrypt values even before we log anything. We want to avoid showing unencrypted values in the logfiles - if (!ModelExecuteOptions.isNoCrypt(options)) { - for(ObjectDelta delta: deltas) { - try { - CryptoUtil.encryptValues(protector, delta); - } catch (EncryptionException e) { - result.recordFatalError(e); - throw new SystemException(e.getMessage(), e); - } - } - } - } - - public static void setRequestee(Task task, LensContext context) { - PrismObject object; - if (context != null && context.getFocusContext() != null - && UserType.class.isAssignableFrom(context.getFocusContext().getObjectTypeClass())) { - object = context.getFocusContext().getObjectAny(); - } else { - object = null; - } - setRequestee(task, object); - } - - public static void setRequestee(Task task, LensFocusContext context) { - setRequestee(task, context.getLensContext()); - } - - public static void setRequestee(Task task, PrismObject object) { - LOGGER.trace("setting requestee in {} to {}", task, object); - if (task != null) { - task.setRequesteeTransient(object); - } - } - - public static void clearRequestee(Task task) { - setRequestee(task, (PrismObject) null); - } - - public static ModelExecuteOptions getModelExecuteOptions(@NotNull Task task) throws SchemaException { - PrismProperty item = task.getExtensionPropertyOrClone(SchemaConstants.C_MODEL_EXECUTE_OPTIONS); - if (item == null || item.isEmpty()) { - return null; - } else if (item.getValues().size() > 1) { - throw new SchemaException("Unexpected number of values for option 'modelExecuteOptions'."); - } else { - ModelExecuteOptionsType modelExecuteOptionsType = item.getValues().iterator().next().getValue(); - if (modelExecuteOptionsType != null) { - return ModelExecuteOptions.fromModelExecutionOptionsType(modelExecuteOptionsType); - } else { - return null; - } - } - } - - public static ExpressionVariables getDefaultExpressionVariables(@NotNull LensContext context, @Nullable LensProjectionContext projCtx) throws SchemaException { - ExpressionVariables variables = new ExpressionVariables(); - if (context.getFocusContext() != null) { - variables.put(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDeltaObject().getDefinition()); - variables.put(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDeltaObject().getDefinition()); - variables.registerAlias(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS); - } - if (projCtx != null) { - variables.put(ExpressionConstants.VAR_PROJECTION, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); - variables.put(ExpressionConstants.VAR_SHADOW, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); - variables.put(ExpressionConstants.VAR_ACCOUNT, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); - variables.registerAlias(ExpressionConstants.VAR_ACCOUNT, ExpressionConstants.VAR_PROJECTION); - variables.registerAlias(ExpressionConstants.VAR_SHADOW, ExpressionConstants.VAR_PROJECTION); - variables.put(ExpressionConstants.VAR_RESOURCE, projCtx.getResource(), projCtx.getResource().asPrismObject().getDefinition()); - } - - variables.put(ExpressionConstants.VAR_OPERATION, projCtx.getOperation().getValue(), String.class); - variables.put(ExpressionConstants.VAR_ITERATION, LensUtil.getIterationVariableValue(projCtx), Integer.class); - variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, LensUtil.getIterationTokenVariableValue(projCtx), String.class); - - variables.put(ExpressionConstants.VAR_CONFIGURATION, context.getSystemConfiguration(), context.getSystemConfiguration().getDefinition()); - return variables; - } - - public static ExpressionVariables getDefaultExpressionVariables(ObjectType focusType, - ShadowType shadowType, ResourceType resourceType, SystemConfigurationType configurationType, - PrismContext prismContext) { - PrismObject focus = null; - if (focusType != null) { - focus = focusType.asPrismObject(); - } - PrismObject shadow = null; - if (shadowType != null) { - shadow = shadowType.asPrismObject(); - } - PrismObject resource = null; - if (resourceType != null) { - resource = resourceType.asPrismObject(); - } - PrismObject configuration = null; - if (configurationType != null) { - configuration = configurationType.asPrismObject(); - } - return getDefaultExpressionVariables(focus, shadow, null, resource, configuration, null, prismContext); - } - - public static ExpressionVariables getDefaultExpressionVariables(PrismObject focus, - PrismObject shadow, ResourceShadowDiscriminator discr, - PrismObject resource, PrismObject configuration, LensElementContext affectedElementContext, - PrismContext prismContext) { - ExpressionVariables variables = new ExpressionVariables(); - addDefaultExpressionVariables(variables, focus, shadow, discr, resource, configuration, affectedElementContext, prismContext); - return variables; - } - - public static void addDefaultExpressionVariables(ExpressionVariables variables, PrismObject focus, - PrismObject shadow, ResourceShadowDiscriminator discr, - PrismObject resource, PrismObject configuration, LensElementContext affectedElementContext, - PrismContext prismContext) { - - PrismObjectDefinition focusDef; - if (focus == null) { - focusDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(FocusType.class); - } else { - focusDef = focus.getDefinition(); - } - - PrismObjectDefinition shadowDef; - if (shadow == null) { - shadowDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class); - } else { - shadowDef = shadow.getDefinition(); - } - - PrismObjectDefinition resourceDef; - if (resource == null) { - resourceDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ResourceType.class); - } else { - resourceDef = resource.getDefinition(); - } - - PrismObjectDefinition configDef; - if (configuration == null) { - configDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(SystemConfigurationType.class); - } else { - configDef = configuration.getDefinition(); - } - - // Legacy. And convenience/understandability. - if (focus == null || focus.canRepresent(UserType.class) || (discr != null && discr.getKind() == ShadowKindType.ACCOUNT)) { - variables.put(ExpressionConstants.VAR_USER, focus, focusDef); - variables.put(ExpressionConstants.VAR_ACCOUNT, shadow, shadowDef); - } - - variables.put(ExpressionConstants.VAR_FOCUS, focus, focusDef); - variables.put(ExpressionConstants.VAR_SHADOW, shadow, shadowDef); - variables.put(ExpressionConstants.VAR_PROJECTION, shadow, shadowDef); - variables.put(ExpressionConstants.VAR_RESOURCE, resource, resourceDef); - variables.put(ExpressionConstants.VAR_CONFIGURATION, configuration, configDef); - - if (affectedElementContext != null) { - variables.put(ExpressionConstants.VAR_OPERATION, affectedElementContext.getOperation().getValue(), String.class); - // We do not want to add delta to all expressions. The delta may be tricky. Is it focus delta? projection delta? Primary? Secondary? - // It is better to leave delta to be accessed from the model context. And in cases when it is clear which delta is meant - // (e.g. provisioning scripts) we can still add the delta explicitly. - } - } - - public static void addAssignmentPathVariables(AssignmentPathVariables assignmentPathVariables, ExpressionVariables expressionVariables, PrismContext prismContext) { - if (assignmentPathVariables != null) { - PrismContainerDefinition assignmentDef = assignmentPathVariables.getAssignmentDefinition(); - expressionVariables.put(ExpressionConstants.VAR_ASSIGNMENT, assignmentPathVariables.getMagicAssignment(), assignmentDef); - expressionVariables.put(ExpressionConstants.VAR_ASSIGNMENT_PATH, assignmentPathVariables.getAssignmentPath(), AssignmentPath.class); - expressionVariables.put(ExpressionConstants.VAR_IMMEDIATE_ASSIGNMENT, assignmentPathVariables.getImmediateAssignment(), assignmentDef); - expressionVariables.put(ExpressionConstants.VAR_THIS_ASSIGNMENT, assignmentPathVariables.getThisAssignment(), assignmentDef); - expressionVariables.put(ExpressionConstants.VAR_FOCUS_ASSIGNMENT, assignmentPathVariables.getFocusAssignment(), assignmentDef); - PrismObjectDefinition abstractRoleDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(AbstractRoleType.class); - expressionVariables.put(ExpressionConstants.VAR_IMMEDIATE_ROLE, (PrismObject) assignmentPathVariables.getImmediateRole(), abstractRoleDefinition); - } else { - // to avoid "no such variable" exceptions in boundary cases - // for null/empty paths we might consider creating empty AssignmentPathVariables objects to keep null/empty path distinction - expressionVariables.put(ExpressionConstants.VAR_ASSIGNMENT_PATH, null, AssignmentPath.class); - } - } - - public static PrismReferenceValue determineAuditTargetDeltaOps( - Collection> deltaOps, - PrismContext prismContext) { - if (deltaOps == null || deltaOps.isEmpty()) { - return null; - } - if (deltaOps.size() == 1) { - ObjectDeltaOperation deltaOp = deltaOps.iterator().next(); - return getAuditTarget(deltaOp.getObjectDelta(), prismContext); - } - for (ObjectDeltaOperation deltaOp: deltaOps) { - if (!ShadowType.class.isAssignableFrom(deltaOp.getObjectDelta().getObjectTypeClass())) { - return getAuditTarget(deltaOp.getObjectDelta(), prismContext); - } - } - // Several raw operations, all on shadows, no focus ... this should not happen - // But if it does we rather do not specify any target. We should not like to choose - // target randomly. That would be confusing. - return null; - } - - public static PrismReferenceValue determineAuditTarget(Collection> deltas, - PrismContext prismContext) { - if (deltas == null || deltas.isEmpty()) { - return null; - } - if (deltas.size() == 1) { - ObjectDelta delta = deltas.iterator().next(); - return getAuditTarget(delta, prismContext); - } - for (ObjectDelta delta: deltas) { - if (!ShadowType.class.isAssignableFrom(delta.getObjectTypeClass())) { - return getAuditTarget(delta, prismContext); - } - } - // Several raw operations, all on shadows, no focus ... this should not happen - // But if it does we rather do not specify any target. We should not like to choose - // target randomly. That would be confusing. - return null; - } - - private static PrismReferenceValue getAuditTarget(ObjectDelta delta, - PrismContext prismContext) { - PrismReferenceValue targetRef = prismContext.itemFactory().createReferenceValue(delta.getOid()); - targetRef.setTargetType(ObjectTypes.getObjectType(delta.getObjectTypeClass()).getTypeQName()); - if (delta.isAdd()) { - targetRef.setObject(delta.getObjectToAdd()); - } - return targetRef; - } - - public static List evaluateScript( - ScriptExpression scriptExpression, LensContext lensContext, ExpressionVariables variables, boolean useNew, String shortDesc, Task task, OperationResult parentResult) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - ExpressionEnvironment env = new ExpressionEnvironment<>(); - env.setLensContext(lensContext); - env.setCurrentResult(parentResult); - env.setCurrentTask(task); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); - - try { - - return scriptExpression.evaluate(variables, ScriptExpressionReturnTypeType.SCALAR, useNew, shortDesc, task, parentResult); - - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); -// if (lensContext.getDebugListener() != null) { -// lensContext.getDebugListener().afterScriptEvaluation(lensContext, scriptExpression); -// } - } - } - - public static CriticalityType handleConnectorErrorCriticality(ResourceType resourceType, Throwable e, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { - CriticalityType criticality; - if (resourceType == null) { - RepoCommonUtils.throwException(e, result); - return CriticalityType.FATAL; // not reached - } else { - ErrorSelectorType errorSelector = ResourceTypeUtil.getConnectorErrorCriticality(resourceType); - if (e instanceof CommunicationException) { - // Network problem. Just continue evaluation. The error is recorded in the result. - // The consistency mechanism has (most likely) already done the best. - // We cannot do any better. - criticality = ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.PARTIAL); - } else if (e instanceof SchemaException || e instanceof PolicyViolationException) { - // This may be caused by a variety of causes. It may be multiple values in a single-valued attribute. - // But it may also be duplicate value or a problem of resource-side password policy. - // Treat this as partial error by default. This is partially motivated by compatibility with - // midPoint 3.8 and earlier. This may be reviewed in the future. - criticality = ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.PARTIAL); - } else { - criticality = ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.FATAL); - } - } - RepoCommonUtils.processErrorCriticality(resourceType, criticality, e, result); - return criticality; - } - - public static String generateRequestIdentifier() { - return UUID.randomUUID().toString(); - } - - /* - the ordering algorithm is: the first level is occupied by - the column which previousColumn == null || "" || notExistingColumnNameValue. - Each next level contains columns which - previousColumn == columnNameFromPreviousLevel - */ - public static List orderCustomColumns(List customColumns){ - if (customColumns == null || customColumns.size() == 0){ - return new ArrayList<>(); - } - List customColumnsList = new ArrayList<>(customColumns); - List previousColumnValues = new ArrayList<>(); - previousColumnValues.add(null); - previousColumnValues.add(""); - - Map columnRefsMap = new HashMap<>(); - for (GuiObjectColumnType column : customColumns){ - columnRefsMap.put(column.getName(), column.getPreviousColumn() == null ? "" : column.getPreviousColumn()); - } - - List temp = new ArrayList<> (); - int index = 0; - while (index < customColumns.size()){ - int sortFrom = index; - for (int i = index; i < customColumnsList.size(); i++){ - GuiObjectColumnType column = customColumnsList.get(i); - if (previousColumnValues.contains(column.getPreviousColumn()) || - !columnRefsMap.containsKey(column.getPreviousColumn())){ - Collections.swap(customColumnsList, index, i); - index++; - temp.add(column.getName()); - } - } - if (temp.size() == 0){ - temp.add(customColumnsList.get(index).getName()); - index++; - } - if (index - sortFrom > 1){ - customColumnsList.subList(sortFrom, index - 1) - .sort((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName())); - } - previousColumnValues.clear(); - previousColumnValues.addAll(temp); - temp.clear(); - } - return customColumnsList; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.util; + +import com.evolveum.midpoint.common.crypto.CryptoUtil; +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.AssignmentPath; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; +import com.evolveum.midpoint.model.impl.ModelConstants; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.importer.ObjectImporter; +import com.evolveum.midpoint.model.impl.lens.AssignmentPathVariables; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensElementContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.path.ItemName; +import com.evolveum.midpoint.prism.query.FullTextFilter; +import com.evolveum.midpoint.prism.query.InOidFilter; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectPaging; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.ValueFilter; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.repo.common.util.RepoCommonUtils; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.schema.util.FocusTypeUtil; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +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.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.*; +import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; +import com.evolveum.prism.xml.ns._public.types_3.EvaluationTimeType; + +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +/** + * + * @author lazyman + * + */ +public class ModelImplUtils { + + private static final String OPERATION_RESOLVE_REFERENCE = ObjectImporter.class.getName() + ".resolveReference"; + + private static final Trace LOGGER = TraceManager.getTrace(ModelImplUtils.class); + + public static void validatePaging(ObjectPaging paging) { + if (paging == null) { + return; + } + + if (paging.getMaxSize() != null && paging.getMaxSize().longValue() < 0) { + throw new IllegalArgumentException("Paging max size must be more than 0."); + } + if (paging.getOffset() != null && paging.getOffset().longValue() < 0) { + throw new IllegalArgumentException("Paging offset index must be more than 0."); + } + } + + public static void recordFatalError(OperationResult result, Throwable e) { + recordFatalError(result, e.getMessage(), e); + } + + public static void recordFatalError(OperationResult result, String message, Throwable e) { + // Do not log at ERROR level. This is too harsh. Especially in object not found case. + // What model considers an error may be just a normal situation for the code is using model API. + // If this is really an error then it should be logged by the invoking code. + LoggingUtils.logExceptionOnDebugLevel(LOGGER, message, e); + result.recordFatalError(message, e); + result.cleanupResult(e); + } + + public static void recordPartialError(OperationResult result, Throwable e) { + recordPartialError(result, e.getMessage(), e); + } + + public static void recordPartialError(OperationResult result, String message, Throwable e) { + // Do not log at ERROR level. This is too harsh. Especially in object not found case. + // What model considers an error may be just a normal situation for the code is using model API. + // If this is really an error then it should be logged by the invoking code. + LoggingUtils.logExceptionOnDebugLevel(LOGGER, message, e); + result.recordPartialError(message, e); + result.cleanupResult(e); + } + + public static String getOperationUrlFromDelta(ObjectDelta delta) { + if (delta == null) { + return null; + } + if (delta.isAdd()) { + return ModelAuthorizationAction.ADD.getUrl(); + } + if (delta.isModify()) { + return ModelAuthorizationAction.MODIFY.getUrl(); + } + if (delta.isDelete()) { + return ModelAuthorizationAction.DELETE.getUrl(); + } + throw new IllegalArgumentException("Unknown delta type "+delta); + } + + + // from the most to least appropriate + @NotNull + public static List getApplicablePolicies( + @Nullable Class objectClass, List objectSubtypes, SystemConfigurationType systemConfigurationType) + throws ConfigurationException { + List rv = new ArrayList<>(); + List typeNoSubtype = new ArrayList<>(); + List typeWithSubtype = new ArrayList<>(); + List noTypeNoSubtype = new ArrayList<>(); + List noTypeWithSubtype = new ArrayList<>(); + List all = new ArrayList<>(); + + all.addAll(systemConfigurationType.getDefaultObjectPolicyConfiguration()); + + for (ObjectPolicyConfigurationType aPolicyConfigurationType: all) { + QName typeQName = aPolicyConfigurationType.getType(); + if (typeQName != null) { + ObjectTypes objectType = ObjectTypes.getObjectTypeFromTypeQName(typeQName); + if (objectType == null) { + throw new ConfigurationException( + "Unknown type " + typeQName + " in default object policy definition or object template definition in system configuration"); + } + if (objectType.getClassDefinition() == objectClass) { + String aSubType = aPolicyConfigurationType.getSubtype(); + if (aSubType == null) { + typeNoSubtype.add(aPolicyConfigurationType); + } else if (objectSubtypes != null && objectSubtypes.contains(aSubType)) { + typeWithSubtype.add(aPolicyConfigurationType); + } + } + } else { + String aSubType = aPolicyConfigurationType.getSubtype(); + if (aSubType == null) { + noTypeNoSubtype.add(aPolicyConfigurationType); + } else if (objectSubtypes != null && objectSubtypes.contains(aSubType)) { + noTypeWithSubtype.add(aPolicyConfigurationType); + } + } + } + rv.addAll(typeWithSubtype); + rv.addAll(typeNoSubtype); + rv.addAll(noTypeWithSubtype); + rv.addAll(noTypeNoSubtype); + return rv; + } + + @NotNull + public static List getApplicablePolicies(LensContext context) { + PrismObject config = context.getSystemConfiguration(); + if (config == null) { + return Collections.emptyList(); + } + LensFocusContext focusContext = context.getFocusContext(); + PrismObject focusObject = focusContext != null ? focusContext.getObjectAny() : null; + Class focusClass = focusContext != null ? focusContext.getObjectTypeClass() : null; + List subTypes = FocusTypeUtil.determineSubTypes(focusObject); + List relevantPolicies; + try { + relevantPolicies = ModelImplUtils.getApplicablePolicies(focusClass, subTypes, config.asObjectable()); + } catch (ConfigurationException e) { + throw new SystemException("Couldn't get relevant object policies", e); + } + LOGGER.trace("Relevant policies: {}", relevantPolicies); + return relevantPolicies; + } + + public static ConflictResolutionType getConflictResolution(LensContext context) { + for (ObjectPolicyConfigurationType p : ModelImplUtils.getApplicablePolicies(context)) { + if (p.getConflictResolution() != null) { + return p.getConflictResolution(); + } + } + return null; + } + + /** + * Resolves references contained in given PrismObject. + * + * @param enforceReferentialIntegrity If true, missing reference causes fatal error when processing (if false, only warning is issued). + * @param forceFilterReevaluation If true, references are reevaluated even if OID is present. (Given that filter is present as well, of course.) + */ + public static void resolveReferences(PrismObject object, RepositoryService repository, + boolean enforceReferentialIntegrity, boolean forceFilterReevaluation, EvaluationTimeType resolutionTime, + boolean throwExceptionOnFailure, + PrismContext prismContext, OperationResult result) { + + Visitor visitor = visitable -> { + if (!(visitable instanceof PrismReferenceValue)) { + return; + } + resolveRef((PrismReferenceValue)visitable, repository, enforceReferentialIntegrity, forceFilterReevaluation, + resolutionTime, prismContext, object.toString(), throwExceptionOnFailure, result); + }; + object.accept(visitor); + } + + /** + * Resolves references contained in ADD and REPLACE value sets for item modifications in a given ObjectDelta. + * (specially treats collisions with values to be deleted) + */ + + public static void resolveReferences(ObjectDelta objectDelta, RepositoryService repository, + boolean enforceReferentialIntegrity, boolean forceFilterReevaluation, + EvaluationTimeType resolutionTime, boolean throwExceptionOnFailure, + PrismContext prismContext, OperationResult result) { + + Visitor visitor = visitable -> { + if (!(visitable instanceof PrismReferenceValue)) { + return; + } + resolveRef((PrismReferenceValue)visitable, repository, enforceReferentialIntegrity, forceFilterReevaluation, + resolutionTime, prismContext, objectDelta.toString(), throwExceptionOnFailure, result); + }; + // We could use objectDelta.accept(visitor), but we want to visit only values to add and replace + // (NOT values to delete! - otherwise very strange effects could result) + + // Another problem is that it is possible that one of valuesToAdd became (after resolving) + // a value that is meant do be deleted. The result would be deletion of that value; definitely + // not what we would want or expect. So we have to check whether a value that was not among + // values to be deleted accidentally becomes one of values to be deleted. + if (objectDelta.isAdd()) { + objectDelta.getObjectToAdd().accept(visitor); + } else if (objectDelta.isModify()) { + for (ItemDelta delta : objectDelta.getModifications()) { + applyVisitorToValues(delta.getValuesToAdd(), delta, visitor); + applyVisitorToValues(delta.getValuesToReplace(), delta, visitor); + } + } + } + + // see description in caller + static void applyVisitorToValues(Collection values, ItemDelta delta, Visitor visitor) { + Collection valuesToDelete = delta.getValuesToDelete(); + if (valuesToDelete == null) { + valuesToDelete = new ArrayList<>(0); // just to simplify the code below + } + if (values != null) { + for (PrismValue pval : values) { + boolean isToBeDeleted = valuesToDelete.contains(pval); + pval.accept(visitor); + if (!isToBeDeleted && valuesToDelete.contains(pval)) { + // value becomes 'to be deleted' -> we remove it from toBeDeleted list + delta.removeValueToDelete(pval); + } + } + } + } + + private static void resolveRef(PrismReferenceValue refVal, RepositoryService repository, + boolean enforceReferentialIntegrity, boolean forceFilterReevaluation, EvaluationTimeType evaluationTimeType, + PrismContext prismContext, String contextDesc, boolean throwExceptionOnFailure, OperationResult parentResult) { + String refName = refVal.getParent() != null ? + refVal.getParent().getElementName().toString() : "(unnamed)"; + + if ((refVal.getResolutionTime() != null && refVal.getResolutionTime() != evaluationTimeType) || + (refVal.getResolutionTime() == null && evaluationTimeType != EvaluationTimeType.IMPORT)) { + LOGGER.trace("Skipping resolution of reference {} in {} because the resolutionTime is set to {}", refName, contextDesc, refVal.getResolutionTime()); + return; + } + + OperationResult result = parentResult.createMinorSubresult(OPERATION_RESOLVE_REFERENCE); + result.addContext(OperationResult.CONTEXT_ITEM, refName); + + QName typeQName = null; + if (refVal.getTargetType() != null) { + typeQName = refVal.getTargetType(); + } + if (typeQName == null) { + PrismReferenceDefinition definition = (PrismReferenceDefinition) refVal.getParent().getDefinition(); + if (definition != null) { + typeQName = definition.getTargetTypeName(); + } + } + Class type = ObjectType.class; + if (typeQName != null) { + type = prismContext.getSchemaRegistry().determineCompileTimeClass(typeQName); + if (type == null) { + result.recordWarning("Unknown type specified in reference or definition of reference " + refName + ": " + + typeQName); + type = ObjectType.class; + } + } + SearchFilterType filter = refVal.getFilter(); + + if (!StringUtils.isBlank(refVal.getOid()) && (!forceFilterReevaluation || filter == null)) { + // We have OID (and "force filter reevaluation" is not requested or not possible) + if (filter != null) { + // We have both filter and OID. We will choose OID, but let's at + // least log a warning + LOGGER.debug("Both OID and filter for property {} in {}, OID takes precedence", refName, contextDesc); + } + // Nothing to resolve, but let's check if the OID exists + PrismObject object = null; + try { + object = repository.getObject(type, refVal.getOid(), null, result); + } catch (ObjectNotFoundException e) { + String message = "Reference " + refName + " refers to a non-existing object " + refVal.getOid(); + if (enforceReferentialIntegrity) { + LOGGER.error(message); + result.recordFatalError(message); + if (throwExceptionOnFailure) { + throw new SystemException(message, e); + } + } else { + LOGGER.warn(message); + result.recordWarning(message); + } + } catch (SchemaException e) { + String message = "Schema error while trying to retrieve object " + refVal.getOid() + " : " + e.getMessage(); + result.recordPartialError(message, e); + LOGGER.error(message, e); + // But continue otherwise + } + if (object != null && refVal.getOriginType() != null) { + // Check if declared and actual type matches + if (!object.getClass().equals(type)) { + result.recordWarning("Type mismatch on property " + refName + ": declared:" + + refVal.getOriginType() + ", actual: " + object.getClass()); + } + } + result.recordSuccessIfUnknown(); + parentResult.computeStatus(); + return; + } + + if (filter == null) { + if (refVal.getObject() != null) { + LOGGER.trace("Skipping resolution of reference {} in {} because the object is present and the filter is not", refName, contextDesc); + result.recordNotApplicableIfUnknown(); + return; + } + // No OID and no filter. We are lost. + String message = "Neither OID nor filter for property " + refName + ": cannot resolve reference"; + result.recordFatalError(message); + if (throwExceptionOnFailure) { + throw new SystemException(message); + } + return; + } + // No OID and we have filter. Let's check the filter a bit + ObjectFilter objFilter; + try{ + PrismObjectDefinition objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type); + objFilter = prismContext.getQueryConverter().parseFilter(filter, objDef); + } catch (SchemaException ex){ + LOGGER.error("Failed to convert object filter from filter because of: "+ ex.getMessage() + "; filter: " + filter.debugDump(), ex); + throw new SystemException("Failed to convert object filter from filter. Reason: " + ex.getMessage(), ex); + } + + LOGGER.trace("Resolving using filter {}", objFilter.debugDump()); + List> objects; + QName objectType = refVal.getTargetType(); + if (objectType == null) { + String message = "Missing definition of type of reference " + refName; + result.recordFatalError(message); + if (throwExceptionOnFailure) { + throw new SystemException(message); + } + return; + } + + if (containExpression(objFilter)){ + result.recordSuccessIfUnknown(); + return; + } + + try { + ObjectQuery query = prismContext.queryFactory().createQuery(objFilter); + objects = (List)repository.searchObjects(type, query, null, result); + + } catch (SchemaException e) { + // This is unexpected, but may happen. Record fatal error + String message = "Repository schema error during resolution of reference " + refName; + result.recordFatalError(message, e); + if (throwExceptionOnFailure) { + throw new SystemException(message, e); + } + return; + } catch (SystemException e) { + // We don't want this to tear down entire import. + String message = "Repository system error during resolution of reference " + refName; + result.recordFatalError(message, e); + if (throwExceptionOnFailure) { + throw new SystemException(message, e); + } + return; + } + if (objects.isEmpty()) { + String message = "Repository reference " + refName + " cannot be resolved: filter matches no object"; + result.recordFatalError(message); + if (throwExceptionOnFailure) { + throw new SystemException(message); + } + return; + } + if (objects.size() > 1) { + String message = "Repository reference " + refName + + " cannot be resolved: filter matches " + objects.size() + " objects"; + result.recordFatalError(message); + if (throwExceptionOnFailure) { + throw new SystemException(message); + } + return; + } + // Bingo. We have exactly one object. + String oid = objects.get(0).getOid(); + refVal.setOid(oid); + result.recordSuccessIfUnknown(); + } + + private static boolean containExpression(ObjectFilter filter) { + if (filter == null) { + return false; + } + if (filter instanceof InOidFilter && ((InOidFilter) filter).getExpression() != null) { + return true; + } + if (filter instanceof FullTextFilter && ((FullTextFilter) filter).getExpression() != null) { + return true; + } + if (filter instanceof ValueFilter && ((ValueFilter) filter).getExpression() != null) { + return true; + } + return false; + } + + public static ObjectClassComplexTypeDefinition determineObjectClass(RefinedResourceSchema refinedSchema, Task task) + throws SchemaException { + QName objectclass = getTaskExtensionPropertyValue(task, ModelConstants.OBJECTCLASS_PROPERTY_NAME); + ShadowKindType kind = getTaskExtensionPropertyValue(task, ModelConstants.KIND_PROPERTY_NAME); + String intent = getTaskExtensionPropertyValue(task, ModelConstants.INTENT_PROPERTY_NAME); + + return determineObjectClassInternal(refinedSchema, objectclass, kind, intent, task); + } + + private static T getTaskExtensionPropertyValue(Task task, ItemName propertyName) { + PrismProperty property = task.getExtensionPropertyOrClone(propertyName); + if (property != null) { + return property.getValue().getValue(); + } else { + return null; + } + } + + public static ObjectClassComplexTypeDefinition determineObjectClass(RefinedResourceSchema refinedSchema, PrismObject shadow) throws SchemaException { + ShadowType s = shadow.asObjectable(); + return determineObjectClassInternal(refinedSchema, s.getObjectClass(), s.getKind(), s.getIntent(), s); + } + + private static ObjectClassComplexTypeDefinition determineObjectClassInternal( + RefinedResourceSchema refinedSchema, QName objectclass, ShadowKindType kind, String intent, Object source) throws SchemaException { + + if (kind == null && intent == null && objectclass != null) { + // Return generic object class definition from resource schema. No kind/intent means that we want + // to process all kinds and intents in the object class. + ObjectClassComplexTypeDefinition objectClassDefinition = refinedSchema.getOriginalResourceSchema().findObjectClassDefinition(objectclass); + if (objectClassDefinition == null) { + throw new SchemaException("No object class "+objectclass+" in the schema for "+source); + } + return objectClassDefinition; + } + + RefinedObjectClassDefinition refinedObjectClassDefinition; + + if (kind != null) { + refinedObjectClassDefinition = refinedSchema.getRefinedDefinition(kind, intent); + LOGGER.trace("Determined refined object class {} by using kind={}, intent={}", + refinedObjectClassDefinition, kind, intent); + } else if (objectclass != null) { + refinedObjectClassDefinition = refinedSchema.getRefinedDefinition(objectclass); + LOGGER.trace("Determined refined object class {} by using objectClass={}", refinedObjectClassDefinition, objectclass); + } else { + refinedObjectClassDefinition = null; + LOGGER.debug("No kind or objectclass specified in {}, assuming null object class", source); + } + + return refinedObjectClassDefinition; + } + + public static void encrypt(Collection> deltas, Protector protector, ModelExecuteOptions options, + OperationResult result) { + // Encrypt values even before we log anything. We want to avoid showing unencrypted values in the logfiles + if (!ModelExecuteOptions.isNoCrypt(options)) { + for(ObjectDelta delta: deltas) { + try { + CryptoUtil.encryptValues(protector, delta); + } catch (EncryptionException e) { + result.recordFatalError(e); + throw new SystemException(e.getMessage(), e); + } + } + } + } + + public static void setRequestee(Task task, LensContext context) { + PrismObject object; + if (context != null && context.getFocusContext() != null + && UserType.class.isAssignableFrom(context.getFocusContext().getObjectTypeClass())) { + object = context.getFocusContext().getObjectAny(); + } else { + object = null; + } + setRequestee(task, object); + } + + public static void setRequestee(Task task, LensFocusContext context) { + setRequestee(task, context.getLensContext()); + } + + public static void setRequestee(Task task, PrismObject object) { + LOGGER.trace("setting requestee in {} to {}", task, object); + if (task != null) { + task.setRequesteeTransient(object); + } + } + + public static void clearRequestee(Task task) { + setRequestee(task, (PrismObject) null); + } + + public static ModelExecuteOptions getModelExecuteOptions(@NotNull Task task) throws SchemaException { + PrismProperty item = task.getExtensionPropertyOrClone(SchemaConstants.C_MODEL_EXECUTE_OPTIONS); + if (item == null || item.isEmpty()) { + return null; + } else if (item.getValues().size() > 1) { + throw new SchemaException("Unexpected number of values for option 'modelExecuteOptions'."); + } else { + ModelExecuteOptionsType modelExecuteOptionsType = item.getValues().iterator().next().getValue(); + if (modelExecuteOptionsType != null) { + return ModelExecuteOptions.fromModelExecutionOptionsType(modelExecuteOptionsType); + } else { + return null; + } + } + } + + public static ExpressionVariables getDefaultExpressionVariables(@NotNull LensContext context, @Nullable LensProjectionContext projCtx) throws SchemaException { + ExpressionVariables variables = new ExpressionVariables(); + if (context.getFocusContext() != null) { + variables.put(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDeltaObject().getDefinition()); + variables.put(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject(), context.getFocusContext().getObjectDeltaObject().getDefinition()); + variables.registerAlias(ExpressionConstants.VAR_USER, ExpressionConstants.VAR_FOCUS); + } + if (projCtx != null) { + variables.put(ExpressionConstants.VAR_PROJECTION, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); + variables.put(ExpressionConstants.VAR_SHADOW, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); + variables.put(ExpressionConstants.VAR_ACCOUNT, projCtx.getObjectDeltaObject(), projCtx.getObjectDefinition()); + variables.registerAlias(ExpressionConstants.VAR_ACCOUNT, ExpressionConstants.VAR_PROJECTION); + variables.registerAlias(ExpressionConstants.VAR_SHADOW, ExpressionConstants.VAR_PROJECTION); + variables.put(ExpressionConstants.VAR_RESOURCE, projCtx.getResource(), projCtx.getResource().asPrismObject().getDefinition()); + } + + variables.put(ExpressionConstants.VAR_OPERATION, projCtx.getOperation().getValue(), String.class); + variables.put(ExpressionConstants.VAR_ITERATION, LensUtil.getIterationVariableValue(projCtx), Integer.class); + variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, LensUtil.getIterationTokenVariableValue(projCtx), String.class); + + variables.put(ExpressionConstants.VAR_CONFIGURATION, context.getSystemConfiguration(), context.getSystemConfiguration().getDefinition()); + return variables; + } + + public static ExpressionVariables getDefaultExpressionVariables(ObjectType focusType, + ShadowType shadowType, ResourceType resourceType, SystemConfigurationType configurationType, + PrismContext prismContext) { + PrismObject focus = null; + if (focusType != null) { + focus = focusType.asPrismObject(); + } + PrismObject shadow = null; + if (shadowType != null) { + shadow = shadowType.asPrismObject(); + } + PrismObject resource = null; + if (resourceType != null) { + resource = resourceType.asPrismObject(); + } + PrismObject configuration = null; + if (configurationType != null) { + configuration = configurationType.asPrismObject(); + } + return getDefaultExpressionVariables(focus, shadow, null, resource, configuration, null, prismContext); + } + + public static ExpressionVariables getDefaultExpressionVariables(PrismObject focus, + PrismObject shadow, ResourceShadowDiscriminator discr, + PrismObject resource, PrismObject configuration, LensElementContext affectedElementContext, + PrismContext prismContext) { + ExpressionVariables variables = new ExpressionVariables(); + addDefaultExpressionVariables(variables, focus, shadow, discr, resource, configuration, affectedElementContext, prismContext); + return variables; + } + + public static void addDefaultExpressionVariables(ExpressionVariables variables, PrismObject focus, + PrismObject shadow, ResourceShadowDiscriminator discr, + PrismObject resource, PrismObject configuration, LensElementContext affectedElementContext, + PrismContext prismContext) { + + PrismObjectDefinition focusDef; + if (focus == null) { + focusDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(FocusType.class); + } else { + focusDef = focus.getDefinition(); + } + + PrismObjectDefinition shadowDef; + if (shadow == null) { + shadowDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class); + } else { + shadowDef = shadow.getDefinition(); + } + + PrismObjectDefinition resourceDef; + if (resource == null) { + resourceDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ResourceType.class); + } else { + resourceDef = resource.getDefinition(); + } + + PrismObjectDefinition configDef; + if (configuration == null) { + configDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(SystemConfigurationType.class); + } else { + configDef = configuration.getDefinition(); + } + + // Legacy. And convenience/understandability. + if (focus == null || focus.canRepresent(UserType.class) || (discr != null && discr.getKind() == ShadowKindType.ACCOUNT)) { + variables.put(ExpressionConstants.VAR_USER, focus, focusDef); + variables.put(ExpressionConstants.VAR_ACCOUNT, shadow, shadowDef); + } + + variables.put(ExpressionConstants.VAR_FOCUS, focus, focusDef); + variables.put(ExpressionConstants.VAR_SHADOW, shadow, shadowDef); + variables.put(ExpressionConstants.VAR_PROJECTION, shadow, shadowDef); + variables.put(ExpressionConstants.VAR_RESOURCE, resource, resourceDef); + variables.put(ExpressionConstants.VAR_CONFIGURATION, configuration, configDef); + + if (affectedElementContext != null) { + variables.put(ExpressionConstants.VAR_OPERATION, affectedElementContext.getOperation().getValue(), String.class); + // We do not want to add delta to all expressions. The delta may be tricky. Is it focus delta? projection delta? Primary? Secondary? + // It is better to leave delta to be accessed from the model context. And in cases when it is clear which delta is meant + // (e.g. provisioning scripts) we can still add the delta explicitly. + } + } + + public static void addAssignmentPathVariables(AssignmentPathVariables assignmentPathVariables, ExpressionVariables expressionVariables, PrismContext prismContext) { + if (assignmentPathVariables != null) { + PrismContainerDefinition assignmentDef = assignmentPathVariables.getAssignmentDefinition(); + expressionVariables.put(ExpressionConstants.VAR_ASSIGNMENT, assignmentPathVariables.getMagicAssignment(), assignmentDef); + expressionVariables.put(ExpressionConstants.VAR_ASSIGNMENT_PATH, assignmentPathVariables.getAssignmentPath(), AssignmentPath.class); + expressionVariables.put(ExpressionConstants.VAR_IMMEDIATE_ASSIGNMENT, assignmentPathVariables.getImmediateAssignment(), assignmentDef); + expressionVariables.put(ExpressionConstants.VAR_THIS_ASSIGNMENT, assignmentPathVariables.getThisAssignment(), assignmentDef); + expressionVariables.put(ExpressionConstants.VAR_FOCUS_ASSIGNMENT, assignmentPathVariables.getFocusAssignment(), assignmentDef); + PrismObjectDefinition abstractRoleDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(AbstractRoleType.class); + expressionVariables.put(ExpressionConstants.VAR_IMMEDIATE_ROLE, (PrismObject) assignmentPathVariables.getImmediateRole(), abstractRoleDefinition); + } else { + // to avoid "no such variable" exceptions in boundary cases + // for null/empty paths we might consider creating empty AssignmentPathVariables objects to keep null/empty path distinction + expressionVariables.put(ExpressionConstants.VAR_ASSIGNMENT_PATH, null, AssignmentPath.class); + } + } + + public static PrismReferenceValue determineAuditTargetDeltaOps( + Collection> deltaOps, + PrismContext prismContext) { + if (deltaOps == null || deltaOps.isEmpty()) { + return null; + } + if (deltaOps.size() == 1) { + ObjectDeltaOperation deltaOp = deltaOps.iterator().next(); + return getAuditTarget(deltaOp.getObjectDelta(), prismContext); + } + for (ObjectDeltaOperation deltaOp: deltaOps) { + if (!ShadowType.class.isAssignableFrom(deltaOp.getObjectDelta().getObjectTypeClass())) { + return getAuditTarget(deltaOp.getObjectDelta(), prismContext); + } + } + // Several raw operations, all on shadows, no focus ... this should not happen + // But if it does we rather do not specify any target. We should not like to choose + // target randomly. That would be confusing. + return null; + } + + public static PrismReferenceValue determineAuditTarget(Collection> deltas, + PrismContext prismContext) { + if (deltas == null || deltas.isEmpty()) { + return null; + } + if (deltas.size() == 1) { + ObjectDelta delta = deltas.iterator().next(); + return getAuditTarget(delta, prismContext); + } + for (ObjectDelta delta: deltas) { + if (!ShadowType.class.isAssignableFrom(delta.getObjectTypeClass())) { + return getAuditTarget(delta, prismContext); + } + } + // Several raw operations, all on shadows, no focus ... this should not happen + // But if it does we rather do not specify any target. We should not like to choose + // target randomly. That would be confusing. + return null; + } + + private static PrismReferenceValue getAuditTarget(ObjectDelta delta, + PrismContext prismContext) { + PrismReferenceValue targetRef = prismContext.itemFactory().createReferenceValue(delta.getOid()); + targetRef.setTargetType(ObjectTypes.getObjectType(delta.getObjectTypeClass()).getTypeQName()); + if (delta.isAdd()) { + targetRef.setObject(delta.getObjectToAdd()); + } + return targetRef; + } + + public static List evaluateScript( + ScriptExpression scriptExpression, LensContext lensContext, ExpressionVariables variables, boolean useNew, String shortDesc, Task task, OperationResult parentResult) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + ExpressionEnvironment env = new ExpressionEnvironment<>(); + env.setLensContext(lensContext); + env.setCurrentResult(parentResult); + env.setCurrentTask(task); + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); + + try { + + return scriptExpression.evaluate(variables, ScriptExpressionReturnTypeType.SCALAR, useNew, shortDesc, task, parentResult); + + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); +// if (lensContext.getDebugListener() != null) { +// lensContext.getDebugListener().afterScriptEvaluation(lensContext, scriptExpression); +// } + } + } + + public static CriticalityType handleConnectorErrorCriticality(ResourceType resourceType, Throwable e, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { + CriticalityType criticality; + if (resourceType == null) { + RepoCommonUtils.throwException(e, result); + return CriticalityType.FATAL; // not reached + } else { + ErrorSelectorType errorSelector = ResourceTypeUtil.getConnectorErrorCriticality(resourceType); + if (e instanceof CommunicationException) { + // Network problem. Just continue evaluation. The error is recorded in the result. + // The consistency mechanism has (most likely) already done the best. + // We cannot do any better. + criticality = ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.PARTIAL); + } else if (e instanceof SchemaException || e instanceof PolicyViolationException) { + // This may be caused by a variety of causes. It may be multiple values in a single-valued attribute. + // But it may also be duplicate value or a problem of resource-side password policy. + // Treat this as partial error by default. This is partially motivated by compatibility with + // midPoint 3.8 and earlier. This may be reviewed in the future. + criticality = ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.PARTIAL); + } else { + criticality = ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.FATAL); + } + } + RepoCommonUtils.processErrorCriticality(resourceType, criticality, e, result); + return criticality; + } + + public static String generateRequestIdentifier() { + return UUID.randomUUID().toString(); + } + +} diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java index f9c95e88cf5..c0b3a9a08b6 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/expr/TestModelExpressions.java @@ -1,333 +1,335 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.expr; - -import static org.testng.AssertJUnit.assertEquals; - -import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.xml.namespace.QName; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.testng.annotations.BeforeSuite; -import org.testng.annotations.Test; -import org.xml.sax.SAXException; - -import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; -import com.evolveum.midpoint.model.impl.AbstractInternalModelIntegrationTest; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.util.PrismTestUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.MidPointPrismContextFactory; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.MidPointConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.internals.InternalCounters; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.test.util.TestUtil; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.PrettyPrinter; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; - -/** - * @author lazyman - * @author mederly - * @author semancik - */ -@ContextConfiguration(locations = { "classpath:ctx-model-test-main.xml" }) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -public class TestModelExpressions extends AbstractInternalModelIntegrationTest { - - private static final File TEST_DIR = new File("src/test/resources/expr"); - - private static final QName PROPERTY_NAME = new QName(SchemaConstants.NS_C, "foo"); - - private static final String CHEF_OID = "00000003-0000-0000-0000-000000000000"; - private static final String CHEESE_OID = "00000002-0000-0000-0000-000000000000"; - private static final String CHEESE_JR_OID = "00000002-0000-0000-0000-000000000001"; - private static final String ELAINE_OID = "00000001-0000-0000-0000-000000000000"; - private static final String LECHUCK_OID = "00000007-0000-0000-0000-000000000000"; - private static final String F0006_OID = "00000000-8888-6666-0000-100000000006"; - - @Autowired - private ScriptExpressionFactory scriptExpressionFactory; - @Autowired private ExpressionFactory expressionFactory; - - @Autowired - private TaskManager taskManager; - - private static final File TEST_EXPRESSIONS_OBJECTS_FILE = new File(TEST_DIR, "orgstruct.xml"); - - @BeforeSuite - public void setup() throws SchemaException, SAXException, IOException { - PrettyPrinter.setDefaultNamespacePrefix(MidPointConstants.NS_MIDPOINT_PUBLIC_PREFIX); - PrismTestUtil.resetPrismContext(MidPointPrismContextFactory.FACTORY); - } - - @Override - public void initSystem(Task initTask, OperationResult initResult) throws Exception { - super.initSystem(initTask, initResult); - - importObjectFromFile(TEST_EXPRESSIONS_OBJECTS_FILE); - } - - @Test - public void testHello() throws Exception { - assertExecuteScriptExpressionString(null, "Hello swashbuckler"); - } - - private ScriptExpressionEvaluatorType parseScriptType(String fileName) - throws SchemaException, IOException { - ScriptExpressionEvaluatorType expressionType = PrismTestUtil.parseAtomicValue( - new File(TEST_DIR, fileName), ScriptExpressionEvaluatorType.COMPLEX_TYPE); - return expressionType; - } - - @Test - public void testGetUserByOid() throws Exception { - // GIVEN - PrismObject chef = repositoryService.getObject( - UserType.class, CHEF_OID, null, getTestOperationResult()); - - ExpressionVariables variables = createVariables(ExpressionConstants.VAR_USER, chef, chef.getDefinition()); - - // WHEN, THEN - assertExecuteScriptExpressionString(variables, chef.asObjectable().getName().getOrig()); - } - - @Test - public void testGetManagersOids() throws Exception { - // GIVEN - Task task = getTestTask(); - OperationResult result = createOperationResult(); - String shortTestName = getTestNameShort(); - - PrismObject chef = repositoryService.getObject(UserType.class, CHEF_OID, null, result); - - ScriptExpressionEvaluatorType scriptType = parseScriptType("expression-" + shortTestName + ".xml"); - PrismPropertyDefinition outputDefinition = - getPrismContext().definitionFactory().createPropertyDefinition( - PROPERTY_NAME, DOMUtil.XSD_STRING); - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( - scriptType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), - expressionFactory, shortTestName, task, result); - ExpressionVariables variables = - createVariables(ExpressionConstants.VAR_USER, chef, chef.getDefinition()); - - // WHEN - List> scriptOutputs = - evaluate(scriptExpression, variables, false, shortTestName, null, result); - - // THEN - display("Script output", scriptOutputs); - assertEquals("Unexpected number of script outputs", 3, scriptOutputs.size()); - Set oids = new HashSet<>(); - oids.add(scriptOutputs.get(0).getValue()); - oids.add(scriptOutputs.get(1).getValue()); - oids.add(scriptOutputs.get(2).getValue()); - Set expectedOids = new HashSet<>( - Arrays.asList(CHEESE_OID, CHEESE_JR_OID, LECHUCK_OID)); - assertEquals("Unexpected script output", expectedOids, oids); - } - - /** - * MID-2887 - */ - @Test - public void testIsUniquePropertyValue() throws Exception { - // GIVEN - Task task = getTestTask(); - OperationResult result = task.getResult(); - String testName = getTestNameShort(); - - PrismObject chef = repositoryService.getObject(UserType.class, CHEF_OID, null, result); - - ScriptExpressionEvaluatorType scriptType = parseScriptType("expression-" + testName + ".xml"); - PrismPropertyDefinition outputDefinition = getPrismContext().definitionFactory().createPropertyDefinition(PROPERTY_NAME, DOMUtil.XSD_BOOLEAN); - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptType, outputDefinition, - MiscSchemaUtil.getExpressionProfile(), expressionFactory, testName, task, result); - - ExpressionVariables variables = createVariables( - ExpressionConstants.VAR_USER, chef, chef.getDefinition(), - ExpressionConstants.VAR_VALUE, "Scumm Bar Chef", String.class); - - // WHEN - List> scriptOutputs = evaluate(scriptExpression, variables, false, testName, null, result); - - // THEN - display("Script output", scriptOutputs); - assertEquals("Unexpected number of script outputs", 1, scriptOutputs.size()); - Boolean scriptOutput = scriptOutputs.get(0).getValue(); - assertEquals("Unexpected script output", Boolean.TRUE, scriptOutput); - } - - @Test - public void testGetOrgByName() throws Exception { - assertExecuteScriptExpressionString(null, F0006_OID); - } - - @Test - public void testGetLinkedShadowName() throws Exception { - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - - PrismObject user = getUser(USER_GUYBRUSH_OID); - ExpressionVariables variables = createVariables( - ExpressionConstants.VAR_USER, user, user.getDefinition()); - - assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); - - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); - } - - @Test - public void testGetLinkedShadowKindIntentUsername() throws Exception { - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - - PrismObject user = getUser(USER_GUYBRUSH_OID); - ExpressionVariables variables = createVariables( - ExpressionConstants.VAR_USER, user, user.getDefinition()); - - assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); - - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); - } - - @Test - public void testGetLinkedShadowKindIntentFullname() throws Exception { - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - - PrismObject user = getUser(USER_GUYBRUSH_OID); - ExpressionVariables variables = createVariables( - ExpressionConstants.VAR_USER, user, user.getDefinition()); - - assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_FULLNAME); - - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); - } - - @Test - public void testGetLinkedShadowNameRepo() throws Exception { - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - - PrismObject user = getUser(USER_GUYBRUSH_OID); - ExpressionVariables variables = createVariables( - ExpressionConstants.VAR_USER, user, user.getDefinition()); - - assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); - - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); - } - - @Test - public void testGetLinkedShadowKindIntentUsernameRepo() throws Exception { - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - - PrismObject user = getUser(USER_GUYBRUSH_OID); - ExpressionVariables variables = createVariables( - ExpressionConstants.VAR_USER, user, user.getDefinition()); - - assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); - - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); - } - - @Test - public void testGetLinkedShadowKindIntentFullnameRepo() throws Exception { - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - - PrismObject user = getUser(USER_GUYBRUSH_OID); - ExpressionVariables variables = createVariables( - ExpressionConstants.VAR_USER, user, user.getDefinition()); - - assertExecuteScriptExpressionString(variables, null); - - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); - } - - private void assertExecuteScriptExpressionString( - ExpressionVariables variables, String expectedOutput) - throws ConfigurationException, ExpressionEvaluationException, ObjectNotFoundException, - IOException, CommunicationException, SchemaException, SecurityViolationException { - String output = executeScriptExpressionString(variables); - assertEquals("Unexpected script output", expectedOutput, output); - } - - private String executeScriptExpressionString(ExpressionVariables variables) - throws SecurityViolationException, ExpressionEvaluationException, SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, IOException { - // GIVEN - Task task = createPlainTask("executeScriptExpressionString"); - OperationResult result = createOperationResult(); - String shortTestName = getTestNameShort(); - - ScriptExpressionEvaluatorType scriptType = parseScriptType("expression-" + shortTestName + ".xml"); - ItemDefinition outputDefinition = - getPrismContext().definitionFactory().createPropertyDefinition( - PROPERTY_NAME, DOMUtil.XSD_STRING); - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( - scriptType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), - expressionFactory, shortTestName, task, result); - if (variables == null) { - variables = new ExpressionVariables(); - } - - // WHEN - when(); - List> scriptOutputs = evaluate(scriptExpression, variables, false, shortTestName, null, result); - - // THEN - then(); - display("Script output", scriptOutputs); - result.computeStatus(); - TestUtil.assertSuccess(result); - - if (scriptOutputs.size() == 0) { - return null; - } - - assertEquals("Unexpected number of script outputs", 1, scriptOutputs.size()); - PrismPropertyValue scriptOutput = scriptOutputs.get(0); - if (scriptOutput == null) { - return null; - } - return scriptOutput.getValue(); - - } - - private List> evaluate(ScriptExpression scriptExpression, ExpressionVariables variables, boolean useNew, - String contextDescription, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - if (task == null) { - task = taskManager.createTaskInstance(); - } - try { - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - - return scriptExpression.evaluate(variables, null, useNew, contextDescription, task, result); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.expr; + +import static org.testng.AssertJUnit.assertEquals; + +import static com.evolveum.midpoint.prism.util.PrismTestUtil.getPrismContext; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; +import org.xml.sax.SAXException; + +import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; +import com.evolveum.midpoint.model.impl.AbstractInternalModelIntegrationTest; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.util.PrismTestUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.MidPointPrismContextFactory; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.MidPointConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.internals.InternalCounters; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.PrettyPrinter; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; + +/** + * @author lazyman + * @author mederly + * @author semancik + */ +@ContextConfiguration(locations = { "classpath:ctx-model-test-main.xml" }) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class TestModelExpressions extends AbstractInternalModelIntegrationTest { + + private static final File TEST_DIR = new File("src/test/resources/expr"); + + private static final QName PROPERTY_NAME = new QName(SchemaConstants.NS_C, "foo"); + + private static final String CHEF_OID = "00000003-0000-0000-0000-000000000000"; + private static final String CHEESE_OID = "00000002-0000-0000-0000-000000000000"; + private static final String CHEESE_JR_OID = "00000002-0000-0000-0000-000000000001"; + private static final String ELAINE_OID = "00000001-0000-0000-0000-000000000000"; + private static final String LECHUCK_OID = "00000007-0000-0000-0000-000000000000"; + private static final String F0006_OID = "00000000-8888-6666-0000-100000000006"; + + @Autowired + private ScriptExpressionFactory scriptExpressionFactory; + @Autowired private ExpressionFactory expressionFactory; + + @Autowired + private TaskManager taskManager; + + private static final File TEST_EXPRESSIONS_OBJECTS_FILE = new File(TEST_DIR, "orgstruct.xml"); + + @BeforeSuite + public void setup() throws SchemaException, SAXException, IOException { + PrettyPrinter.setDefaultNamespacePrefix(MidPointConstants.NS_MIDPOINT_PUBLIC_PREFIX); + PrismTestUtil.resetPrismContext(MidPointPrismContextFactory.FACTORY); + } + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + importObjectFromFile(TEST_EXPRESSIONS_OBJECTS_FILE); + } + + @Test + public void testHello() throws Exception { + assertExecuteScriptExpressionString(null, "Hello swashbuckler"); + } + + private ScriptExpressionEvaluatorType parseScriptType(String fileName) + throws SchemaException, IOException { + ScriptExpressionEvaluatorType expressionType = PrismTestUtil.parseAtomicValue( + new File(TEST_DIR, fileName), ScriptExpressionEvaluatorType.COMPLEX_TYPE); + return expressionType; + } + + @Test + public void testGetUserByOid() throws Exception { + // GIVEN + PrismObject chef = repositoryService.getObject( + UserType.class, CHEF_OID, null, getTestOperationResult()); + + ExpressionVariables variables = createVariables(ExpressionConstants.VAR_USER, chef, chef.getDefinition()); + + // WHEN, THEN + assertExecuteScriptExpressionString(variables, chef.asObjectable().getName().getOrig()); + } + + @Test + public void testGetManagersOids() throws Exception { + // GIVEN + Task task = getTestTask(); + OperationResult result = createOperationResult(); + String shortTestName = getTestNameShort(); + + PrismObject chef = repositoryService.getObject(UserType.class, CHEF_OID, null, result); + + ScriptExpressionEvaluatorType scriptType = parseScriptType("expression-" + shortTestName + ".xml"); + PrismPropertyDefinition outputDefinition = + getPrismContext().definitionFactory().createPropertyDefinition( + PROPERTY_NAME, DOMUtil.XSD_STRING); + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( + scriptType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), + expressionFactory, shortTestName, task, result); + ExpressionVariables variables = + createVariables(ExpressionConstants.VAR_USER, chef, chef.getDefinition()); + + // WHEN + List> scriptOutputs = + evaluate(scriptExpression, variables, false, shortTestName, null, result); + + // THEN + display("Script output", scriptOutputs); + assertEquals("Unexpected number of script outputs", 3, scriptOutputs.size()); + Set oids = new HashSet<>(); + oids.add(scriptOutputs.get(0).getValue()); + oids.add(scriptOutputs.get(1).getValue()); + oids.add(scriptOutputs.get(2).getValue()); + Set expectedOids = new HashSet<>( + Arrays.asList(CHEESE_OID, CHEESE_JR_OID, LECHUCK_OID)); + assertEquals("Unexpected script output", expectedOids, oids); + } + + /** + * MID-2887 + */ + @Test + public void testIsUniquePropertyValue() throws Exception { + // GIVEN + Task task = getTestTask(); + OperationResult result = task.getResult(); + String testName = getTestNameShort(); + + PrismObject chef = repositoryService.getObject(UserType.class, CHEF_OID, null, result); + + ScriptExpressionEvaluatorType scriptType = parseScriptType("expression-" + testName + ".xml"); + PrismPropertyDefinition outputDefinition = getPrismContext().definitionFactory().createPropertyDefinition(PROPERTY_NAME, DOMUtil.XSD_BOOLEAN); + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptType, outputDefinition, + MiscSchemaUtil.getExpressionProfile(), expressionFactory, testName, task, result); + + ExpressionVariables variables = createVariables( + ExpressionConstants.VAR_USER, chef, chef.getDefinition(), + ExpressionConstants.VAR_VALUE, "Scumm Bar Chef", String.class); + + // WHEN + List> scriptOutputs = evaluate(scriptExpression, variables, false, testName, null, result); + + // THEN + display("Script output", scriptOutputs); + assertEquals("Unexpected number of script outputs", 1, scriptOutputs.size()); + Boolean scriptOutput = scriptOutputs.get(0).getValue(); + assertEquals("Unexpected script output", Boolean.TRUE, scriptOutput); + } + + @Test + public void testGetOrgByName() throws Exception { + assertExecuteScriptExpressionString(null, F0006_OID); + } + + @Test + public void testGetLinkedShadowName() throws Exception { + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + + PrismObject user = getUser(USER_GUYBRUSH_OID); + ExpressionVariables variables = createVariables( + ExpressionConstants.VAR_USER, user, user.getDefinition()); + + assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); + + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); + } + + @Test + public void testGetLinkedShadowKindIntentUsername() throws Exception { + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + + PrismObject user = getUser(USER_GUYBRUSH_OID); + ExpressionVariables variables = createVariables( + ExpressionConstants.VAR_USER, user, user.getDefinition()); + + assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); + + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); + } + + @Test + public void testGetLinkedShadowKindIntentFullname() throws Exception { + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + + PrismObject user = getUser(USER_GUYBRUSH_OID); + ExpressionVariables variables = createVariables( + ExpressionConstants.VAR_USER, user, user.getDefinition()); + + assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_FULLNAME); + + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 1); + } + + @Test + public void testGetLinkedShadowNameRepo() throws Exception { + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + + PrismObject user = getUser(USER_GUYBRUSH_OID); + ExpressionVariables variables = createVariables( + ExpressionConstants.VAR_USER, user, user.getDefinition()); + + assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); + + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); + } + + @Test + public void testGetLinkedShadowKindIntentUsernameRepo() throws Exception { + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + + PrismObject user = getUser(USER_GUYBRUSH_OID); + ExpressionVariables variables = createVariables( + ExpressionConstants.VAR_USER, user, user.getDefinition()); + + assertExecuteScriptExpressionString(variables, ACCOUNT_GUYBRUSH_DUMMY_USERNAME); + + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); + } + + @Test + public void testGetLinkedShadowKindIntentFullnameRepo() throws Exception { + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + + PrismObject user = getUser(USER_GUYBRUSH_OID); + ExpressionVariables variables = createVariables( + ExpressionConstants.VAR_USER, user, user.getDefinition()); + + assertExecuteScriptExpressionString(variables, null); + + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); + } + + private void assertExecuteScriptExpressionString( + ExpressionVariables variables, String expectedOutput) + throws ConfigurationException, ExpressionEvaluationException, ObjectNotFoundException, + IOException, CommunicationException, SchemaException, SecurityViolationException { + String output = executeScriptExpressionString(variables); + assertEquals("Unexpected script output", expectedOutput, output); + } + + private String executeScriptExpressionString(ExpressionVariables variables) + throws SecurityViolationException, ExpressionEvaluationException, SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, IOException { + // GIVEN + Task task = createPlainTask("executeScriptExpressionString"); + OperationResult result = createOperationResult(); + String shortTestName = getTestNameShort(); + + ScriptExpressionEvaluatorType scriptType = parseScriptType("expression-" + shortTestName + ".xml"); + ItemDefinition outputDefinition = + getPrismContext().definitionFactory().createPropertyDefinition( + PROPERTY_NAME, DOMUtil.XSD_STRING); + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( + scriptType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), + expressionFactory, shortTestName, task, result); + if (variables == null) { + variables = new ExpressionVariables(); + } + + // WHEN + when(); + List> scriptOutputs = evaluate(scriptExpression, variables, false, shortTestName, null, result); + + // THEN + then(); + display("Script output", scriptOutputs); + result.computeStatus(); + TestUtil.assertSuccess(result); + + if (scriptOutputs.size() == 0) { + return null; + } + + assertEquals("Unexpected number of script outputs", 1, scriptOutputs.size()); + PrismPropertyValue scriptOutput = scriptOutputs.get(0); + if (scriptOutput == null) { + return null; + } + return scriptOutput.getValue(); + + } + + private List> evaluate(ScriptExpression scriptExpression, ExpressionVariables variables, boolean useNew, + String contextDescription, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + if (task == null) { + task = taskManager.createTaskInstance(); + } + try { + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + + return scriptExpression.evaluate(variables, null, useNew, contextDescription, task, result); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/orgstruct/TestOrgStruct.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/orgstruct/TestOrgStruct.java index 54b135b85bf..87ffed3f177 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/orgstruct/TestOrgStruct.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/orgstruct/TestOrgStruct.java @@ -1,1728 +1,1728 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.intest.orgstruct; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.AssertJUnit.assertEquals; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.ContextConfiguration; -import org.testng.AssertJUnit; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -import com.evolveum.icf.dummy.resource.DummyAccount; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismReferenceDefinition; -import com.evolveum.midpoint.prism.PrismReferenceValue; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.ReferenceDelta; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.util.PrismAsserts; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.internals.InternalCounters; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ObjectQueryUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.test.DummyResourceContoller; -import com.evolveum.midpoint.test.util.MidPointAsserts; -import com.evolveum.midpoint.test.util.TestUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -/** - * @author semancik - */ -@ContextConfiguration(locations = { "classpath:ctx-model-intest-test-main.xml" }) -@DirtiesContext(classMode = ClassMode.AFTER_CLASS) -@Listeners({ com.evolveum.midpoint.tools.testng.AlphabeticalMethodInterceptor.class }) -public class TestOrgStruct extends AbstractInitializedModelIntegrationTest { - - public static final File TEST_DIR = new File("src/test/resources/orgstruct"); - - // RED resource has STRONG mappings - protected static final File RESOURCE_DUMMY_ORGTARGET_FILE = new File(TEST_DIR, "resource-dummy-orgtarget.xml"); - protected static final String RESOURCE_DUMMY_ORGTARGET_OID = "89cb4c72-cd61-11e8-a21b-27cbf58a8c0e"; - protected static final String RESOURCE_DUMMY_ORGTARGET_NAME = "orgtarget"; - - public static final File ROLE_DEFENDER_FILE = new File(TEST_DIR, "role-defender.xml"); - public static final String ROLE_DEFENDER_OID = "12345111-1111-2222-1111-121212111567"; - - public static final File ROLE_META_DEFENDER_FILE = new File(TEST_DIR, "role-meta-defender.xml"); - public static final String ROLE_META_DEFENDER_OID = "12345111-1111-2222-1111-121212111568"; - - public static final File ROLE_OFFENDER_FILE = new File(TEST_DIR, "role-offender.xml"); - public static final String ROLE_OFFENDER_OID = "12345111-1111-2222-1111-121212111569"; - - public static final File ROLE_OFFENDER_ADMIN_FILE = new File(TEST_DIR, "role-offender-admin.xml"); - public static final String ROLE_OFFENDER_ADMIN_OID = "12345111-1111-2222-1111-121212111566"; - - public static final File ROLE_META_DEFENDER_ADMIN_FILE = new File(TEST_DIR, "role-meta-defender-admin.xml"); - public static final String ROLE_META_DEFENDER_ADMIN_OID = "12345111-1111-2222-1111-121212111565"; - - public static final File ROLE_END_PIRATE_FILE = new File(TEST_DIR, "role-end-pirate.xml"); - public static final String ROLE_END_PIRATE_OID = "67780b58-cd69-11e8-b664-dbc7b09e163e"; - - public static final File ORG_TEMP_FILE = new File(TEST_DIR, "org-temp.xml"); - public static final String ORG_TEMP_OID = "43214321-4311-0952-4762-854392584320"; - - public static final File ORG_FICTIONAL_FILE = new File(TEST_DIR, "org-fictional.xml"); - public static final String ORG_FICTIONAL_OID = "b5b179cc-03c7-11e5-9839-001e8c717e5b"; - - @Override - public void initSystem(Task initTask, OperationResult initResult) throws Exception { - super.initSystem(initTask, initResult); - addObject(ROLE_DEFENDER_FILE); - addObject(ROLE_META_DEFENDER_FILE); - addObject(ROLE_META_DEFENDER_ADMIN_FILE); - addObject(ROLE_OFFENDER_FILE); - addObject(ROLE_OFFENDER_ADMIN_FILE); - addObject(ROLE_END_PIRATE_FILE); - addObject(USER_HERMAN_FILE); - setDefaultUserTemplate(USER_TEMPLATE_ORG_ASSIGNMENT_OID); // used for tests 4xx - //DebugUtil.setDetailedDebugDump(true); - - initDummyResourcePirate(RESOURCE_DUMMY_ORGTARGET_NAME, - RESOURCE_DUMMY_ORGTARGET_FILE, RESOURCE_DUMMY_ORGTARGET_OID, initTask, initResult); - - } - - @Test - public void test010AddOrgStruct() throws Exception { - // Dummy, just to be overridden in subclasses - addOrgStruct(); - } - - protected void addOrgStruct() throws Exception { - // Dummy, just to be overridden in subclasses - } - - @Test - public void test051OrgStructSanity() throws Exception { - // WHEN - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test052RootOrgQuery() throws Exception { - // GIVEN - Task task = getTestTask(); - OperationResult result = task.getResult(); - - ObjectQuery query = ObjectQueryUtil.createRootOrgQuery(prismContext); - - // WHEN - List> rootOrgs = modelService.searchObjects(OrgType.class, query, null, task, result); - - // THEN - assertEquals("Unexpected number of root orgs", 2, rootOrgs.size()); - - // Post-condition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test100JackAssignOrgtarget() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // Precondition - assertNoDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME); - assertNoDummyAccount(RESOURCE_DUMMY_ORGTARGET_NAME, ACCOUNT_JACK_DUMMY_USERNAME); - - // WHEN - assignAccountToUser(USER_JACK_OID, RESOURCE_DUMMY_ORGTARGET_OID, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAccount(userJack, RESOURCE_DUMMY_ORGTARGET_OID); - - assertJackOrgtarget(null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Scumm bar org also acts as a role, assigning account on dummy resource. - */ - @Test - public void test101JackAssignScummBar() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // Precondition - assertNoDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME); - - // WHEN - assignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserOrg(userJack, ORG_SCUMM_BAR_OID); - - assertDefaultDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); - - assertJackOrgtarget(null, ORG_SCUMM_BAR_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test102JackUnassignScummBar() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - unassignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserNoOrg(userJack); - - assertJackOrgtarget(null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Assign jack to both functional and project orgstruct. - * Assign both orgs at the same time. - */ - @Test - public void test201JackAssignScummBarAndSaveElaine() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - Collection> modifications = new ArrayList<>(); - modifications.add(createAssignmentModification(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); - modifications.add(createAssignmentModification(ORG_SAVE_ELAINE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserOrg(userJack, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); - - assertJackOrgtarget(null, ORG_SCUMM_BAR_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Assign jack to functional orgstruct again. - */ - @Test - public void test202JackAssignMinistryOfOffense() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserOrg(userJack, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_OFFENSE_OID); - - assertJackOrgtarget(null, ORG_SCUMM_BAR_NAME, ORG_MINISTRY_OF_OFFENSE_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test207JackUnAssignScummBar() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - unassignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserOrg(userJack, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_OFFENSE_OID); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test208JackUnassignAll() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - when(); - unassignAllReplace(USER_JACK_OID, task, result); - - // THEN - then(); - assertSuccess(result); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserNoOrg(userJack); - - assertNoDummyAccount(RESOURCE_DUMMY_ORGTARGET_NAME, ACCOUNT_JACK_DUMMY_USERNAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - // besides Offense org assignment, we create also Defender role assignment (which indirectly creates Defense org assignment) - @Test - public void test210JackAssignMinistryOfOffenseMember() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - Collection> modifications = new ArrayList<>(); - modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); - modifications.add(createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, true)); - modifications.add(createAssignmentModification(RESOURCE_DUMMY_ORGTARGET_OID, ShadowKindType.ACCOUNT, null, true)); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test211JackAssignMinistryOfOffenseMinister() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test212JackUnassignMinistryOfOffenseMember() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - unassignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_DEFENSE_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test213JackUnassignMinistryOfOffenseManager() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - unassignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedNoOrg(userJack); - assertHasOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_DEFENSE_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test220JackAssignMinistryOfOffenseMemberAgain() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Assign jack to both functional and project orgstruct. - * Implemented to check org struct reconciliation in test223. - */ - @Test - public void test221JackAssignScummBarAndSaveElaine() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - Collection> modifications = new ArrayList<>(); - modifications.add(createAssignmentModification(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); - modifications.add(createAssignmentModification(ORG_SAVE_ELAINE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - - // WHEN - executeChanges(userDelta, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME, ORG_SCUMM_BAR_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test223JackChangeMinistryOfOffenseMemberToManager() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - assertThat(getUser(USER_JACK_OID)).isNotNull(); - - Collection> modifications = new ArrayList<>(); - - // this is now forbidden TODO 2020: do we want to keep it around? -// Long id = findAssignmentIdForTarget(jack, ORG_MINISTRY_OF_OFFENSE_OID); -// PrismReferenceDefinition referenceDefinition = getUserDefinition() -// .findItemDefinition( -// ItemPath.create(UserType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF), PrismReferenceDefinition.class); -// ReferenceDelta referenceDelta = new ReferenceDelta( -// ItemPath.create( -// new NameItemPathSegment(UserType.F_ASSIGNMENT), -// new IdItemPathSegment(id), -// new NameItemPathSegment(AssignmentType.F_TARGET_REF)), referenceDefinition, prismContext); -// PrismReferenceValue oldValue = new PrismReferenceValueImpl(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); -// PrismReferenceValue newValue = new PrismReferenceValueImpl(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); -// newValue.setRelation(SchemaConstants.ORG_MANAGER); -// -// referenceDelta.addValueToDelete(oldValue); -// referenceDelta.addValueToAdd(newValue); -// modifications.add(referenceDelta); - - modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, false)); - modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, true)); - - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - - // WHEN - executeChanges(userDelta, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertAssignedOrg(userJack, ORG_SCUMM_BAR_OID, null); - assertHasOrg(userJack, ORG_SCUMM_BAR_OID, null); - assertAssignedOrg(userJack, ORG_SAVE_ELAINE_OID, null); - assertHasOrg(userJack, ORG_SAVE_ELAINE_OID, null); - - assertJackOrgtarget(null, ORG_MINISTRY_OF_DEFENSE_NAME, ORG_SCUMM_BAR_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Recompute jack. Make sure nothing is changed. - * MID-3384 - */ - @Test - public void test230JackRecompute() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - - // WHEN - when(); - recomputeUser(USER_JACK_OID, task, result); - - // THEN - then(); - assertSuccess(result); - - assertRefs23x(); - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 2); - } - - /** - * Destroy parentOrgRef and roleMembershipRef in the repo. Then recompute. - * Make sure that the refs are fixed. - * MID-3384 - */ - @Test - public void test232JackDestroyRefsAndRecompute() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - clearUserOrgAndRoleRefs(USER_JACK_OID); - - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - rememberCounter(InternalCounters.CONNECTOR_OPERATION_COUNT); - - // WHEN - when(); - recomputeUser(USER_JACK_OID, ModelExecuteOptions.createReconcile(), task, result); - - // THEN - then(); - assertSuccess(result); - - assertRefs23x(); - - // Why so many operations? But this is a very special case. As long as we do not see significant - // increase of operation count in normal scenarios we are quite OK. - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 4); - assertCounterIncrement(InternalCounters.CONNECTOR_OPERATION_COUNT, 8); - } - - /** - * Destroy parentOrgRef and roleMembershipRef in the repo. Then light recompute. - * Make sure that the refs are fixed and that the resources were not touched. - * MID-3384 - */ - @Test - public void test234JackDestroyRefsAndLightRecompute() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - clearUserOrgAndRoleRefs(USER_JACK_OID); - - rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - rememberCounter(InternalCounters.CONNECTOR_OPERATION_COUNT); - - PartialProcessingOptionsType partialProcessing = new PartialProcessingOptionsType(); - partialProcessing.setInbound(PartialProcessingTypeType.SKIP); - partialProcessing.setObjectTemplateBeforeAssignments(PartialProcessingTypeType.SKIP); - partialProcessing.setObjectTemplateAfterAssignments(PartialProcessingTypeType.SKIP); - partialProcessing.setProjection(PartialProcessingTypeType.SKIP); - partialProcessing.setApprovals(PartialProcessingTypeType.SKIP); - ModelExecuteOptions options = ModelExecuteOptions.createPartialProcessing(partialProcessing); - options.setReconcileFocus(true); - - // WHEN - when(); - modelService.recompute(UserType.class, USER_JACK_OID, options, task, result); - - // THEN - then(); - assertSuccess(result); - - assertRefs23x(); - assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); - assertCounterIncrement(InternalCounters.CONNECTOR_OPERATION_COUNT, 0); - } - - private void assertRefs23x() throws Exception { - PrismObject userAfter = getUser(USER_JACK_OID); - display("User after", userAfter); - assertAssignedOrgs(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); - assertHasOrgs(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertAssignedOrg(userAfter, ORG_SCUMM_BAR_OID, null); - assertHasOrg(userAfter, ORG_SCUMM_BAR_OID, null); - assertAssignedOrg(userAfter, ORG_SAVE_ELAINE_OID, null); - assertHasOrg(userAfter, ORG_SAVE_ELAINE_OID, null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - private Long findAssignmentIdForTarget(PrismObject user, String targetOid) { - for (AssignmentType assignmentType : user.asObjectable().getAssignment()) { - if (assignmentType.getTargetRef() != null && targetOid.equals(assignmentType.getTargetRef().getOid())) { - return assignmentType.getId(); - } - } - throw new IllegalStateException("No assignment pointing to " + targetOid + " found"); - } - - @Test - public void test300JackUnassignAllOrgs() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - Collection> modifications = new ArrayList<>(); - modifications.add((createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, false))); - modifications.add((createAssignmentModification(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE, null, null, null, false))); - modifications.add((createAssignmentModification(ORG_SAVE_ELAINE_OID, OrgType.COMPLEX_TYPE, null, null, null, false))); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - - // WHEN - when(); - modelService.executeChanges(MiscSchemaUtil.createCollection(userDelta), null, task, result); - - // THEN - then(); - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedNoOrg(userJack); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Assign jack to functional orgstruct again. Make him both minister and member (for Defense org i.e. for that which he already has indirect assignment) - */ - @Test - public void test301JackAssignMinistryOfOffense() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER, task, result); - assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_DEFENSE_OID, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Conflict: removing the role assignment (that should remove org assignment), while keeping explicit org assignment present - */ - @Test - public void test305JackConflictZeroAndMinus() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - Collection> modifications = new ArrayList<>(); - modifications.add((createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, false))); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertUserOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Another conflict: adding the role assignment (that should add org assignment), while deleting explicit org assignment - */ - @Test - public void test307JackConflictPlusAndMinus() throws Exception { - executeConflictPlusAndMinus(); - } - - protected void executeConflictPlusAndMinus() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - Collection> modifications = new ArrayList<>(); - modifications.add((createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, true))); - modifications.add((createAssignmentModification(ORG_MINISTRY_OF_DEFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, false))); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - assertNotAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); - - assertHasOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - // preparation for test309 - // also tests that when removing indirectly assigned org, it disappears from parentOrgRef - @Test - public void test308JackUnassignRoleDefender() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - Collection> modifications = new ArrayList<>(); - modifications.add((createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, false))); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - assertNotAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); - assertNotAssignedRole(userJack, ROLE_DEFENDER_OID); - - assertHasOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); - assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * Retrying last kind of conflict: adding the role assignment (that should add org assignment), - * while deleting explicit org assignment - even it is not there! - *

- * So this time there is originally NO parentOrgRef to Ministry of Defense/null. - *

- * This situation is a kind of abnormal, but deleting non-present value is considered to be legal. - * So we should treat a situation like this well. - */ - @Test - public void test309JackConflictPlusAndMinusAgain() throws Exception { - executeConflictPlusAndMinus(); - } - - /** - * MID-3874 - */ - @Test - public void test310JackConflictParentOrgRefAndAssignmentsAddOrg() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject orgBefore = createObject(OrgType.class, "Cheaters"); - orgBefore.asObjectable().parentOrgRef(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE); - display("Org before"); - - try { - // WHEN - when(); - addObject(orgBefore, task, result); - - assertNotReached(); - } catch (PolicyViolationException e) { - // THEN - then(); - display("Expected exception", e); - assertFailure(result); - } - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - // TODO: modify org: add parentOrgRef, removeParentOrgRef - - /** - * Delete jack while he is still assigned. - */ - @Test - public void test349DeleteJack() throws Exception { - executeDeleteJack(); - } - - protected void executeDeleteJack() - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, - PolicyViolationException, SecurityViolationException { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - ObjectDelta userDelta = prismContext.deltaFactory().object().createDeleteDelta(UserType.class, USER_JACK_OID - ); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); - - try { - getUser(USER_JACK_OID); - AssertJUnit.fail("Jack survived!"); - } catch (ObjectNotFoundException e) { - // This is expected - } - } - - /** - * Add new user Jack with an assignments as an manager and also a member of ministry of offense. - */ - @Test - public void test350AddJackAsMinistryOfOffenseManager() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userJack = prismContext.parseObject(USER_JACK_FILE); - - AssignmentType assignmentType = new AssignmentType(); - ObjectReferenceType targetRef = new ObjectReferenceType(); - targetRef.setOid(ORG_MINISTRY_OF_OFFENSE_OID); - targetRef.setType(OrgType.COMPLEX_TYPE); - assignmentType.setTargetRef(targetRef); - userJack.asObjectable().getAssignment().add(assignmentType); - - assignmentType = new AssignmentType(); - targetRef = new ObjectReferenceType(); - targetRef.setOid(ORG_MINISTRY_OF_OFFENSE_OID); - targetRef.setType(OrgType.COMPLEX_TYPE); - targetRef.setRelation(SchemaConstants.ORG_MANAGER); - assignmentType.setTargetRef(targetRef); - userJack.asObjectable().getAssignment().add(assignmentType); - - Collection> deltas = MiscSchemaUtil.createCollection(userJack.createAddDelta()); - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - - assertManager(USER_JACK_OID, null, null, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); - assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test360ElaineAssignGovernor() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_ELAINE_OID, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER, task, result); - - // THEN - PrismObject userElaine = getUser(USER_ELAINE_OID); - display("User jack after", userElaine); - assertAssignedOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID); - assertHasOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID); - assertAssignedOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); - - assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); - assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); - assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_ELAINE_OID, null, null, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_ELAINE_OID, null, null, true, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test362ElaineAssignGovernmentMember() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_ELAINE_OID, ORG_GOVERNOR_OFFICE_OID, null, task, result); - - // THEN - PrismObject userElaine = getUser(USER_ELAINE_OID); - display("User jack after", userElaine); - assertAssignedOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID, ORG_GOVERNOR_OFFICE_OID); - assertHasOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID, ORG_GOVERNOR_OFFICE_OID); - assertAssignedOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); - assertAssignedOrg(userElaine, ORG_GOVERNOR_OFFICE_OID); - assertHasOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); - - assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); - assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); - assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_ELAINE_OID, null, null, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test365GuybrushAssignSwashbucklerMember() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_GUYBRUSH_OID, ORG_SWASHBUCKLER_SECTION_OID, null, task, result); - - // THEN - PrismObject userGuybrush = getUser(USER_GUYBRUSH_OID); - display("User jack after", userGuybrush); - assertAssignedOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); - assertHasOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); - assertAssignedOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); - assertHasOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); - - assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); - assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); - assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_ELAINE_OID, null, null, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, true, result); - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test368GuybrushAssignSwashbucklerManager() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_GUYBRUSH_OID, ORG_SWASHBUCKLER_SECTION_OID, SchemaConstants.ORG_MANAGER, task, result); - - // THEN - PrismObject userGuybrush = getUser(USER_GUYBRUSH_OID); - display("User jack after", userGuybrush); - assertAssignedOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, ORG_SWASHBUCKLER_SECTION_OID); - assertHasOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, ORG_SWASHBUCKLER_SECTION_OID); - assertAssignedOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); - assertAssignedOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, SchemaConstants.ORG_MANAGER); - assertHasOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); - assertHasOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, SchemaConstants.ORG_MANAGER); - - assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); - assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); - assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_ELAINE_OID, null, null, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, null, true, result); - assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test370BarbossaAssignOffenseMember() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - assignOrg(USER_BARBOSSA_OID, ORG_MINISTRY_OF_OFFENSE_OID, null, task, result); - - // THEN - PrismObject userBarbossa = getUser(USER_BARBOSSA_OID); - display("User jack after", userBarbossa); - assertAssignedOrgs(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); - assertAssignedOrg(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrg(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); - - assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); - assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); - assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_ELAINE_OID, null, null, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, null, true, result); - assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, false, result); - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, true, result); - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, true, result); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test372HermanAssignSwashbucklerMember() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - PrismObject userHerman = getUser(USER_HERMAN_OID); - assertHasNoOrg(userHerman); - - // WHEN - assignOrg(USER_HERMAN_OID, ORG_SWASHBUCKLER_SECTION_OID, null, task, result); - - // THEN - userHerman = getUser(USER_HERMAN_OID); - display("User jack after", userHerman); - assertAssignedOrgs(userHerman, ORG_SWASHBUCKLER_SECTION_OID); - assertHasOrgs(userHerman, ORG_SWASHBUCKLER_SECTION_OID); - assertAssignedOrg(userHerman, ORG_SWASHBUCKLER_SECTION_OID); - assertHasOrg(userHerman, ORG_SWASHBUCKLER_SECTION_OID); - - assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); - assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); - assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_ELAINE_OID, null, null, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); - assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); - assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, null, true, result); - assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, false, result); - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, true, result); - assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, true, result); - - assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, null, false, result); - assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, false, result); - assertManager(USER_HERMAN_OID, null, ORG_TYPE_PROJECT, false, result); - assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, null, true, result); - assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); - assertManager(USER_HERMAN_OID, null, ORG_TYPE_PROJECT, true, result); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test399DeleteJack() throws Exception { - executeDeleteJack(); - } - - /** - * Add new user Jack with an assignments as an manager and also a member of ministry of offense. - */ - @Test - public void test400AddJackWithOrgUnit() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - OrgType minOffense = getObject(OrgType.class, ORG_MINISTRY_OF_OFFENSE_OID).asObjectable(); - - PrismObject userJack = prismContext.parseObject(USER_JACK_FILE); - userJack.asObjectable().getOrganizationalUnit().add(minOffense.getName()); - - Collection> deltas = MiscSchemaUtil.createCollection(userJack.createAddDelta()); - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test402JackChangeMinistryOfOffenseMemberToManagerByAddingRemovingAssignment() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - assertThat(getUser(USER_JACK_OID)).isNotNull(); - - Collection> modifications = new ArrayList<>(); - - modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, false)); - modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, true)); - - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - // No assignment from object template. The object template mapping is normal. It will NOT be applied - // because there is primary delta. - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); // because of the modification - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test404JackChangeMinistryOfOffenseManagerToMemberByAddingRemovingAssignment() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - assertThat(getUser(USER_JACK_OID)).isNotNull(); - - Collection> modifications = new ArrayList<>(); - - modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, false)); - modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); - - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); // because of object template and the modification - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test409DeleteJack() throws Exception { - executeDeleteJack(); - } - - /** - * Add new user Jack with an assignments as an manager and also a member of ministry of offense. - * (copied from test400) - */ - @Test - public void test410AddJackWithOrgUnit() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - OrgType minOffense = getObject(OrgType.class, ORG_MINISTRY_OF_OFFENSE_OID).asObjectable(); - - PrismObject userJack = prismContext.parseObject(USER_JACK_FILE); - userJack.asObjectable().getOrganizationalUnit().add(minOffense.getName()); - - Collection> deltas = MiscSchemaUtil.createCollection(userJack.createAddDelta()); - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - // this test should generate a SchemaException (modifying targetRef in assignment should be prohibited) - @Test - public void test412JackChangeMinistryOfOffenseMemberToManagerByModifyingAssignment() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject jack = getUser(USER_JACK_OID); - Long id = findAssignmentIdForTarget(jack, ORG_MINISTRY_OF_OFFENSE_OID); - - Collection> modifications = new ArrayList<>(); - - PrismReferenceDefinition referenceDefinition = getUserDefinition() - .findItemDefinition( - ItemPath.create(UserType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF), PrismReferenceDefinition.class); - ReferenceDelta referenceDelta = prismContext.deltaFactory().reference().create( - ItemPath.create(UserType.F_ASSIGNMENT, id, AssignmentType.F_TARGET_REF), referenceDefinition); - PrismReferenceValue oldValue = itemFactory().createReferenceValue(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); - PrismReferenceValue newValue = itemFactory().createReferenceValue(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); - newValue.setRelation(SchemaConstants.ORG_MANAGER); - - referenceDelta.addValueToDelete(oldValue); - referenceDelta.addValueToAdd(newValue); - modifications.add(referenceDelta); - - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - try { - modelService.executeChanges(deltas, null, task, result); - AssertJUnit.fail("executeChanges should fail but it did not."); - } catch (SchemaException e) { - // ok! - } catch (Exception e) { - AssertJUnit.fail("executeChanges failed in the wrong way (expected SchemaException): " + e); - } - } - - // import temp org + assign to jack (preparation for the next test) - @Test - public void test420JackAssignTempOrg() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - addObject(ORG_TEMP_FILE); - - // WHEN - assignOrg(USER_JACK_OID, ORG_TEMP_OID, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_TEMP_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_TEMP_OID); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - // delete the org and then unassign it - @Test - public void test425JackUnassignDeletedOrg() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - deleteObject(OrgType.class, ORG_TEMP_OID, task, result); - - // WHEN - when(); - unassignOrg(USER_JACK_OID, ORG_TEMP_OID, task, result); - - // THEN - then(); - display("result", result); - result.computeStatus(); - TestUtil.assertSuccess(result, 1); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test430JackAssignMetaroleOffender() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userJackBefore = getUser(USER_JACK_OID); - display("User jack before", userJackBefore); - assertAssignedOrgs(userJackBefore, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJackBefore, ORG_MINISTRY_OF_OFFENSE_OID); - - Collection> modifications = new ArrayList<>(); - modifications.add(createAssignmentModification(ROLE_OFFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, true)); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test431JackAssignMetaroleOffenderAdmin() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - Collection> modifications = new ArrayList<>(); - modifications.add(createAssignmentModification(ROLE_OFFENDER_ADMIN_OID, RoleType.COMPLEX_TYPE, null, null, null, true)); - ObjectDelta userDelta = prismContext.deltaFactory().object() - .createModifyDelta(USER_JACK_OID, modifications, UserType.class); - Collection> deltas = MiscSchemaUtil.createCollection(userDelta); - - // WHEN - modelService.executeChanges(deltas, null, task, result); - - // THEN - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - MidPointAsserts.assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test437JackUnassignOffender() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userBefore = getUser(USER_JACK_OID); - display("user before", userBefore); - - // WHEN - unassignRole(USER_JACK_OID, ROLE_OFFENDER_OID, task, result); - - // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); - MidPointAsserts.assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test438JackUnassignOffenderAdmin() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userBefore = getUser(USER_JACK_OID); - display("user before", userBefore); - - // WHEN - unassignRole(USER_JACK_OID, ROLE_OFFENDER_ADMIN_OID, task, result); - - // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test439JackCleanup() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - modifyUserReplace(USER_JACK_OID, UserType.F_ORGANIZATIONAL_UNIT, task, result); - - // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertNoAssignments(userJack); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - // Now let's test working with assignments when there is an object template that prescribes an org assignment - // based on organizationalUnit property. - - /** - * MID-3545 - */ - @Test - public void test440JackModifyEmployeeTypeRolePirate() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userBefore = getUser(USER_JACK_OID); - display("user before", userBefore); - assertNoAssignments(userBefore); - - // WHEN - when(); - modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result, "ROLE:Pirate"); - - // THEN - then(); - PrismObject userAfter = getUser(USER_JACK_OID); - display("User after", userAfter); - assertAssignments(userAfter, 1); - assertAssignedRole(userAfter, ROLE_PIRATE_OID); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * MID-3545 - */ - @Test - public void test441JackModifyEmployeeTypeRoleCaptain() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userBefore = getUser(USER_JACK_OID); - display("user before", userBefore); - - // WHEN - modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result, "ROLE:Captain"); - - // THEN - PrismObject userAfter = getUser(USER_JACK_OID); - display("User after", userAfter); - assertAssignments(userAfter, 1); - assertAssignedRole(userAfter, ROLE_CAPTAIN_OID); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * MID-3545 - */ - @Test - public void test443JackModifyEmployeeTypeRoleNotExist() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userBefore = getUser(USER_JACK_OID); - display("user before", userBefore); - - // WHEN - modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result, "ROLE:TheRoleThatDoesNotExist"); - - // THEN - PrismObject userAfter = getUser(USER_JACK_OID); - display("User after", userAfter); - assertAssignments(userAfter, 1); - assertAssignedRole(userAfter, ROLE_EMPTY_OID); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * MID-3545 - */ - @Test - public void test449JackModifyEmployeeTypeNull() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - PrismObject userBefore = getUser(USER_JACK_OID); - display("user before", userBefore); - - // WHEN - modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result); - - // THEN - PrismObject userAfter = getUser(USER_JACK_OID); - display("User after", userAfter); - assertNoAssignments(userAfter); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test500JackEndPirate() throws Exception { - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // preconditions - PrismObject userBefore = getUser(USER_JACK_OID); - display("User before", userBefore); - assertNoAssignments(userBefore); - assertLinks(userBefore, 0); - - // WHEN - assignAccountToUser(USER_JACK_OID, RESOURCE_DUMMY_ORGTARGET_OID, null, task, result); - assignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); - assignRole(USER_JACK_OID, ROLE_END_PIRATE_OID, task, result); - - // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignments(userJack, 3); - - assertJackOrgtarget(USER_ELAINE_USERNAME, ORG_SCUMM_BAR_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - /** - * MID-4934 - */ - @Test - public void test510JackEndPirate() throws Exception { - login(USER_JACK_USERNAME); - - Task task = getTestTask(); - OperationResult result = task.getResult(); - - // WHEN - when(); - modifyUserChangePassword(USER_JACK_OID, "X.marks.the.SPOT", task, result); - - // THEN - then(); - result.computeStatus(); - TestUtil.assertSuccess(result); - - login(USER_ADMINISTRATOR_USERNAME); - - PrismObject userJack = getUser(USER_JACK_OID); - display("User jack after", userJack); - assertAssignments(userJack, 3); - - assertJackOrgtarget(USER_ELAINE_USERNAME, ORG_SCUMM_BAR_NAME); - - // Postcondition - assertMonkeyIslandOrgSanity(); - } - - @Test - public void test799DeleteJack() throws Exception { - login(USER_ADMINISTRATOR_USERNAME); - - executeDeleteJack(); - } - - // BEWARE, tests 800+ are executed in TestOrgStructMeta, so this class has to end with test799 and no jack present - // --------------------------------------------------------------------------------------------------------------- - - protected void assertUserOrg(PrismObject user, String... orgOids) throws Exception { - for (String orgOid : orgOids) { - assertAssignedOrg(user, orgOid); - assertHasOrg(user, orgOid); - } - assertHasOrgs(user, orgOids.length); - } - - protected void assertUserNoOrg(PrismObject user) throws Exception { - assertAssignedNoOrg(user); - assertHasNoOrg(user); - assertHasOrgs(user, 0); - - } - - private void assertManager(String userOid, String managerOid, String orgType, boolean allowSelf, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - PrismObject user = getUser(userOid); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(null, result)); - Collection managers = libraryMidpointFunctions.getManagers(user.asObjectable(), orgType, allowSelf); - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - if (managerOid == null) { - if (managers == null || managers.isEmpty()) { - return; - } else { - AssertJUnit.fail("Expected no manager for " + user + ", but got " + managers); - } - } else { - if (managers == null) { - AssertJUnit.fail("Expected manager for " + user + ", but got no manager"); - } - if (managers.size() != 1) { - AssertJUnit.fail("Expected one manager for " + user + ", but got: " + managers); - } else { - UserType manager = managers.iterator().next(); - if (manager.getOid().equals(managerOid)) { - return; - } else { - AssertJUnit.fail("Expected manager with OID " + managerOid + " for " + user + ", but got " + manager); - } - } - } - } - - private void assertJackOrgtarget(String expectedShip, String... expectedTitleValues) throws Exception { - DummyAccount account = assertDummyAccount(RESOURCE_DUMMY_ORGTARGET_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); - display("orgtarget account", account); - String shipAccountValue = account.getAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME); - assertEquals("Jack's ship is wrong", expectedShip, shipAccountValue); - Set titleAccountValues = account.getAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, String.class); - if (titleAccountValues == null && expectedTitleValues.length == 0) { - return; - } - PrismAsserts.assertEqualsCollectionUnordered("Jack's titles are wrong", titleAccountValues, expectedTitleValues); - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.intest.orgstruct; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.AssertJUnit.assertEquals; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.AssertJUnit; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +import com.evolveum.icf.dummy.resource.DummyAccount; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceDefinition; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.ReferenceDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.internals.InternalCounters; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectQueryUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.DummyResourceContoller; +import com.evolveum.midpoint.test.util.MidPointAsserts; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * @author semancik + */ +@ContextConfiguration(locations = { "classpath:ctx-model-intest-test-main.xml" }) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +@Listeners({ com.evolveum.midpoint.tools.testng.AlphabeticalMethodInterceptor.class }) +public class TestOrgStruct extends AbstractInitializedModelIntegrationTest { + + public static final File TEST_DIR = new File("src/test/resources/orgstruct"); + + // RED resource has STRONG mappings + protected static final File RESOURCE_DUMMY_ORGTARGET_FILE = new File(TEST_DIR, "resource-dummy-orgtarget.xml"); + protected static final String RESOURCE_DUMMY_ORGTARGET_OID = "89cb4c72-cd61-11e8-a21b-27cbf58a8c0e"; + protected static final String RESOURCE_DUMMY_ORGTARGET_NAME = "orgtarget"; + + public static final File ROLE_DEFENDER_FILE = new File(TEST_DIR, "role-defender.xml"); + public static final String ROLE_DEFENDER_OID = "12345111-1111-2222-1111-121212111567"; + + public static final File ROLE_META_DEFENDER_FILE = new File(TEST_DIR, "role-meta-defender.xml"); + public static final String ROLE_META_DEFENDER_OID = "12345111-1111-2222-1111-121212111568"; + + public static final File ROLE_OFFENDER_FILE = new File(TEST_DIR, "role-offender.xml"); + public static final String ROLE_OFFENDER_OID = "12345111-1111-2222-1111-121212111569"; + + public static final File ROLE_OFFENDER_ADMIN_FILE = new File(TEST_DIR, "role-offender-admin.xml"); + public static final String ROLE_OFFENDER_ADMIN_OID = "12345111-1111-2222-1111-121212111566"; + + public static final File ROLE_META_DEFENDER_ADMIN_FILE = new File(TEST_DIR, "role-meta-defender-admin.xml"); + public static final String ROLE_META_DEFENDER_ADMIN_OID = "12345111-1111-2222-1111-121212111565"; + + public static final File ROLE_END_PIRATE_FILE = new File(TEST_DIR, "role-end-pirate.xml"); + public static final String ROLE_END_PIRATE_OID = "67780b58-cd69-11e8-b664-dbc7b09e163e"; + + public static final File ORG_TEMP_FILE = new File(TEST_DIR, "org-temp.xml"); + public static final String ORG_TEMP_OID = "43214321-4311-0952-4762-854392584320"; + + public static final File ORG_FICTIONAL_FILE = new File(TEST_DIR, "org-fictional.xml"); + public static final String ORG_FICTIONAL_OID = "b5b179cc-03c7-11e5-9839-001e8c717e5b"; + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + addObject(ROLE_DEFENDER_FILE); + addObject(ROLE_META_DEFENDER_FILE); + addObject(ROLE_META_DEFENDER_ADMIN_FILE); + addObject(ROLE_OFFENDER_FILE); + addObject(ROLE_OFFENDER_ADMIN_FILE); + addObject(ROLE_END_PIRATE_FILE); + addObject(USER_HERMAN_FILE); + setDefaultUserTemplate(USER_TEMPLATE_ORG_ASSIGNMENT_OID); // used for tests 4xx + //DebugUtil.setDetailedDebugDump(true); + + initDummyResourcePirate(RESOURCE_DUMMY_ORGTARGET_NAME, + RESOURCE_DUMMY_ORGTARGET_FILE, RESOURCE_DUMMY_ORGTARGET_OID, initTask, initResult); + + } + + @Test + public void test010AddOrgStruct() throws Exception { + // Dummy, just to be overridden in subclasses + addOrgStruct(); + } + + protected void addOrgStruct() throws Exception { + // Dummy, just to be overridden in subclasses + } + + @Test + public void test051OrgStructSanity() throws Exception { + // WHEN + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test052RootOrgQuery() throws Exception { + // GIVEN + Task task = getTestTask(); + OperationResult result = task.getResult(); + + ObjectQuery query = ObjectQueryUtil.createRootOrgQuery(prismContext); + + // WHEN + List> rootOrgs = modelService.searchObjects(OrgType.class, query, null, task, result); + + // THEN + assertEquals("Unexpected number of root orgs", 2, rootOrgs.size()); + + // Post-condition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test100JackAssignOrgtarget() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // Precondition + assertNoDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME); + assertNoDummyAccount(RESOURCE_DUMMY_ORGTARGET_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + + // WHEN + assignAccountToUser(USER_JACK_OID, RESOURCE_DUMMY_ORGTARGET_OID, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAccount(userJack, RESOURCE_DUMMY_ORGTARGET_OID); + + assertJackOrgtarget(null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Scumm bar org also acts as a role, assigning account on dummy resource. + */ + @Test + public void test101JackAssignScummBar() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // Precondition + assertNoDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME); + + // WHEN + assignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserOrg(userJack, ORG_SCUMM_BAR_OID); + + assertDefaultDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + + assertJackOrgtarget(null, ORG_SCUMM_BAR_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test102JackUnassignScummBar() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + unassignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserNoOrg(userJack); + + assertJackOrgtarget(null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Assign jack to both functional and project orgstruct. + * Assign both orgs at the same time. + */ + @Test + public void test201JackAssignScummBarAndSaveElaine() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + Collection> modifications = new ArrayList<>(); + modifications.add(createAssignmentModification(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); + modifications.add(createAssignmentModification(ORG_SAVE_ELAINE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserOrg(userJack, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); + + assertJackOrgtarget(null, ORG_SCUMM_BAR_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Assign jack to functional orgstruct again. + */ + @Test + public void test202JackAssignMinistryOfOffense() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserOrg(userJack, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_OFFENSE_OID); + + assertJackOrgtarget(null, ORG_SCUMM_BAR_NAME, ORG_MINISTRY_OF_OFFENSE_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test207JackUnAssignScummBar() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + unassignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserOrg(userJack, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_OFFENSE_OID); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test208JackUnassignAll() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + when(); + unassignAllReplace(USER_JACK_OID, task, result); + + // THEN + then(); + assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserNoOrg(userJack); + + assertNoDummyAccount(RESOURCE_DUMMY_ORGTARGET_NAME, ACCOUNT_JACK_DUMMY_USERNAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + // besides Offense org assignment, we create also Defender role assignment (which indirectly creates Defense org assignment) + @Test + public void test210JackAssignMinistryOfOffenseMember() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + Collection> modifications = new ArrayList<>(); + modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); + modifications.add(createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, true)); + modifications.add(createAssignmentModification(RESOURCE_DUMMY_ORGTARGET_OID, ShadowKindType.ACCOUNT, null, true)); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test211JackAssignMinistryOfOffenseMinister() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test212JackUnassignMinistryOfOffenseMember() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + unassignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_DEFENSE_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test213JackUnassignMinistryOfOffenseManager() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + unassignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedNoOrg(userJack); + assertHasOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_DEFENSE_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test220JackAssignMinistryOfOffenseMemberAgain() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_OFFENSE_OID, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Assign jack to both functional and project orgstruct. + * Implemented to check org struct reconciliation in test223. + */ + @Test + public void test221JackAssignScummBarAndSaveElaine() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + Collection> modifications = new ArrayList<>(); + modifications.add(createAssignmentModification(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); + modifications.add(createAssignmentModification(ORG_SAVE_ELAINE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + + // WHEN + executeChanges(userDelta, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_OFFENSE_NAME, ORG_MINISTRY_OF_DEFENSE_NAME, ORG_SCUMM_BAR_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test223JackChangeMinistryOfOffenseMemberToManager() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + assertThat(getUser(USER_JACK_OID)).isNotNull(); + + Collection> modifications = new ArrayList<>(); + + // this is now forbidden TODO 2020: do we want to keep it around? +// Long id = findAssignmentIdForTarget(jack, ORG_MINISTRY_OF_OFFENSE_OID); +// PrismReferenceDefinition referenceDefinition = getUserDefinition() +// .findItemDefinition( +// ItemPath.create(UserType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF), PrismReferenceDefinition.class); +// ReferenceDelta referenceDelta = new ReferenceDelta( +// ItemPath.create( +// new NameItemPathSegment(UserType.F_ASSIGNMENT), +// new IdItemPathSegment(id), +// new NameItemPathSegment(AssignmentType.F_TARGET_REF)), referenceDefinition, prismContext); +// PrismReferenceValue oldValue = new PrismReferenceValueImpl(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); +// PrismReferenceValue newValue = new PrismReferenceValueImpl(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); +// newValue.setRelation(SchemaConstants.ORG_MANAGER); +// +// referenceDelta.addValueToDelete(oldValue); +// referenceDelta.addValueToAdd(newValue); +// modifications.add(referenceDelta); + + modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, false)); + modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, true)); + + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + + // WHEN + executeChanges(userDelta, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertAssignedOrg(userJack, ORG_SCUMM_BAR_OID, null); + assertHasOrg(userJack, ORG_SCUMM_BAR_OID, null); + assertAssignedOrg(userJack, ORG_SAVE_ELAINE_OID, null); + assertHasOrg(userJack, ORG_SAVE_ELAINE_OID, null); + + assertJackOrgtarget(null, ORG_MINISTRY_OF_DEFENSE_NAME, ORG_SCUMM_BAR_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Recompute jack. Make sure nothing is changed. + * MID-3384 + */ + @Test + public void test230JackRecompute() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + + // WHEN + when(); + recomputeUser(USER_JACK_OID, task, result); + + // THEN + then(); + assertSuccess(result); + + assertRefs23x(); + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 2); + } + + /** + * Destroy parentOrgRef and roleMembershipRef in the repo. Then recompute. + * Make sure that the refs are fixed. + * MID-3384 + */ + @Test + public void test232JackDestroyRefsAndRecompute() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + clearUserOrgAndRoleRefs(USER_JACK_OID); + + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + rememberCounter(InternalCounters.CONNECTOR_OPERATION_COUNT); + + // WHEN + when(); + recomputeUser(USER_JACK_OID, ModelExecuteOptions.createReconcile(), task, result); + + // THEN + then(); + assertSuccess(result); + + assertRefs23x(); + + // Why so many operations? But this is a very special case. As long as we do not see significant + // increase of operation count in normal scenarios we are quite OK. + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 4); + assertCounterIncrement(InternalCounters.CONNECTOR_OPERATION_COUNT, 8); + } + + /** + * Destroy parentOrgRef and roleMembershipRef in the repo. Then light recompute. + * Make sure that the refs are fixed and that the resources were not touched. + * MID-3384 + */ + @Test + public void test234JackDestroyRefsAndLightRecompute() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + clearUserOrgAndRoleRefs(USER_JACK_OID); + + rememberCounter(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); + rememberCounter(InternalCounters.CONNECTOR_OPERATION_COUNT); + + PartialProcessingOptionsType partialProcessing = new PartialProcessingOptionsType(); + partialProcessing.setInbound(PartialProcessingTypeType.SKIP); + partialProcessing.setObjectTemplateBeforeAssignments(PartialProcessingTypeType.SKIP); + partialProcessing.setObjectTemplateAfterAssignments(PartialProcessingTypeType.SKIP); + partialProcessing.setProjection(PartialProcessingTypeType.SKIP); + partialProcessing.setApprovals(PartialProcessingTypeType.SKIP); + ModelExecuteOptions options = ModelExecuteOptions.createPartialProcessing(partialProcessing); + options.setReconcileFocus(true); + + // WHEN + when(); + modelService.recompute(UserType.class, USER_JACK_OID, options, task, result); + + // THEN + then(); + assertSuccess(result); + + assertRefs23x(); + assertCounterIncrement(InternalCounters.SHADOW_FETCH_OPERATION_COUNT, 0); + assertCounterIncrement(InternalCounters.CONNECTOR_OPERATION_COUNT, 0); + } + + private void assertRefs23x() throws Exception { + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertAssignedOrgs(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID); + assertHasOrgs(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, ORG_SCUMM_BAR_OID, ORG_SAVE_ELAINE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userAfter, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertAssignedOrg(userAfter, ORG_SCUMM_BAR_OID, null); + assertHasOrg(userAfter, ORG_SCUMM_BAR_OID, null); + assertAssignedOrg(userAfter, ORG_SAVE_ELAINE_OID, null); + assertHasOrg(userAfter, ORG_SAVE_ELAINE_OID, null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + private Long findAssignmentIdForTarget(PrismObject user, String targetOid) { + for (AssignmentType assignmentType : user.asObjectable().getAssignment()) { + if (assignmentType.getTargetRef() != null && targetOid.equals(assignmentType.getTargetRef().getOid())) { + return assignmentType.getId(); + } + } + throw new IllegalStateException("No assignment pointing to " + targetOid + " found"); + } + + @Test + public void test300JackUnassignAllOrgs() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + Collection> modifications = new ArrayList<>(); + modifications.add((createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, false))); + modifications.add((createAssignmentModification(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE, null, null, null, false))); + modifications.add((createAssignmentModification(ORG_SAVE_ELAINE_OID, OrgType.COMPLEX_TYPE, null, null, null, false))); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + + // WHEN + when(); + modelService.executeChanges(MiscSchemaUtil.createCollection(userDelta), null, task, result); + + // THEN + then(); + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedNoOrg(userJack); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Assign jack to functional orgstruct again. Make him both minister and member (for Defense org i.e. for that which he already has indirect assignment) + */ + @Test + public void test301JackAssignMinistryOfOffense() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER, task, result); + assignOrg(USER_JACK_OID, ORG_MINISTRY_OF_DEFENSE_OID, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Conflict: removing the role assignment (that should remove org assignment), while keeping explicit org assignment present + */ + @Test + public void test305JackConflictZeroAndMinus() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + Collection> modifications = new ArrayList<>(); + modifications.add((createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, false))); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertUserOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Another conflict: adding the role assignment (that should add org assignment), while deleting explicit org assignment + */ + @Test + public void test307JackConflictPlusAndMinus() throws Exception { + executeConflictPlusAndMinus(); + } + + protected void executeConflictPlusAndMinus() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + Collection> modifications = new ArrayList<>(); + modifications.add((createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, true))); + modifications.add((createAssignmentModification(ORG_MINISTRY_OF_DEFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, false))); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + assertNotAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); + + assertHasOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + // preparation for test309 + // also tests that when removing indirectly assigned org, it disappears from parentOrgRef + @Test + public void test308JackUnassignRoleDefender() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + Collection> modifications = new ArrayList<>(); + modifications.add((createAssignmentModification(ROLE_DEFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, false))); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + assertNotAssignedOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, null); + assertNotAssignedRole(userJack, ROLE_DEFENDER_OID); + + assertHasOrgs(userJack, ORG_MINISTRY_OF_DEFENSE_OID); + assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * Retrying last kind of conflict: adding the role assignment (that should add org assignment), + * while deleting explicit org assignment - even it is not there! + *

+ * So this time there is originally NO parentOrgRef to Ministry of Defense/null. + *

+ * This situation is a kind of abnormal, but deleting non-present value is considered to be legal. + * So we should treat a situation like this well. + */ + @Test + public void test309JackConflictPlusAndMinusAgain() throws Exception { + executeConflictPlusAndMinus(); + } + + /** + * MID-3874 + */ + @Test + public void test310JackConflictParentOrgRefAndAssignmentsAddOrg() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject orgBefore = createObject(OrgType.class, "Cheaters"); + orgBefore.asObjectable().parentOrgRef(ORG_SCUMM_BAR_OID, OrgType.COMPLEX_TYPE); + display("Org before"); + + try { + // WHEN + when(); + addObject(orgBefore, task, result); + + assertNotReached(); + } catch (PolicyViolationException e) { + // THEN + then(); + display("Expected exception", e); + assertFailure(result); + } + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + // TODO: modify org: add parentOrgRef, removeParentOrgRef + + /** + * Delete jack while he is still assigned. + */ + @Test + public void test349DeleteJack() throws Exception { + executeDeleteJack(); + } + + protected void executeDeleteJack() + throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, ConfigurationException, + PolicyViolationException, SecurityViolationException { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + ObjectDelta userDelta = prismContext.deltaFactory().object().createDeleteDelta(UserType.class, USER_JACK_OID + ); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + try { + getUser(USER_JACK_OID); + AssertJUnit.fail("Jack survived!"); + } catch (ObjectNotFoundException e) { + // This is expected + } + } + + /** + * Add new user Jack with an assignments as an manager and also a member of ministry of offense. + */ + @Test + public void test350AddJackAsMinistryOfOffenseManager() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userJack = prismContext.parseObject(USER_JACK_FILE); + + AssignmentType assignmentType = new AssignmentType(); + ObjectReferenceType targetRef = new ObjectReferenceType(); + targetRef.setOid(ORG_MINISTRY_OF_OFFENSE_OID); + targetRef.setType(OrgType.COMPLEX_TYPE); + assignmentType.setTargetRef(targetRef); + userJack.asObjectable().getAssignment().add(assignmentType); + + assignmentType = new AssignmentType(); + targetRef = new ObjectReferenceType(); + targetRef.setOid(ORG_MINISTRY_OF_OFFENSE_OID); + targetRef.setType(OrgType.COMPLEX_TYPE); + targetRef.setRelation(SchemaConstants.ORG_MANAGER); + assignmentType.setTargetRef(targetRef); + userJack.asObjectable().getAssignment().add(assignmentType); + + Collection> deltas = MiscSchemaUtil.createCollection(userJack.createAddDelta()); + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_OFFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + + assertManager(USER_JACK_OID, null, null, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); + assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test360ElaineAssignGovernor() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_ELAINE_OID, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER, task, result); + + // THEN + PrismObject userElaine = getUser(USER_ELAINE_OID); + display("User jack after", userElaine); + assertAssignedOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID); + assertHasOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID); + assertAssignedOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); + + assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); + assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); + assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_ELAINE_OID, null, null, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_ELAINE_OID, null, null, true, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test362ElaineAssignGovernmentMember() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_ELAINE_OID, ORG_GOVERNOR_OFFICE_OID, null, task, result); + + // THEN + PrismObject userElaine = getUser(USER_ELAINE_OID); + display("User jack after", userElaine); + assertAssignedOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID, ORG_GOVERNOR_OFFICE_OID); + assertHasOrgs(userElaine, ORG_GOVERNOR_OFFICE_OID, ORG_GOVERNOR_OFFICE_OID); + assertAssignedOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); + assertAssignedOrg(userElaine, ORG_GOVERNOR_OFFICE_OID); + assertHasOrg(userElaine, ORG_GOVERNOR_OFFICE_OID, SchemaConstants.ORG_MANAGER); + + assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); + assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); + assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_ELAINE_OID, null, null, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test365GuybrushAssignSwashbucklerMember() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_GUYBRUSH_OID, ORG_SWASHBUCKLER_SECTION_OID, null, task, result); + + // THEN + PrismObject userGuybrush = getUser(USER_GUYBRUSH_OID); + display("User jack after", userGuybrush); + assertAssignedOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); + assertHasOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); + assertAssignedOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); + assertHasOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); + + assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); + assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); + assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_ELAINE_OID, null, null, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, true, result); + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test368GuybrushAssignSwashbucklerManager() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_GUYBRUSH_OID, ORG_SWASHBUCKLER_SECTION_OID, SchemaConstants.ORG_MANAGER, task, result); + + // THEN + PrismObject userGuybrush = getUser(USER_GUYBRUSH_OID); + display("User jack after", userGuybrush); + assertAssignedOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, ORG_SWASHBUCKLER_SECTION_OID); + assertHasOrgs(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, ORG_SWASHBUCKLER_SECTION_OID); + assertAssignedOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); + assertAssignedOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, SchemaConstants.ORG_MANAGER); + assertHasOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID); + assertHasOrg(userGuybrush, ORG_SWASHBUCKLER_SECTION_OID, SchemaConstants.ORG_MANAGER); + + assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); + assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); + assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_ELAINE_OID, null, null, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, null, true, result); + assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test370BarbossaAssignOffenseMember() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + assignOrg(USER_BARBOSSA_OID, ORG_MINISTRY_OF_OFFENSE_OID, null, task, result); + + // THEN + PrismObject userBarbossa = getUser(USER_BARBOSSA_OID); + display("User jack after", userBarbossa); + assertAssignedOrgs(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); + assertAssignedOrg(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrg(userBarbossa, ORG_MINISTRY_OF_OFFENSE_OID); + + assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); + assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); + assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_ELAINE_OID, null, null, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, null, true, result); + assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, false, result); + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, true, result); + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, true, result); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test372HermanAssignSwashbucklerMember() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + PrismObject userHerman = getUser(USER_HERMAN_OID); + assertHasNoOrg(userHerman); + + // WHEN + assignOrg(USER_HERMAN_OID, ORG_SWASHBUCKLER_SECTION_OID, null, task, result); + + // THEN + userHerman = getUser(USER_HERMAN_OID); + display("User jack after", userHerman); + assertAssignedOrgs(userHerman, ORG_SWASHBUCKLER_SECTION_OID); + assertHasOrgs(userHerman, ORG_SWASHBUCKLER_SECTION_OID); + assertAssignedOrg(userHerman, ORG_SWASHBUCKLER_SECTION_OID); + assertHasOrg(userHerman, ORG_SWASHBUCKLER_SECTION_OID); + + assertManager(USER_JACK_OID, USER_ELAINE_OID, null, false, result); + assertManager(USER_JACK_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_JACK_OID, USER_JACK_OID, null, true, result); + assertManager(USER_JACK_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_JACK_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_ELAINE_OID, null, null, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, null, true, result); + assertManager(USER_ELAINE_OID, USER_ELAINE_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_ELAINE_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, null, false, result); + assertManager(USER_GUYBRUSH_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, null, true, result); + assertManager(USER_GUYBRUSH_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_GUYBRUSH_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, false, result); + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, null, true, result); + assertManager(USER_BARBOSSA_OID, USER_JACK_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_BARBOSSA_OID, null, ORG_TYPE_PROJECT, true, result); + + assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, null, false, result); + assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, false, result); + assertManager(USER_HERMAN_OID, null, ORG_TYPE_PROJECT, false, result); + assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, null, true, result); + assertManager(USER_HERMAN_OID, USER_GUYBRUSH_OID, ORG_TYPE_FUNCTIONAL, true, result); + assertManager(USER_HERMAN_OID, null, ORG_TYPE_PROJECT, true, result); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test399DeleteJack() throws Exception { + executeDeleteJack(); + } + + /** + * Add new user Jack with an assignments as an manager and also a member of ministry of offense. + */ + @Test + public void test400AddJackWithOrgUnit() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + OrgType minOffense = getObject(OrgType.class, ORG_MINISTRY_OF_OFFENSE_OID).asObjectable(); + + PrismObject userJack = prismContext.parseObject(USER_JACK_FILE); + userJack.asObjectable().getOrganizationalUnit().add(minOffense.getName()); + + Collection> deltas = MiscSchemaUtil.createCollection(userJack.createAddDelta()); + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test402JackChangeMinistryOfOffenseMemberToManagerByAddingRemovingAssignment() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + assertThat(getUser(USER_JACK_OID)).isNotNull(); + + Collection> modifications = new ArrayList<>(); + + modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, false)); + modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, true)); + + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + // No assignment from object template. The object template mapping is normal. It will NOT be applied + // because there is primary delta. + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); // because of the modification + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, SchemaConstants.ORG_MANAGER); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test404JackChangeMinistryOfOffenseManagerToMemberByAddingRemovingAssignment() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + assertThat(getUser(USER_JACK_OID)).isNotNull(); + + Collection> modifications = new ArrayList<>(); + + modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, SchemaConstants.ORG_MANAGER, null, null, false)); + modifications.add(createAssignmentModification(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE, null, null, null, true)); + + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); // because of object template and the modification + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test409DeleteJack() throws Exception { + executeDeleteJack(); + } + + /** + * Add new user Jack with an assignments as an manager and also a member of ministry of offense. + * (copied from test400) + */ + @Test + public void test410AddJackWithOrgUnit() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + OrgType minOffense = getObject(OrgType.class, ORG_MINISTRY_OF_OFFENSE_OID).asObjectable(); + + PrismObject userJack = prismContext.parseObject(USER_JACK_FILE); + userJack.asObjectable().getOrganizationalUnit().add(minOffense.getName()); + + Collection> deltas = MiscSchemaUtil.createCollection(userJack.createAddDelta()); + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertAssignedOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + assertHasOrg(userJack, ORG_MINISTRY_OF_OFFENSE_OID, null); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + // this test should generate a SchemaException (modifying targetRef in assignment should be prohibited) + @Test + public void test412JackChangeMinistryOfOffenseMemberToManagerByModifyingAssignment() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject jack = getUser(USER_JACK_OID); + Long id = findAssignmentIdForTarget(jack, ORG_MINISTRY_OF_OFFENSE_OID); + + Collection> modifications = new ArrayList<>(); + + PrismReferenceDefinition referenceDefinition = getUserDefinition() + .findItemDefinition( + ItemPath.create(UserType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF), PrismReferenceDefinition.class); + ReferenceDelta referenceDelta = prismContext.deltaFactory().reference().create( + ItemPath.create(UserType.F_ASSIGNMENT, id, AssignmentType.F_TARGET_REF), referenceDefinition); + PrismReferenceValue oldValue = itemFactory().createReferenceValue(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); + PrismReferenceValue newValue = itemFactory().createReferenceValue(ORG_MINISTRY_OF_OFFENSE_OID, OrgType.COMPLEX_TYPE); + newValue.setRelation(SchemaConstants.ORG_MANAGER); + + referenceDelta.addValueToDelete(oldValue); + referenceDelta.addValueToAdd(newValue); + modifications.add(referenceDelta); + + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + try { + modelService.executeChanges(deltas, null, task, result); + AssertJUnit.fail("executeChanges should fail but it did not."); + } catch (SchemaException e) { + // ok! + } catch (Exception e) { + AssertJUnit.fail("executeChanges failed in the wrong way (expected SchemaException): " + e); + } + } + + // import temp org + assign to jack (preparation for the next test) + @Test + public void test420JackAssignTempOrg() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + addObject(ORG_TEMP_FILE); + + // WHEN + assignOrg(USER_JACK_OID, ORG_TEMP_OID, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_TEMP_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_TEMP_OID); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + // delete the org and then unassign it + @Test + public void test425JackUnassignDeletedOrg() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + deleteObject(OrgType.class, ORG_TEMP_OID, task, result); + + // WHEN + when(); + unassignOrg(USER_JACK_OID, ORG_TEMP_OID, task, result); + + // THEN + then(); + display("result", result); + result.computeStatus(); + TestUtil.assertSuccess(result, 1); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test430JackAssignMetaroleOffender() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userJackBefore = getUser(USER_JACK_OID); + display("User jack before", userJackBefore); + assertAssignedOrgs(userJackBefore, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJackBefore, ORG_MINISTRY_OF_OFFENSE_OID); + + Collection> modifications = new ArrayList<>(); + modifications.add(createAssignmentModification(ROLE_OFFENDER_OID, RoleType.COMPLEX_TYPE, null, null, null, true)); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test431JackAssignMetaroleOffenderAdmin() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + Collection> modifications = new ArrayList<>(); + modifications.add(createAssignmentModification(ROLE_OFFENDER_ADMIN_OID, RoleType.COMPLEX_TYPE, null, null, null, true)); + ObjectDelta userDelta = prismContext.deltaFactory().object() + .createModifyDelta(USER_JACK_OID, modifications, UserType.class); + Collection> deltas = MiscSchemaUtil.createCollection(userDelta); + + // WHEN + modelService.executeChanges(deltas, null, task, result); + + // THEN + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + MidPointAsserts.assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test437JackUnassignOffender() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("user before", userBefore); + + // WHEN + unassignRole(USER_JACK_OID, ROLE_OFFENDER_OID, task, result); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID, ORG_MINISTRY_OF_DEFENSE_OID); + MidPointAsserts.assertHasOrg(userJack, ORG_MINISTRY_OF_DEFENSE_OID, SchemaConstants.ORG_MANAGER); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test438JackUnassignOffenderAdmin() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("user before", userBefore); + + // WHEN + unassignRole(USER_JACK_OID, ROLE_OFFENDER_ADMIN_OID, task, result); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignedOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + assertHasOrgs(userJack, ORG_MINISTRY_OF_OFFENSE_OID); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test439JackCleanup() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + modifyUserReplace(USER_JACK_OID, UserType.F_ORGANIZATIONAL_UNIT, task, result); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertNoAssignments(userJack); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + // Now let's test working with assignments when there is an object template that prescribes an org assignment + // based on organizationalUnit property. + + /** + * MID-3545 + */ + @Test + public void test440JackModifyEmployeeTypeRolePirate() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("user before", userBefore); + assertNoAssignments(userBefore); + + // WHEN + when(); + modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result, "ROLE:Pirate"); + + // THEN + then(); + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertAssignments(userAfter, 1); + assertAssignedRole(userAfter, ROLE_PIRATE_OID); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * MID-3545 + */ + @Test + public void test441JackModifyEmployeeTypeRoleCaptain() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("user before", userBefore); + + // WHEN + modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result, "ROLE:Captain"); + + // THEN + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertAssignments(userAfter, 1); + assertAssignedRole(userAfter, ROLE_CAPTAIN_OID); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * MID-3545 + */ + @Test + public void test443JackModifyEmployeeTypeRoleNotExist() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("user before", userBefore); + + // WHEN + modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result, "ROLE:TheRoleThatDoesNotExist"); + + // THEN + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertAssignments(userAfter, 1); + assertAssignedRole(userAfter, ROLE_EMPTY_OID); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * MID-3545 + */ + @Test + public void test449JackModifyEmployeeTypeNull() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_JACK_OID); + display("user before", userBefore); + + // WHEN + modifyUserReplace(USER_JACK_OID, UserType.F_SUBTYPE, task, result); + + // THEN + PrismObject userAfter = getUser(USER_JACK_OID); + display("User after", userAfter); + assertNoAssignments(userAfter); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test500JackEndPirate() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // preconditions + PrismObject userBefore = getUser(USER_JACK_OID); + display("User before", userBefore); + assertNoAssignments(userBefore); + assertLinks(userBefore, 0); + + // WHEN + assignAccountToUser(USER_JACK_OID, RESOURCE_DUMMY_ORGTARGET_OID, null, task, result); + assignOrg(USER_JACK_OID, ORG_SCUMM_BAR_OID, task, result); + assignRole(USER_JACK_OID, ROLE_END_PIRATE_OID, task, result); + + // THEN + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignments(userJack, 3); + + assertJackOrgtarget(USER_ELAINE_USERNAME, ORG_SCUMM_BAR_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + /** + * MID-4934 + */ + @Test + public void test510JackEndPirate() throws Exception { + login(USER_JACK_USERNAME); + + Task task = getTestTask(); + OperationResult result = task.getResult(); + + // WHEN + when(); + modifyUserChangePassword(USER_JACK_OID, "X.marks.the.SPOT", task, result); + + // THEN + then(); + result.computeStatus(); + TestUtil.assertSuccess(result); + + login(USER_ADMINISTRATOR_USERNAME); + + PrismObject userJack = getUser(USER_JACK_OID); + display("User jack after", userJack); + assertAssignments(userJack, 3); + + assertJackOrgtarget(USER_ELAINE_USERNAME, ORG_SCUMM_BAR_NAME); + + // Postcondition + assertMonkeyIslandOrgSanity(); + } + + @Test + public void test799DeleteJack() throws Exception { + login(USER_ADMINISTRATOR_USERNAME); + + executeDeleteJack(); + } + + // BEWARE, tests 800+ are executed in TestOrgStructMeta, so this class has to end with test799 and no jack present + // --------------------------------------------------------------------------------------------------------------- + + protected void assertUserOrg(PrismObject user, String... orgOids) throws Exception { + for (String orgOid : orgOids) { + assertAssignedOrg(user, orgOid); + assertHasOrg(user, orgOid); + } + assertHasOrgs(user, orgOids.length); + } + + protected void assertUserNoOrg(PrismObject user) throws Exception { + assertAssignedNoOrg(user); + assertHasNoOrg(user); + assertHasOrgs(user, 0); + + } + + private void assertManager(String userOid, String managerOid, String orgType, boolean allowSelf, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + PrismObject user = getUser(userOid); + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(null, result)); + Collection managers = libraryMidpointFunctions.getManagers(user.asObjectable(), orgType, allowSelf); + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + if (managerOid == null) { + if (managers == null || managers.isEmpty()) { + return; + } else { + AssertJUnit.fail("Expected no manager for " + user + ", but got " + managers); + } + } else { + if (managers == null) { + AssertJUnit.fail("Expected manager for " + user + ", but got no manager"); + } + if (managers.size() != 1) { + AssertJUnit.fail("Expected one manager for " + user + ", but got: " + managers); + } else { + UserType manager = managers.iterator().next(); + if (manager.getOid().equals(managerOid)) { + return; + } else { + AssertJUnit.fail("Expected manager with OID " + managerOid + " for " + user + ", but got " + manager); + } + } + } + } + + private void assertJackOrgtarget(String expectedShip, String... expectedTitleValues) throws Exception { + DummyAccount account = assertDummyAccount(RESOURCE_DUMMY_ORGTARGET_NAME, ACCOUNT_JACK_DUMMY_USERNAME, USER_JACK_FULL_NAME, true); + display("orgtarget account", account); + String shipAccountValue = account.getAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME); + assertEquals("Jack's ship is wrong", expectedShip, shipAccountValue); + Set titleAccountValues = account.getAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, String.class); + if (titleAccountValues == null && expectedTitleValues.length == 0) { + return; + } + PrismAsserts.assertEqualsCollectionUnordered("Jack's titles are wrong", titleAccountValues, expectedTitleValues); + } + +} diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/CustomTransport.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/CustomTransport.java index a2e0ad16e0c..48fb3bb3874 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/CustomTransport.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/CustomTransport.java @@ -1,212 +1,212 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.notifications.impl.api.transports; - -import com.evolveum.midpoint.notifications.impl.TransportRegistry; -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.notifications.api.NotificationManager; -import com.evolveum.midpoint.notifications.api.events.Event; -import com.evolveum.midpoint.notifications.api.transports.Message; -import com.evolveum.midpoint.notifications.api.transports.Transport; -import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -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.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -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.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.CustomTransportConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.xml.namespace.QName; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -/** - * TODO clean up - * @author mederly - */ -@Component -public class CustomTransport implements Transport { - - private static final Trace LOGGER = TraceManager.getTrace(CustomTransport.class); - - private static final String NAME = "custom"; - - private static final String DOT_CLASS = CustomTransport.class.getName() + "."; - - @Autowired - protected PrismContext prismContext; - - @Autowired - protected ExpressionFactory expressionFactory; - - @Autowired - @Qualifier("cacheRepositoryService") - private transient RepositoryService cacheRepositoryService; - - @Autowired - private NotificationManager notificationManager; - - @Autowired private TransportRegistry transportRegistry; - - @PostConstruct - public void init() { - transportRegistry.registerTransport(NAME, this); - } - - @Override - public void send(Message message, String transportName, Event event, Task task, OperationResult parentResult) { - - OperationResult result = parentResult.createSubresult(DOT_CLASS + "send"); - result.addArbitraryObjectCollectionAsParam("message recipient(s)", message.getTo()); - result.addParam("message subject", message.getSubject()); - - SystemConfigurationType systemConfiguration = NotificationFunctionsImpl - .getSystemConfiguration(cacheRepositoryService, result); - if (systemConfiguration == null || systemConfiguration.getNotificationConfiguration() == null) { - String msg = "No notifications are configured. Custom notification to " + message.getTo() + " will not be sent."; - LOGGER.warn(msg) ; - result.recordWarning(msg); - return; - } - - String configName = transportName.length() > NAME.length() ? transportName.substring(NAME.length() + 1) : null; // after "sms:" - CustomTransportConfigurationType configuration = systemConfiguration.getNotificationConfiguration().getCustomTransport().stream() - .filter(transportConfigurationType -> java.util.Objects.equals(configName, transportConfigurationType.getName())) - .findFirst().orElse(null); - - if (configuration == null) { - String msg = "Custom configuration '" + configName + "' not found. Custom notification to " + message.getTo() + " will not be sent."; - LOGGER.warn(msg) ; - result.recordWarning(msg); - return; - } - String logToFile = configuration.getLogToFile(); - if (logToFile != null) { - TransportUtil.logToFile(logToFile, TransportUtil.formatToFileNew(message, transportName), LOGGER); - } - - int optionsForFilteringRecipient = TransportUtil.optionsForFilteringRecipient(configuration); - - List allowedRecipientTo = new ArrayList(); - List forbiddenRecipientTo = new ArrayList(); - List allowedRecipientCc = new ArrayList(); - List forbiddenRecipientCc = new ArrayList(); - List allowedRecipientBcc = new ArrayList(); - List forbiddenRecipientBcc = new ArrayList(); - - String file = configuration.getRedirectToFile(); - if (optionsForFilteringRecipient != 0) { - TransportUtil.validateRecipient(allowedRecipientTo, forbiddenRecipientTo, message.getTo(), configuration, task, result, - expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); - TransportUtil.validateRecipient(allowedRecipientCc, forbiddenRecipientCc, message.getCc(), configuration, task, result, - expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); - TransportUtil.validateRecipient(allowedRecipientBcc, forbiddenRecipientBcc, message.getBcc(), configuration, task, result, - expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); - - if (file != null) { - if(!forbiddenRecipientTo.isEmpty() || !forbiddenRecipientCc.isEmpty() || !forbiddenRecipientBcc.isEmpty()) { - message.setTo(forbiddenRecipientTo); - message.setCc(forbiddenRecipientCc); - message.setBcc(forbiddenRecipientBcc); - writeToFile(message, file, result); - } - message.setTo(allowedRecipientTo); - message.setCc(allowedRecipientCc); - message.setBcc(allowedRecipientBcc); - } - - } else if (file != null) { - writeToFile(message, file, result); - return; - } - - try { - evaluateExpression(configuration.getExpression(), getDefaultVariables(message, event), - "custom transport expression", task, result); - LOGGER.trace("Custom transport expression execution finished"); - result.recordSuccess(); - } catch (Throwable t) { - String msg = "Couldn't execute custom transport expression"; - LoggingUtils.logException(LOGGER, msg, t); - result.recordFatalError(msg + ": " + t.getMessage(), t); - } - } - - // TODO deduplicate - private void writeToFile(Message message, String file, OperationResult result) { - try { - TransportUtil.appendToFile(file, formatToFile(message)); - result.recordSuccess(); - } catch (IOException e) { - LoggingUtils.logException(LOGGER, "Couldn't write to message redirect file {}", e, file); - result.recordPartialError("Couldn't write to message redirect file " + file, e); - } - } - - private String formatToFile(Message mailMessage) { - return "================ " + new Date() + " =======\n" + mailMessage.toString() + "\n\n"; - } - - // TODO deduplicate - private void evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_STRING); - - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); - } - - protected ExpressionVariables getDefaultVariables(Message message, Event event) throws UnsupportedEncodingException { - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_MESSAGE, message, Message.class); - variables.put(ExpressionConstants.VAR_EVENT, event, Event.class); - return variables; - } - - @Override - public String getDefaultRecipientAddress(UserType recipient) { - return "anything"; - } - - @Override - public String getName() { - return NAME; - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.notifications.impl.api.transports; + +import com.evolveum.midpoint.notifications.impl.TransportRegistry; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.notifications.api.NotificationManager; +import com.evolveum.midpoint.notifications.api.events.Event; +import com.evolveum.midpoint.notifications.api.transports.Message; +import com.evolveum.midpoint.notifications.api.transports.Transport; +import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +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.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +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.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.CustomTransportConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.xml.namespace.QName; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * TODO clean up + * @author mederly + */ +@Component +public class CustomTransport implements Transport { + + private static final Trace LOGGER = TraceManager.getTrace(CustomTransport.class); + + private static final String NAME = "custom"; + + private static final String DOT_CLASS = CustomTransport.class.getName() + "."; + + @Autowired + protected PrismContext prismContext; + + @Autowired + protected ExpressionFactory expressionFactory; + + @Autowired + @Qualifier("cacheRepositoryService") + private transient RepositoryService cacheRepositoryService; + + @Autowired + private NotificationManager notificationManager; + + @Autowired private TransportRegistry transportRegistry; + + @PostConstruct + public void init() { + transportRegistry.registerTransport(NAME, this); + } + + @Override + public void send(Message message, String transportName, Event event, Task task, OperationResult parentResult) { + + OperationResult result = parentResult.createSubresult(DOT_CLASS + "send"); + result.addArbitraryObjectCollectionAsParam("message recipient(s)", message.getTo()); + result.addParam("message subject", message.getSubject()); + + SystemConfigurationType systemConfiguration = NotificationFunctionsImpl + .getSystemConfiguration(cacheRepositoryService, result); + if (systemConfiguration == null || systemConfiguration.getNotificationConfiguration() == null) { + String msg = "No notifications are configured. Custom notification to " + message.getTo() + " will not be sent."; + LOGGER.warn(msg) ; + result.recordWarning(msg); + return; + } + + String configName = transportName.length() > NAME.length() ? transportName.substring(NAME.length() + 1) : null; // after "sms:" + CustomTransportConfigurationType configuration = systemConfiguration.getNotificationConfiguration().getCustomTransport().stream() + .filter(transportConfigurationType -> java.util.Objects.equals(configName, transportConfigurationType.getName())) + .findFirst().orElse(null); + + if (configuration == null) { + String msg = "Custom configuration '" + configName + "' not found. Custom notification to " + message.getTo() + " will not be sent."; + LOGGER.warn(msg) ; + result.recordWarning(msg); + return; + } + String logToFile = configuration.getLogToFile(); + if (logToFile != null) { + TransportUtil.logToFile(logToFile, TransportUtil.formatToFileNew(message, transportName), LOGGER); + } + + int optionsForFilteringRecipient = TransportUtil.optionsForFilteringRecipient(configuration); + + List allowedRecipientTo = new ArrayList(); + List forbiddenRecipientTo = new ArrayList(); + List allowedRecipientCc = new ArrayList(); + List forbiddenRecipientCc = new ArrayList(); + List allowedRecipientBcc = new ArrayList(); + List forbiddenRecipientBcc = new ArrayList(); + + String file = configuration.getRedirectToFile(); + if (optionsForFilteringRecipient != 0) { + TransportUtil.validateRecipient(allowedRecipientTo, forbiddenRecipientTo, message.getTo(), configuration, task, result, + expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); + TransportUtil.validateRecipient(allowedRecipientCc, forbiddenRecipientCc, message.getCc(), configuration, task, result, + expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); + TransportUtil.validateRecipient(allowedRecipientBcc, forbiddenRecipientBcc, message.getBcc(), configuration, task, result, + expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); + + if (file != null) { + if(!forbiddenRecipientTo.isEmpty() || !forbiddenRecipientCc.isEmpty() || !forbiddenRecipientBcc.isEmpty()) { + message.setTo(forbiddenRecipientTo); + message.setCc(forbiddenRecipientCc); + message.setBcc(forbiddenRecipientBcc); + writeToFile(message, file, result); + } + message.setTo(allowedRecipientTo); + message.setCc(allowedRecipientCc); + message.setBcc(allowedRecipientBcc); + } + + } else if (file != null) { + writeToFile(message, file, result); + return; + } + + try { + evaluateExpression(configuration.getExpression(), getDefaultVariables(message, event), + "custom transport expression", task, result); + LOGGER.trace("Custom transport expression execution finished"); + result.recordSuccess(); + } catch (Throwable t) { + String msg = "Couldn't execute custom transport expression"; + LoggingUtils.logException(LOGGER, msg, t); + result.recordFatalError(msg + ": " + t.getMessage(), t); + } + } + + // TODO deduplicate + private void writeToFile(Message message, String file, OperationResult result) { + try { + TransportUtil.appendToFile(file, formatToFile(message)); + result.recordSuccess(); + } catch (IOException e) { + LoggingUtils.logException(LOGGER, "Couldn't write to message redirect file {}", e, file); + result.recordPartialError("Couldn't write to message redirect file " + file, e); + } + } + + private String formatToFile(Message mailMessage) { + return "================ " + new Date() + " =======\n" + mailMessage.toString() + "\n\n"; + } + + // TODO deduplicate + private void evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_STRING); + + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); + } + + protected ExpressionVariables getDefaultVariables(Message message, Event event) throws UnsupportedEncodingException { + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_MESSAGE, message, Message.class); + variables.put(ExpressionConstants.VAR_EVENT, event, Event.class); + return variables; + } + + @Override + public String getDefaultRecipientAddress(UserType recipient) { + return "anything"; + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/SimpleSmsTransport.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/SimpleSmsTransport.java index 927d9b698bd..9357cf9f3e1 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/SimpleSmsTransport.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/api/transports/SimpleSmsTransport.java @@ -1,416 +1,415 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.notifications.impl.api.transports; - -import com.evolveum.midpoint.notifications.impl.TransportRegistry; -import com.evolveum.midpoint.notifications.impl.util.HttpUtil; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.crypto.Protector; -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.notifications.api.NotificationManager; -import com.evolveum.midpoint.notifications.api.events.Event; -import com.evolveum.midpoint.notifications.api.transports.Message; -import com.evolveum.midpoint.notifications.api.transports.Transport; -import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -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.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -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.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.*; - -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; -import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import javax.xml.namespace.QName; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.stream.Collectors; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; - -/** - * @author mederly - */ -@Component -public class SimpleSmsTransport implements Transport { - - private static final Trace LOGGER = TraceManager.getTrace(SimpleSmsTransport.class); - - private static final String NAME = "sms"; - - private static final String DOT_CLASS = SimpleSmsTransport.class.getName() + "."; - - @Autowired protected PrismContext prismContext; - @Autowired protected ExpressionFactory expressionFactory; - @Autowired private TransportRegistry transportRegistry; - @Autowired - @Qualifier("cacheRepositoryService") - private transient RepositoryService cacheRepositoryService; - @Autowired protected Protector protector; - - @PostConstruct - public void init() { - transportRegistry.registerTransport(NAME, this); - } - - @Override - public void send(Message message, String transportName, Event event, Task task, OperationResult parentResult) { - - OperationResult result = parentResult.createSubresult(DOT_CLASS + "send"); - result.addArbitraryObjectCollectionAsParam("message recipient(s)", message.getTo()); - result.addParam("message subject", message.getSubject()); - - SystemConfigurationType systemConfiguration = NotificationFunctionsImpl.getSystemConfiguration(cacheRepositoryService, result); - if (systemConfiguration == null || systemConfiguration.getNotificationConfiguration() == null) { - String msg = "No notifications are configured. SMS notification to " + message.getTo() + " will not be sent."; - LOGGER.warn(msg) ; - result.recordWarning(msg); - return; - } - - String smsConfigName = StringUtils.substringAfter(transportName, NAME + ":"); - SmsConfigurationType found = null; - for (SmsConfigurationType smsConfigurationType: systemConfiguration.getNotificationConfiguration().getSms()) { - if (StringUtils.isEmpty(smsConfigName) && smsConfigurationType.getName() == null - || StringUtils.isNotEmpty(smsConfigName) && smsConfigName.equals(smsConfigurationType.getName())) { - found = smsConfigurationType; - break; - } - } - - if (found == null) { - String msg = "SMS configuration '" + smsConfigName + "' not found. SMS notification to " + message.getTo() + " will not be sent."; - LOGGER.warn(msg) ; - result.recordWarning(msg); - return; - } - - SmsConfigurationType smsConfigurationType = found; - String logToFile = smsConfigurationType.getLogToFile(); - if (logToFile != null) { - TransportUtil.logToFile(logToFile, TransportUtil.formatToFileNew(message, transportName), LOGGER); - } - String file = smsConfigurationType.getRedirectToFile(); - int optionsForFilteringRecipient = TransportUtil.optionsForFilteringRecipient(smsConfigurationType); - - List allowedRecipientTo = new ArrayList(); - List forbiddenRecipientTo = new ArrayList(); - - if (optionsForFilteringRecipient != 0) { - TransportUtil.validateRecipient(allowedRecipientTo, forbiddenRecipientTo, message.getTo(), smsConfigurationType, task, result, - expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); - - if (file != null) { - if (!forbiddenRecipientTo.isEmpty()) { - message.setTo(forbiddenRecipientTo); - writeToFile(message, file, null, emptyList(), null, result); - } - message.setTo(allowedRecipientTo); - } - - } else if (file != null) { - writeToFile(message, file, null, emptyList(), null, result); - return; - } - - if (smsConfigurationType.getGateway().isEmpty()) { - String msg = "SMS gateway(s) are not defined, notification to " + message.getTo() + " will not be sent."; - LOGGER.warn(msg) ; - result.recordWarning(msg); - return; - } - - String from; - if (message.getFrom() != null) { - from = message.getFrom(); - } else if (smsConfigurationType.getDefaultFrom() != null) { - from = smsConfigurationType.getDefaultFrom(); - } else { - from = ""; - } - - if (message.getTo().isEmpty()) { - if(optionsForFilteringRecipient != 0) { - String msg = "After recipient validation there is no recipient to send the notification to."; - LOGGER.debug(msg) ; - result.recordSuccess(); - } else { - String msg = "There is no recipient to send the notification to."; - LOGGER.warn(msg) ; - result.recordWarning(msg); - } - return; - } - - List to = message.getTo(); - assert to.size() > 0; - - for (SmsGatewayConfigurationType smsGatewayConfigurationType : smsConfigurationType.getGateway()) { - OperationResult resultForGateway = result.createSubresult(DOT_CLASS + "send.forGateway"); - resultForGateway.addContext("gateway name", smsGatewayConfigurationType.getName()); - try { - ExpressionVariables variables = getDefaultVariables(from, to, message); - HttpMethodType method = defaultIfNull(smsGatewayConfigurationType.getMethod(), HttpMethodType.GET); - ExpressionType urlExpression = defaultIfNull(smsGatewayConfigurationType.getUrlExpression(), null); - String url = evaluateExpressionChecked(urlExpression, variables, "sms gateway request url", task, result); - String proxyHost = smsGatewayConfigurationType.getProxyHost(); - String proxyPort = smsGatewayConfigurationType.getProxyPort(); - LOGGER.debug("Sending SMS to URL {} via proxy host {} and port {} (method {})", url, proxyHost, proxyPort, method); - if (url == null) { - throw new IllegalArgumentException("No URL specified"); - } - List headersList = evaluateExpressionsChecked(smsGatewayConfigurationType.getHeadersExpression(), variables, - "sms gateway request headers", task, result); - LOGGER.debug("Using request headers:\n{}", headersList); - - String encoding = defaultIfNull(smsGatewayConfigurationType.getBodyEncoding(), StandardCharsets.ISO_8859_1.name()); - String body = evaluateExpressionChecked(smsGatewayConfigurationType.getBodyExpression(), variables, - "sms gateway request body", task, result); - LOGGER.debug("Using request body text (encoding: {}):\n{}", encoding, body); - - if (smsGatewayConfigurationType.getLogToFile() != null) { - TransportUtil.logToFile(smsGatewayConfigurationType.getLogToFile(), formatToFile(message, url, headersList, body), LOGGER); - } - if (smsGatewayConfigurationType.getRedirectToFile() != null) { - writeToFile(message, smsGatewayConfigurationType.getRedirectToFile(), url, headersList, body, resultForGateway); - result.computeStatus(); - return; - } else { - HttpClientBuilder builder = HttpClientBuilder.create(); - String username = smsGatewayConfigurationType.getUsername(); - ProtectedStringType password = smsGatewayConfigurationType.getPassword(); - CredentialsProvider provider = new BasicCredentialsProvider(); - if (username != null) { - String plainPassword = password != null ? protector.decryptString(password) : null; - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, plainPassword); - provider.setCredentials(AuthScope.ANY, credentials); - builder = builder.setDefaultCredentialsProvider(provider); - } - String proxyUsername = smsGatewayConfigurationType.getProxyUsername(); - ProtectedStringType proxyPassword = smsGatewayConfigurationType.getProxyPassword(); - if(StringUtils.isNotBlank(proxyHost)) { - HttpHost proxy; - if(StringUtils.isNotBlank(proxyPort) && isInteger(proxyPort)){ - int port = Integer.parseInt(proxyPort); - proxy = new HttpHost(proxyHost, port); - } else { - proxy = new HttpHost(proxyHost); - } - if(StringUtils.isNotBlank(proxyUsername)) { - String plainProxyPassword = proxyPassword != null ? protector.decryptString(proxyPassword) : null; - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(proxyUsername, plainProxyPassword); - provider.setCredentials(new AuthScope(proxy), credentials); - } - builder = builder.setDefaultCredentialsProvider(provider); - builder = builder.setProxy(proxy); - } - - HttpClient client = builder.build(); - HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client); - ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpUtil.toHttpMethod(method)); - setHeaders(request, headersList); - if (body != null) { - request.getBody().write(body.getBytes(encoding)); - } - ClientHttpResponse response = request.execute(); - LOGGER.debug("Result: " + response.getStatusCode() + "/" + response.getStatusText()); - if (response.getStatusCode().series() != HttpStatus.Series.SUCCESSFUL) { - throw new SystemException("SMS gateway communication failed: " + response.getStatusCode() + ": " + response.getStatusText()); - } - LOGGER.info("Message sent successfully to {} via gateway {}.", message.getTo(), smsGatewayConfigurationType.getName()); - resultForGateway.recordSuccess(); - result.recordSuccess(); - return; - } - } catch (Throwable t) { - String msg = "Couldn't send SMS to " + message.getTo() + " via " + smsGatewayConfigurationType.getName() + ", trying another gateway, if there is any"; - LoggingUtils.logException(LOGGER, msg, t); - resultForGateway.recordFatalError(msg, t); - } - } - LOGGER.warn("No more SMS gateways to try, notification to " + message.getTo() + " will not be sent.") ; - result.recordWarning("Notification to " + message.getTo() + " could not be sent."); - } - - private static boolean isInteger(String s) { - try { - Integer.parseInt(s); - } catch(NumberFormatException e) { - return false; - } catch(NullPointerException e) { - return false; - } - return true; - } - - private void setHeaders(ClientHttpRequest request, List headersList) { - for (String headerAsString : headersList) { - if (StringUtils.isEmpty(headerAsString)) { - continue; - } - int i = headerAsString.indexOf(':'); - if (i < 0) { - throw new IllegalArgumentException("Illegal header specification (expected was 'name: value' pair): " + headerAsString); - } - String headerName = headerAsString.substring(0, i); - int headerValueIndex; - if (i+1 == headerAsString.length() || headerAsString.charAt(i+1) != ' ') { - // let's be nice and treat well the wrong case (there's no space after ':') - headerValueIndex = i+1; - } else { - // correct case: ':' followed by space - headerValueIndex = i+2; - } - String headerValue = headerAsString.substring(headerValueIndex); - request.getHeaders().add(headerName, headerValue); - } - } - - private void writeToFile(Message message, String file, String url, List headers, String body, OperationResult result) { - try { - TransportUtil.appendToFile(file, formatToFile(message, url, headers, body)); - result.recordSuccess(); - } catch (IOException e) { - LoggingUtils.logException(LOGGER, "Couldn't write to SMS redirect file {}", e, file); - result.recordPartialError("Couldn't write to SMS redirect file " + file, e); - } - } - - private String formatToFile(Message mailMessage, String url, List headers, String body) { - return "================ " + new Date() + " ======= " + (url != null ? url : "") - + "\nHeaders:\n" + headers - + "\n\nBody:\n" + body - + "\n\nFor message:\n" + mailMessage.toString() + "\n\n"; - } - - private String evaluateExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) { - try { - return evaluateExpression(expressionType, expressionVariables, false, shortDesc, task, result).get(0); - } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); - result.recordFatalError("Couldn't evaluate " + shortDesc, e); - throw new SystemException(e); - } - } - - @NotNull - private List evaluateExpressionsChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, - @SuppressWarnings("SameParameterValue") String shortDesc, Task task, OperationResult result) { - try { - return evaluateExpression(expressionType, expressionVariables, true, shortDesc, task, result); - } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); - result.recordFatalError("Couldn't evaluate " + shortDesc, e); - throw new SystemException(e); - } - } - - // A little hack: for single-value cases we always return single-item list (even if the returned value is null) - @NotNull - private List evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, - boolean multipleValues, String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (expressionType == null) { - return multipleValues ? emptyList() : singletonList(null); - } - QName resultName = new QName(SchemaConstants.NS_C, "result"); - MutablePrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_STRING); - if (multipleValues) { - resultDef.setMaxOccurs(-1); - } - - Expression,PrismPropertyDefinition> expression = - expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, task, result); - - if (!multipleValues) { - if (exprResult.getZeroSet().size() > 1) { - throw new SystemException("Invalid number of return values (" + exprResult.getZeroSet().size() + "), expected at most 1."); - } else if (exprResult.getZeroSet().isEmpty()) { - return singletonList(null); - } else { - // single-valued response is treated below - } - } - return exprResult.getZeroSet().stream().map(ppv -> ppv.getValue()).collect(Collectors.toList()); - } - - protected ExpressionVariables getDefaultVariables(String from, List to, Message message) throws UnsupportedEncodingException { - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_FROM, from, String.class); - variables.put(ExpressionConstants.VAR_ENCODED_FROM, URLEncoder.encode(from, "US-ASCII"), String.class); - variables.put(ExpressionConstants.VAR_TO, to.get(0), String.class); - variables.put(ExpressionConstants.VAR_TO_LIST, to, List.class); - List encodedTo = new ArrayList<>(); - for (String s : to) { - encodedTo.add(URLEncoder.encode(s, "US-ASCII")); - } - variables.put(ExpressionConstants.VAR_ENCODED_TO, encodedTo.get(0), String.class); - variables.put(ExpressionConstants.VAR_ENCODED_TO_LIST, encodedTo, List.class); - variables.put(ExpressionConstants.VAR_MESSAGE_TEXT, message.getBody(), String.class); - variables.put(ExpressionConstants.VAR_ENCODED_MESSAGE_TEXT, URLEncoder.encode(message.getBody(), "US-ASCII"), String.class); - variables.put(ExpressionConstants.VAR_MESSAGE, message, Message.class); - return variables; - } - - @Override - public String getDefaultRecipientAddress(UserType recipient) { - return recipient.getTelephoneNumber(); - } - - @Override - public String getName() { - return NAME; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.notifications.impl.api.transports; + +import com.evolveum.midpoint.notifications.impl.TransportRegistry; +import com.evolveum.midpoint.notifications.impl.util.HttpUtil; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.crypto.Protector; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.notifications.api.events.Event; +import com.evolveum.midpoint.notifications.api.transports.Message; +import com.evolveum.midpoint.notifications.api.transports.Transport; +import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +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.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +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.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.*; + +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.xml.namespace.QName; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +/** + * @author mederly + */ +@Component +public class SimpleSmsTransport implements Transport { + + private static final Trace LOGGER = TraceManager.getTrace(SimpleSmsTransport.class); + + private static final String NAME = "sms"; + + private static final String DOT_CLASS = SimpleSmsTransport.class.getName() + "."; + + @Autowired protected PrismContext prismContext; + @Autowired protected ExpressionFactory expressionFactory; + @Autowired private TransportRegistry transportRegistry; + @Autowired + @Qualifier("cacheRepositoryService") + private transient RepositoryService cacheRepositoryService; + @Autowired protected Protector protector; + + @PostConstruct + public void init() { + transportRegistry.registerTransport(NAME, this); + } + + @Override + public void send(Message message, String transportName, Event event, Task task, OperationResult parentResult) { + + OperationResult result = parentResult.createSubresult(DOT_CLASS + "send"); + result.addArbitraryObjectCollectionAsParam("message recipient(s)", message.getTo()); + result.addParam("message subject", message.getSubject()); + + SystemConfigurationType systemConfiguration = NotificationFunctionsImpl.getSystemConfiguration(cacheRepositoryService, result); + if (systemConfiguration == null || systemConfiguration.getNotificationConfiguration() == null) { + String msg = "No notifications are configured. SMS notification to " + message.getTo() + " will not be sent."; + LOGGER.warn(msg) ; + result.recordWarning(msg); + return; + } + + String smsConfigName = StringUtils.substringAfter(transportName, NAME + ":"); + SmsConfigurationType found = null; + for (SmsConfigurationType smsConfigurationType: systemConfiguration.getNotificationConfiguration().getSms()) { + if (StringUtils.isEmpty(smsConfigName) && smsConfigurationType.getName() == null + || StringUtils.isNotEmpty(smsConfigName) && smsConfigName.equals(smsConfigurationType.getName())) { + found = smsConfigurationType; + break; + } + } + + if (found == null) { + String msg = "SMS configuration '" + smsConfigName + "' not found. SMS notification to " + message.getTo() + " will not be sent."; + LOGGER.warn(msg) ; + result.recordWarning(msg); + return; + } + + SmsConfigurationType smsConfigurationType = found; + String logToFile = smsConfigurationType.getLogToFile(); + if (logToFile != null) { + TransportUtil.logToFile(logToFile, TransportUtil.formatToFileNew(message, transportName), LOGGER); + } + String file = smsConfigurationType.getRedirectToFile(); + int optionsForFilteringRecipient = TransportUtil.optionsForFilteringRecipient(smsConfigurationType); + + List allowedRecipientTo = new ArrayList(); + List forbiddenRecipientTo = new ArrayList(); + + if (optionsForFilteringRecipient != 0) { + TransportUtil.validateRecipient(allowedRecipientTo, forbiddenRecipientTo, message.getTo(), smsConfigurationType, task, result, + expressionFactory, MiscSchemaUtil.getExpressionProfile(), LOGGER); + + if (file != null) { + if (!forbiddenRecipientTo.isEmpty()) { + message.setTo(forbiddenRecipientTo); + writeToFile(message, file, null, emptyList(), null, result); + } + message.setTo(allowedRecipientTo); + } + + } else if (file != null) { + writeToFile(message, file, null, emptyList(), null, result); + return; + } + + if (smsConfigurationType.getGateway().isEmpty()) { + String msg = "SMS gateway(s) are not defined, notification to " + message.getTo() + " will not be sent."; + LOGGER.warn(msg) ; + result.recordWarning(msg); + return; + } + + String from; + if (message.getFrom() != null) { + from = message.getFrom(); + } else if (smsConfigurationType.getDefaultFrom() != null) { + from = smsConfigurationType.getDefaultFrom(); + } else { + from = ""; + } + + if (message.getTo().isEmpty()) { + if(optionsForFilteringRecipient != 0) { + String msg = "After recipient validation there is no recipient to send the notification to."; + LOGGER.debug(msg) ; + result.recordSuccess(); + } else { + String msg = "There is no recipient to send the notification to."; + LOGGER.warn(msg) ; + result.recordWarning(msg); + } + return; + } + + List to = message.getTo(); + assert to.size() > 0; + + for (SmsGatewayConfigurationType smsGatewayConfigurationType : smsConfigurationType.getGateway()) { + OperationResult resultForGateway = result.createSubresult(DOT_CLASS + "send.forGateway"); + resultForGateway.addContext("gateway name", smsGatewayConfigurationType.getName()); + try { + ExpressionVariables variables = getDefaultVariables(from, to, message); + HttpMethodType method = defaultIfNull(smsGatewayConfigurationType.getMethod(), HttpMethodType.GET); + ExpressionType urlExpression = defaultIfNull(smsGatewayConfigurationType.getUrlExpression(), null); + String url = evaluateExpressionChecked(urlExpression, variables, "sms gateway request url", task, result); + String proxyHost = smsGatewayConfigurationType.getProxyHost(); + String proxyPort = smsGatewayConfigurationType.getProxyPort(); + LOGGER.debug("Sending SMS to URL {} via proxy host {} and port {} (method {})", url, proxyHost, proxyPort, method); + if (url == null) { + throw new IllegalArgumentException("No URL specified"); + } + List headersList = evaluateExpressionsChecked(smsGatewayConfigurationType.getHeadersExpression(), variables, + "sms gateway request headers", task, result); + LOGGER.debug("Using request headers:\n{}", headersList); + + String encoding = defaultIfNull(smsGatewayConfigurationType.getBodyEncoding(), StandardCharsets.ISO_8859_1.name()); + String body = evaluateExpressionChecked(smsGatewayConfigurationType.getBodyExpression(), variables, + "sms gateway request body", task, result); + LOGGER.debug("Using request body text (encoding: {}):\n{}", encoding, body); + + if (smsGatewayConfigurationType.getLogToFile() != null) { + TransportUtil.logToFile(smsGatewayConfigurationType.getLogToFile(), formatToFile(message, url, headersList, body), LOGGER); + } + if (smsGatewayConfigurationType.getRedirectToFile() != null) { + writeToFile(message, smsGatewayConfigurationType.getRedirectToFile(), url, headersList, body, resultForGateway); + result.computeStatus(); + return; + } else { + HttpClientBuilder builder = HttpClientBuilder.create(); + String username = smsGatewayConfigurationType.getUsername(); + ProtectedStringType password = smsGatewayConfigurationType.getPassword(); + CredentialsProvider provider = new BasicCredentialsProvider(); + if (username != null) { + String plainPassword = password != null ? protector.decryptString(password) : null; + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, plainPassword); + provider.setCredentials(AuthScope.ANY, credentials); + builder = builder.setDefaultCredentialsProvider(provider); + } + String proxyUsername = smsGatewayConfigurationType.getProxyUsername(); + ProtectedStringType proxyPassword = smsGatewayConfigurationType.getProxyPassword(); + if(StringUtils.isNotBlank(proxyHost)) { + HttpHost proxy; + if(StringUtils.isNotBlank(proxyPort) && isInteger(proxyPort)){ + int port = Integer.parseInt(proxyPort); + proxy = new HttpHost(proxyHost, port); + } else { + proxy = new HttpHost(proxyHost); + } + if(StringUtils.isNotBlank(proxyUsername)) { + String plainProxyPassword = proxyPassword != null ? protector.decryptString(proxyPassword) : null; + UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(proxyUsername, plainProxyPassword); + provider.setCredentials(new AuthScope(proxy), credentials); + } + builder = builder.setDefaultCredentialsProvider(provider); + builder = builder.setProxy(proxy); + } + + HttpClient client = builder.build(); + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client); + ClientHttpRequest request = requestFactory.createRequest(new URI(url), HttpUtil.toHttpMethod(method)); + setHeaders(request, headersList); + if (body != null) { + request.getBody().write(body.getBytes(encoding)); + } + ClientHttpResponse response = request.execute(); + LOGGER.debug("Result: " + response.getStatusCode() + "/" + response.getStatusText()); + if (response.getStatusCode().series() != HttpStatus.Series.SUCCESSFUL) { + throw new SystemException("SMS gateway communication failed: " + response.getStatusCode() + ": " + response.getStatusText()); + } + LOGGER.info("Message sent successfully to {} via gateway {}.", message.getTo(), smsGatewayConfigurationType.getName()); + resultForGateway.recordSuccess(); + result.recordSuccess(); + return; + } + } catch (Throwable t) { + String msg = "Couldn't send SMS to " + message.getTo() + " via " + smsGatewayConfigurationType.getName() + ", trying another gateway, if there is any"; + LoggingUtils.logException(LOGGER, msg, t); + resultForGateway.recordFatalError(msg, t); + } + } + LOGGER.warn("No more SMS gateways to try, notification to " + message.getTo() + " will not be sent.") ; + result.recordWarning("Notification to " + message.getTo() + " could not be sent."); + } + + private static boolean isInteger(String s) { + try { + Integer.parseInt(s); + } catch(NumberFormatException e) { + return false; + } catch(NullPointerException e) { + return false; + } + return true; + } + + private void setHeaders(ClientHttpRequest request, List headersList) { + for (String headerAsString : headersList) { + if (StringUtils.isEmpty(headerAsString)) { + continue; + } + int i = headerAsString.indexOf(':'); + if (i < 0) { + throw new IllegalArgumentException("Illegal header specification (expected was 'name: value' pair): " + headerAsString); + } + String headerName = headerAsString.substring(0, i); + int headerValueIndex; + if (i+1 == headerAsString.length() || headerAsString.charAt(i+1) != ' ') { + // let's be nice and treat well the wrong case (there's no space after ':') + headerValueIndex = i+1; + } else { + // correct case: ':' followed by space + headerValueIndex = i+2; + } + String headerValue = headerAsString.substring(headerValueIndex); + request.getHeaders().add(headerName, headerValue); + } + } + + private void writeToFile(Message message, String file, String url, List headers, String body, OperationResult result) { + try { + TransportUtil.appendToFile(file, formatToFile(message, url, headers, body)); + result.recordSuccess(); + } catch (IOException e) { + LoggingUtils.logException(LOGGER, "Couldn't write to SMS redirect file {}", e, file); + result.recordPartialError("Couldn't write to SMS redirect file " + file, e); + } + } + + private String formatToFile(Message mailMessage, String url, List headers, String body) { + return "================ " + new Date() + " ======= " + (url != null ? url : "") + + "\nHeaders:\n" + headers + + "\n\nBody:\n" + body + + "\n\nFor message:\n" + mailMessage.toString() + "\n\n"; + } + + private String evaluateExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) { + try { + return evaluateExpression(expressionType, expressionVariables, false, shortDesc, task, result).get(0); + } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { + LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); + result.recordFatalError("Couldn't evaluate " + shortDesc, e); + throw new SystemException(e); + } + } + + @NotNull + private List evaluateExpressionsChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, + @SuppressWarnings("SameParameterValue") String shortDesc, Task task, OperationResult result) { + try { + return evaluateExpression(expressionType, expressionVariables, true, shortDesc, task, result); + } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { + LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", e, shortDesc, expressionType); + result.recordFatalError("Couldn't evaluate " + shortDesc, e); + throw new SystemException(e); + } + } + + // A little hack: for single-value cases we always return single-item list (even if the returned value is null) + @NotNull + private List evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, + boolean multipleValues, String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + if (expressionType == null) { + return multipleValues ? emptyList() : singletonList(null); + } + QName resultName = new QName(SchemaConstants.NS_C, "result"); + MutablePrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_STRING); + if (multipleValues) { + resultDef.setMaxOccurs(-1); + } + + Expression,PrismPropertyDefinition> expression = + expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, params, task, result); + + if (!multipleValues) { + if (exprResult.getZeroSet().size() > 1) { + throw new SystemException("Invalid number of return values (" + exprResult.getZeroSet().size() + "), expected at most 1."); + } else if (exprResult.getZeroSet().isEmpty()) { + return singletonList(null); + } else { + // single-valued response is treated below + } + } + return exprResult.getZeroSet().stream().map(ppv -> ppv.getValue()).collect(Collectors.toList()); + } + + protected ExpressionVariables getDefaultVariables(String from, List to, Message message) throws UnsupportedEncodingException { + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_FROM, from, String.class); + variables.put(ExpressionConstants.VAR_ENCODED_FROM, URLEncoder.encode(from, "US-ASCII"), String.class); + variables.put(ExpressionConstants.VAR_TO, to.get(0), String.class); + variables.put(ExpressionConstants.VAR_TO_LIST, to, List.class); + List encodedTo = new ArrayList<>(); + for (String s : to) { + encodedTo.add(URLEncoder.encode(s, "US-ASCII")); + } + variables.put(ExpressionConstants.VAR_ENCODED_TO, encodedTo.get(0), String.class); + variables.put(ExpressionConstants.VAR_ENCODED_TO_LIST, encodedTo, List.class); + variables.put(ExpressionConstants.VAR_MESSAGE_TEXT, message.getBody(), String.class); + variables.put(ExpressionConstants.VAR_ENCODED_MESSAGE_TEXT, URLEncoder.encode(message.getBody(), "US-ASCII"), String.class); + variables.put(ExpressionConstants.VAR_MESSAGE, message, Message.class); + return variables; + } + + @Override + public String getDefaultRecipientAddress(UserType recipient) { + return recipient.getTelephoneNumber(); + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/helpers/NotificationExpressionHelper.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/helpers/NotificationExpressionHelper.java index 219c2a0baec..94d53750c05 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/helpers/NotificationExpressionHelper.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/helpers/NotificationExpressionHelper.java @@ -1,185 +1,185 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.notifications.impl.helpers; - -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.notifications.api.NotificationFunctions; -import com.evolveum.midpoint.notifications.impl.events.BaseEventImpl; -import com.evolveum.midpoint.notifications.api.events.Event; -import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; -import com.evolveum.midpoint.notifications.impl.formatters.TextFormatter; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.VariablesMap; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.NotificationMessageAttachmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; -import org.jetbrains.annotations.Nullable; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.namespace.QName; -import java.util.*; - -@Component -public class NotificationExpressionHelper { - - private static final Trace LOGGER = TraceManager.getTrace(NotificationExpressionHelper.class); - - @Autowired private NotificationFunctionsImpl notificationsUtil; - @Autowired private PrismContext prismContext; - @Autowired private ExpressionFactory expressionFactory; - @Autowired private TextFormatter textFormatter; - @Autowired private SystemObjectCache systemObjectCache; - - // shortDesc = what is to be evaluated e.g. "event filter expression" - public boolean evaluateBooleanExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) { - - Throwable failReason; - try { - return evaluateBooleanExpression(expressionType, expressionVariables, shortDesc, task, result); - } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - failReason = e; - } - - LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", failReason, shortDesc, expressionType); - result.recordFatalError("Couldn't evaluate " + shortDesc, failReason); - throw new SystemException(failReason); - } - - public boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, - Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_BOOLEAN); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - - PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, task, result); - - Collection> exprResult = exprResultTriple.getZeroSet(); - if (exprResult.size() == 0) { - return false; - } else if (exprResult.size() > 1) { - throw new IllegalStateException("Filter expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); - } - Boolean boolResult = exprResult.iterator().next().getValue(); - return boolResult != null ? boolResult : false; - } - - public List evaluateExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) { - - Throwable failReason; - try { - return evaluateExpression(expressionType, expressionVariables, shortDesc, task, result); - } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - failReason = e; - } - - LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", failReason, shortDesc, expressionType); - result.recordFatalError("Couldn't evaluate " + shortDesc, failReason); - throw new SystemException(failReason); - } - - private List evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - MutablePrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_STRING); - resultDef.setMaxOccurs(-1); - - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, task, result); - - List retval = new ArrayList<>(); - for (PrismPropertyValue item : exprResult.getZeroSet()) { - retval.add(item.getValue()); - } - return retval; - } - - public List evaluateNotificationMessageAttachmentTypeExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) { - - Throwable failReason; - try { - return evaluateNotificationMessageAttachmentTypeExpression(expressionType, expressionVariables, shortDesc, task, result); - } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - failReason = e; - } - - LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", failReason, shortDesc, expressionType); - result.recordFatalError("Couldn't evaluate " + shortDesc, failReason); - throw new SystemException(failReason); - } - - public List evaluateNotificationMessageAttachmentTypeExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, - Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, NotificationMessageAttachmentType.COMPLEX_TYPE); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - - PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, task, result); - - Collection> exprResult = exprResultTriple.getZeroSet(); - if (exprResult.size() == 0) { - return null; - } - - List retval = new ArrayList<>(); - for (PrismPropertyValue item : exprResult) { - retval.add(item.getValue()); - } - return retval; - } - - public ExpressionVariables getDefaultVariables(Event event, OperationResult result) { - ExpressionVariables expressionVariables = new ExpressionVariables(); - VariablesMap variables = new VariablesMap(); - ((BaseEventImpl) event).createExpressionVariables(variables, result); - variables.put(ExpressionConstants.VAR_TEXT_FORMATTER, textFormatter, TextFormatter.class); - variables.put(ExpressionConstants.VAR_NOTIFICATION_FUNCTIONS, notificationsUtil, NotificationFunctions.class); - PrismObject systemConfiguration = getSystemConfiguration(result); - variables.put(ExpressionConstants.VAR_CONFIGURATION, systemConfiguration, systemConfiguration.getDefinition()); - expressionVariables.addVariableDefinitions(variables); - return expressionVariables; - } - - @Nullable - private PrismObject getSystemConfiguration(OperationResult result) { - try { - return systemObjectCache.getSystemConfiguration(result); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get system configuration", e); - return null; - } - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.notifications.impl.helpers; + +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.notifications.api.NotificationFunctions; +import com.evolveum.midpoint.notifications.impl.events.BaseEventImpl; +import com.evolveum.midpoint.notifications.api.events.Event; +import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; +import com.evolveum.midpoint.notifications.impl.formatters.TextFormatter; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.VariablesMap; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.NotificationMessageAttachmentType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemConfigurationType; +import org.jetbrains.annotations.Nullable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; +import java.util.*; + +@Component +public class NotificationExpressionHelper { + + private static final Trace LOGGER = TraceManager.getTrace(NotificationExpressionHelper.class); + + @Autowired private NotificationFunctionsImpl notificationsUtil; + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private TextFormatter textFormatter; + @Autowired private SystemObjectCache systemObjectCache; + + // shortDesc = what is to be evaluated e.g. "event filter expression" + public boolean evaluateBooleanExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) { + + Throwable failReason; + try { + return evaluateBooleanExpression(expressionType, expressionVariables, shortDesc, task, result); + } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { + failReason = e; + } + + LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", failReason, shortDesc, expressionType); + result.recordFatalError("Couldn't evaluate " + shortDesc, failReason); + throw new SystemException(failReason); + } + + public boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_BOOLEAN); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + + PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, params, task, result); + + Collection> exprResult = exprResultTriple.getZeroSet(); + if (exprResult.size() == 0) { + return false; + } else if (exprResult.size() > 1) { + throw new IllegalStateException("Filter expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); + } + Boolean boolResult = exprResult.iterator().next().getValue(); + return boolResult != null ? boolResult : false; + } + + public List evaluateExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) { + + Throwable failReason; + try { + return evaluateExpression(expressionType, expressionVariables, shortDesc, task, result); + } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { + failReason = e; + } + + LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", failReason, shortDesc, expressionType); + result.recordFatalError("Couldn't evaluate " + shortDesc, failReason); + throw new SystemException(failReason); + } + + private List evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + MutablePrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_STRING); + resultDef.setMaxOccurs(-1); + + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, params, task, result); + + List retval = new ArrayList<>(); + for (PrismPropertyValue item : exprResult.getZeroSet()) { + retval.add(item.getValue()); + } + return retval; + } + + public List evaluateNotificationMessageAttachmentTypeExpressionChecked(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) { + + Throwable failReason; + try { + return evaluateNotificationMessageAttachmentTypeExpression(expressionType, expressionVariables, shortDesc, task, result); + } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { + failReason = e; + } + + LoggingUtils.logException(LOGGER, "Couldn't evaluate {} {}", failReason, shortDesc, expressionType); + result.recordFatalError("Couldn't evaluate " + shortDesc, failReason); + throw new SystemException(failReason); + } + + public List evaluateNotificationMessageAttachmentTypeExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String shortDesc, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, NotificationMessageAttachmentType.COMPLEX_TYPE); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + + PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, params, task, result); + + Collection> exprResult = exprResultTriple.getZeroSet(); + if (exprResult.size() == 0) { + return null; + } + + List retval = new ArrayList<>(); + for (PrismPropertyValue item : exprResult) { + retval.add(item.getValue()); + } + return retval; + } + + public ExpressionVariables getDefaultVariables(Event event, OperationResult result) { + ExpressionVariables expressionVariables = new ExpressionVariables(); + VariablesMap variables = new VariablesMap(); + ((BaseEventImpl) event).createExpressionVariables(variables, result); + variables.put(ExpressionConstants.VAR_TEXT_FORMATTER, textFormatter, TextFormatter.class); + variables.put(ExpressionConstants.VAR_NOTIFICATION_FUNCTIONS, notificationsUtil, NotificationFunctions.class); + PrismObject systemConfiguration = getSystemConfiguration(result); + variables.put(ExpressionConstants.VAR_CONFIGURATION, systemConfiguration, systemConfiguration.getDefinition()); + expressionVariables.addVariableDefinitions(variables); + return expressionVariables; + } + + @Nullable + private PrismObject getSystemConfiguration(OperationResult result) { + try { + return systemObjectCache.getSystemConfiguration(result); + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get system configuration", e); + return null; + } + } +} diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/ConfirmationNotifier.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/ConfirmationNotifier.java index e96d5696027..9653594ec55 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/ConfirmationNotifier.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/ConfirmationNotifier.java @@ -1,83 +1,83 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.notifications.impl.notifiers; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.model.api.expr.MidpointFunctions; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.notifications.api.events.ModelEvent; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConfirmationNotifierType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.RegistrationConfirmationMethodType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; - -/** - * @author katkav - */ -@Component -public abstract class ConfirmationNotifier extends AbstractGeneralNotifier { - - private static final Trace LOGGER = TraceManager.getTrace(ConfirmationNotifier.class); - - @Autowired private MidpointFunctions midpointFunctions; - - @Override - public Class getEventType() { - return ModelEvent.class; - } - - public String getConfirmationLink(UserType userType) { - throw new UnsupportedOperationException("Please implement in concrete notifier"); - } - - String createConfirmationLink(UserType userType, N config, OperationResult result) { - - RegistrationConfirmationMethodType confirmationMethod = config.getConfirmationMethod(); - if (confirmationMethod == null) { - return null; - } - ExpressionEnvironment expressionEnv = new ExpressionEnvironment<>(); - expressionEnv.setCurrentResult(result); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(expressionEnv); - - try { - switch (confirmationMethod) { - case LINK: - return getConfirmationLink(userType); - case PIN: - throw new UnsupportedOperationException("PIN confirmation not supported yes"); - // return getNonce(userType); - default: - return null; - } - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - protected UserType getUser(ModelEvent event) { - //noinspection unchecked - PrismObject newUser = (PrismObject) event.getFocusContext().getObjectNew(); - return newUser.asObjectable(); - } - - @Override - protected Trace getLogger() { - return LOGGER; - } - - MidpointFunctions getMidpointFunctions() { - return midpointFunctions; - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.notifications.impl.notifiers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.model.api.expr.MidpointFunctions; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.notifications.api.events.ModelEvent; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ConfirmationNotifierType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RegistrationConfirmationMethodType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; + +/** + * @author katkav + */ +@Component +public abstract class ConfirmationNotifier extends AbstractGeneralNotifier { + + private static final Trace LOGGER = TraceManager.getTrace(ConfirmationNotifier.class); + + @Autowired private MidpointFunctions midpointFunctions; + + @Override + public Class getEventType() { + return ModelEvent.class; + } + + public String getConfirmationLink(UserType userType) { + throw new UnsupportedOperationException("Please implement in concrete notifier"); + } + + String createConfirmationLink(UserType userType, N config, OperationResult result) { + + RegistrationConfirmationMethodType confirmationMethod = config.getConfirmationMethod(); + if (confirmationMethod == null) { + return null; + } + ExpressionEnvironment expressionEnv = new ExpressionEnvironment<>(); + expressionEnv.setCurrentResult(result); + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(expressionEnv); + + try { + switch (confirmationMethod) { + case LINK: + return getConfirmationLink(userType); + case PIN: + throw new UnsupportedOperationException("PIN confirmation not supported yes"); + // return getNonce(userType); + default: + return null; + } + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + protected UserType getUser(ModelEvent event) { + //noinspection unchecked + PrismObject newUser = (PrismObject) event.getFocusContext().getObjectNew(); + return newUser.asObjectable(); + } + + @Override + protected Trace getLogger() { + return LOGGER; + } + + MidpointFunctions getMidpointFunctions() { + return midpointFunctions; + } +} diff --git a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/CustomNotifier.java b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/CustomNotifier.java index afa5428d95f..e739103efaa 100644 --- a/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/CustomNotifier.java +++ b/model/notifications-impl/src/main/java/com/evolveum/midpoint/notifications/impl/notifiers/CustomNotifier.java @@ -1,150 +1,150 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.notifications.impl.notifiers; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import javax.xml.namespace.QName; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.notifications.api.NotificationManager; -import com.evolveum.midpoint.notifications.api.events.Event; -import com.evolveum.midpoint.notifications.api.transports.Message; -import com.evolveum.midpoint.notifications.api.transports.Transport; -import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; -import com.evolveum.midpoint.notifications.impl.TransportRegistry; -import com.evolveum.midpoint.notifications.impl.api.transports.CustomTransport; -import com.evolveum.midpoint.notifications.impl.formatters.TextFormatter; -import com.evolveum.midpoint.notifications.impl.handlers.AggregatedEventHandler; -import com.evolveum.midpoint.notifications.impl.handlers.BaseHandler; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -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.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.CustomNotifierType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.NotificationMessageType; - -@Component -public class CustomNotifier extends BaseHandler { - - private static final Trace DEFAULT_LOGGER = TraceManager.getTrace(CustomNotifier.class); - - @Autowired protected NotificationManager notificationManager; - @Autowired protected NotificationFunctionsImpl notificationsUtil; - @Autowired protected TextFormatter textFormatter; - @Autowired protected AggregatedEventHandler aggregatedEventHandler; - @Autowired private CustomTransport customTransport; - @Autowired private TransportRegistry transportRegistry; - - @Override - public Class getEventType() { - return Event.class; - } - - @Override - public Class getEventHandlerConfigurationType() { - return CustomNotifierType.class; - } - - @Override - public boolean processEvent(Event event, CustomNotifierType configuration, - Task task, OperationResult parentResult) throws SchemaException { - - OperationResult result = parentResult.createMinorSubresult(CustomNotifier.class.getName() + ".processEvent"); - - logStart(getLogger(), event, configuration); - - boolean applies = aggregatedEventHandler.processEvent(event, configuration, task, result); - - if (applies) { - ExpressionVariables variables = getDefaultVariables(event, result); - - List transports = new ArrayList<>(configuration.getTransport()); - if (transports.isEmpty()) { - transports.add(customTransport.getName()); - } - - reportNotificationStart(event); - try { - for (String transportName : configuration.getTransport()) { - variables.put(ExpressionConstants.VAR_TRANSPORT_NAME, transportName, String.class); - Transport transport = transportRegistry.getTransport(transportName); - - Message message = getMessageFromExpression(configuration, variables, task, result); - if (message != null) { - getLogger().trace("Sending notification via transport {}:\n{}", transportName, message); - transport.send(message, transportName, event, task, result); - } else { - getLogger().debug("No message for transport {}, won't send anything", transportName); - } - } - } finally { - reportNotificationEnd(event, result); - } - } - logEnd(getLogger(), event, applies); - result.computeStatusIfUnknown(); - return true; // not-applicable notifiers do not stop processing of other notifiers - } - - protected Trace getLogger() { - return DEFAULT_LOGGER; // in case a subclass does not provide its own logger - } - - private Message getMessageFromExpression(CustomNotifierType config, ExpressionVariables variables, - Task task, OperationResult result) { - if (config.getExpression() == null) { - return null; - } - List messages; - try { - messages = evaluateExpression(config.getExpression(), variables, - "message expression", task, result); - } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - throw new SystemException("Couldn't evaluate custom notifier expression: " + e.getMessage(), e); - } - if (messages == null || messages.isEmpty()) { - return null; - } else if (messages.size() > 1) { - getLogger().warn("Custom notifier returned more than one message: {}", messages); - } - return messages.get(0) != null ? new Message(messages.get(0)) : null; - } - - @SuppressWarnings("SameParameterValue") - private List evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, - String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, - ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismPropertyDefinition resultDef = - prismContext.definitionFactory().createPropertyDefinition(resultName, NotificationMessageType.COMPLEX_TYPE); - - Expression,PrismPropertyDefinition> expression = - expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); - PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, task, result); - return exprResult.getZeroSet().stream().map(PrismPropertyValue::getValue).collect(Collectors.toList()); - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.notifications.impl.notifiers; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.xml.namespace.QName; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.notifications.api.NotificationManager; +import com.evolveum.midpoint.notifications.api.events.Event; +import com.evolveum.midpoint.notifications.api.transports.Message; +import com.evolveum.midpoint.notifications.api.transports.Transport; +import com.evolveum.midpoint.notifications.impl.NotificationFunctionsImpl; +import com.evolveum.midpoint.notifications.impl.TransportRegistry; +import com.evolveum.midpoint.notifications.impl.api.transports.CustomTransport; +import com.evolveum.midpoint.notifications.impl.formatters.TextFormatter; +import com.evolveum.midpoint.notifications.impl.handlers.AggregatedEventHandler; +import com.evolveum.midpoint.notifications.impl.handlers.BaseHandler; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +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.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.CustomNotifierType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.NotificationMessageType; + +@Component +public class CustomNotifier extends BaseHandler { + + private static final Trace DEFAULT_LOGGER = TraceManager.getTrace(CustomNotifier.class); + + @Autowired protected NotificationManager notificationManager; + @Autowired protected NotificationFunctionsImpl notificationsUtil; + @Autowired protected TextFormatter textFormatter; + @Autowired protected AggregatedEventHandler aggregatedEventHandler; + @Autowired private CustomTransport customTransport; + @Autowired private TransportRegistry transportRegistry; + + @Override + public Class getEventType() { + return Event.class; + } + + @Override + public Class getEventHandlerConfigurationType() { + return CustomNotifierType.class; + } + + @Override + public boolean processEvent(Event event, CustomNotifierType configuration, + Task task, OperationResult parentResult) throws SchemaException { + + OperationResult result = parentResult.createMinorSubresult(CustomNotifier.class.getName() + ".processEvent"); + + logStart(getLogger(), event, configuration); + + boolean applies = aggregatedEventHandler.processEvent(event, configuration, task, result); + + if (applies) { + ExpressionVariables variables = getDefaultVariables(event, result); + + List transports = new ArrayList<>(configuration.getTransport()); + if (transports.isEmpty()) { + transports.add(customTransport.getName()); + } + + reportNotificationStart(event); + try { + for (String transportName : configuration.getTransport()) { + variables.put(ExpressionConstants.VAR_TRANSPORT_NAME, transportName, String.class); + Transport transport = transportRegistry.getTransport(transportName); + + Message message = getMessageFromExpression(configuration, variables, task, result); + if (message != null) { + getLogger().trace("Sending notification via transport {}:\n{}", transportName, message); + transport.send(message, transportName, event, task, result); + } else { + getLogger().debug("No message for transport {}, won't send anything", transportName); + } + } + } finally { + reportNotificationEnd(event, result); + } + } + logEnd(getLogger(), event, applies); + result.computeStatusIfUnknown(); + return true; // not-applicable notifiers do not stop processing of other notifiers + } + + protected Trace getLogger() { + return DEFAULT_LOGGER; // in case a subclass does not provide its own logger + } + + private Message getMessageFromExpression(CustomNotifierType config, ExpressionVariables variables, + Task task, OperationResult result) { + if (config.getExpression() == null) { + return null; + } + List messages; + try { + messages = evaluateExpression(config.getExpression(), variables, + "message expression", task, result); + } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { + throw new SystemException("Couldn't evaluate custom notifier expression: " + e.getMessage(), e); + } + if (messages == null || messages.isEmpty()) { + return null; + } else if (messages.size() > 1) { + getLogger().warn("Custom notifier returned more than one message: {}", messages); + } + return messages.get(0) != null ? new Message(messages.get(0)) : null; + } + + @SuppressWarnings("SameParameterValue") + private List evaluateExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, + String shortDesc, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, + ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismPropertyDefinition resultDef = + prismContext.definitionFactory().createPropertyDefinition(resultName, NotificationMessageType.COMPLEX_TYPE); + + Expression,PrismPropertyDefinition> expression = + expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), shortDesc, task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, shortDesc, task); + PrismValueDeltaSetTriple> exprResult = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, params, task, result); + return exprResult.getZeroSet().stream().map(PrismPropertyValue::getValue).collect(Collectors.toList()); + } +} 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 b279240f953..807119e7f14 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 @@ -1,59 +1,59 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.api; - -import java.util.Collection; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.expression.VariablesMap; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.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.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; - -public interface ReportService { - - String PARAMETER_REPORT_SERVICE = "reportService"; - - PrismObject getReportDefinition(String reportOid, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; - - ObjectQuery parseQuery(PrismObject report, String query, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; - - Collection> searchObjects(ObjectQuery query, Collection> options, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; - - Collection> evaluateScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; - - Object evaluate(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; - - Collection evaluateAuditScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; - - // hack todo fixme - PrismContext getPrismContext(); - - boolean isAuthorizedToRunReport(PrismObject report, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, - ConfigurationException, SecurityViolationException; -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.api; + +import java.util.Collection; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.expression.VariablesMap; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.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.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; + +public interface ReportService { + + String PARAMETER_REPORT_SERVICE = "reportService"; + + PrismObject getReportDefinition(String reportOid, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; + + ObjectQuery parseQuery(PrismObject report, String query, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; + + Collection> searchObjects(ObjectQuery query, Collection> options, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; + + Collection> evaluateScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; + + Object evaluate(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; + + Collection evaluateAuditScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; + + // hack todo fixme + PrismContext getPrismContext(); + + boolean isAuthorizedToRunReport(PrismObject report, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException; +} diff --git a/model/report-impl/pom.xml b/model/report-impl/pom.xml index 9d27432ea9d..21631fb004c 100644 --- a/model/report-impl/pom.xml +++ b/model/report-impl/pom.xml @@ -1,314 +1,309 @@ - - - - - 4.0.0 - - - model - com.evolveum.midpoint.model - 4.1-SNAPSHOT - - - report-impl - jar - - midPoint Report - impl - - - - com.evolveum.midpoint.model - report-api - 4.1-SNAPSHOT - - - com.evolveum.midpoint.infra - util - 4.1-SNAPSHOT - - - com.evolveum.midpoint.infra - schema - 4.1-SNAPSHOT - - - com.evolveum.midpoint.infra - prism-api - 4.1-SNAPSHOT - - - com.evolveum.midpoint.infra - prism-impl - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.repo - audit-api - 4.1-SNAPSHOT - - - com.evolveum.midpoint.repo - task-api - 4.1-SNAPSHOT - - - com.evolveum.midpoint.repo - repo-common - 4.1-SNAPSHOT - - - com.evolveum.midpoint.repo - security-enforcer-api - 4.1-SNAPSHOT - - - com.evolveum.midpoint.infra - common - 4.1-SNAPSHOT - - - com.evolveum.midpoint.model - model-api - 4.1-SNAPSHOT - - - com.evolveum.midpoint.model - certification-api - 4.1-SNAPSHOT - - - com.evolveum.midpoint.model - model-common - 4.1-SNAPSHOT - - - com.evolveum.midpoint.model - model-impl - 4.1-SNAPSHOT - - - com.evolveum.midpoint.model - workflow-api - 4.1-SNAPSHOT - - - - org.jetbrains - annotations - - - commons-lang - commons-lang - - - org.apache.commons - commons-lang3 - - - commons-io - commons-io - - - commons-codec - commons-codec - - - commons-collections - commons-collections - - - - - net.sf.jasperreports - jasperreports - - - - commons-javaflow - commons-javaflow - runtime - - - - org.apache.cxf - cxf-core - - - - org.springframework - spring-context - - - org.springframework - spring-beans - - - javax.annotation - javax.annotation-api - - - javax.xml.ws - jaxws-api - - - javax.xml.soap - javax.xml.soap-api - - - - - - javax.ws.rs - javax.ws.rs-api - - - - - - com.j2html - j2html - 1.4.0 - - - - org.apache.cxf - cxf-rt-rs-client - - - - - com.evolveum.midpoint.repo - repo-cache - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.provisioning - provisioning-impl - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.repo - task-quartz-impl - 4.1-SNAPSHOT - test - - - com.evolveum.icf - dummy-resource - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.repo - audit-impl - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.repo - security-impl - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.repo - security-enforcer-impl - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.model - model-test - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.repo - repo-sql-impl - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint - midpoint-localization - ${project.version} - test - - - com.evolveum.midpoint.repo - repo-test-util - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.infra - test-util - test - - - org.testng - testng - test - - - com.evolveum.midpoint.tools - test-ng - test - - - org.springframework - spring-test - test - - - com.evolveum.midpoint.repo - system-init - 4.1-SNAPSHOT - test - - - com.evolveum.midpoint.repo - repo-sql-impl-test - 4.1-SNAPSHOT - test - - - org.springframework - spring-aspects - test - - - org.springframework - spring-aop - test - - - javax.servlet - servlet-api - test - - - com.googlecode.java-diff-utils - diffutils - 1.2.1 - test - - - - - - - - maven-failsafe-plugin - - - - + + + + + 4.0.0 + + + model + com.evolveum.midpoint.model + 4.1-SNAPSHOT + + + report-impl + jar + + midPoint Report - impl + + + + com.evolveum.midpoint.model + report-api + 4.1-SNAPSHOT + + + com.evolveum.midpoint.infra + util + 4.1-SNAPSHOT + + + com.evolveum.midpoint.infra + schema + 4.1-SNAPSHOT + + + com.evolveum.midpoint.infra + prism-api + 4.1-SNAPSHOT + + + com.evolveum.midpoint.infra + prism-impl + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.repo + audit-api + 4.1-SNAPSHOT + + + com.evolveum.midpoint.repo + task-api + 4.1-SNAPSHOT + + + com.evolveum.midpoint.repo + repo-common + 4.1-SNAPSHOT + + + com.evolveum.midpoint.repo + security-enforcer-api + 4.1-SNAPSHOT + + + com.evolveum.midpoint.infra + common + 4.1-SNAPSHOT + + + com.evolveum.midpoint.model + model-api + 4.1-SNAPSHOT + + + com.evolveum.midpoint.model + certification-api + 4.1-SNAPSHOT + + + com.evolveum.midpoint.model + model-common + 4.1-SNAPSHOT + + + com.evolveum.midpoint.model + workflow-api + 4.1-SNAPSHOT + + + + org.jetbrains + annotations + + + commons-lang + commons-lang + + + org.apache.commons + commons-lang3 + + + commons-io + commons-io + + + commons-codec + commons-codec + + + commons-collections + commons-collections + + + + + net.sf.jasperreports + jasperreports + + + + commons-javaflow + commons-javaflow + runtime + + + + org.apache.cxf + cxf-core + + + + org.springframework + spring-context + + + org.springframework + spring-beans + + + javax.annotation + javax.annotation-api + + + javax.xml.ws + jaxws-api + + + javax.xml.soap + javax.xml.soap-api + + + + + + javax.ws.rs + javax.ws.rs-api + + + + + + com.j2html + j2html + 1.4.0 + + + + org.apache.cxf + cxf-rt-rs-client + + + + + com.evolveum.midpoint.repo + repo-cache + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.provisioning + provisioning-impl + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.repo + task-quartz-impl + 4.1-SNAPSHOT + test + + + com.evolveum.icf + dummy-resource + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.repo + audit-impl + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.repo + security-impl + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.repo + security-enforcer-impl + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.model + model-test + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.repo + repo-sql-impl + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint + midpoint-localization + ${project.version} + test + + + com.evolveum.midpoint.repo + repo-test-util + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.infra + test-util + test + + + org.testng + testng + test + + + com.evolveum.midpoint.tools + test-ng + test + + + org.springframework + spring-test + test + + + com.evolveum.midpoint.repo + system-init + 4.1-SNAPSHOT + test + + + com.evolveum.midpoint.repo + repo-sql-impl-test + 4.1-SNAPSHOT + test + + + org.springframework + spring-aspects + test + + + org.springframework + spring-aop + test + + + javax.servlet + servlet-api + test + + + com.googlecode.java-diff-utils + diffutils + 1.2.1 + test + + + + + + + + maven-failsafe-plugin + + + + diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointLocalQueryExecutor.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointLocalQueryExecutor.java index 0b51fcece27..b0b940c5889 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointLocalQueryExecutor.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointLocalQueryExecutor.java @@ -1,151 +1,149 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import net.sf.jasperreports.engine.JRDataSource; -import net.sf.jasperreports.engine.JRDataset; -import net.sf.jasperreports.engine.JRValueParameter; -import net.sf.jasperreports.engine.JasperReportsContext; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.report.api.ReportService; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.expression.TypedValue; -import com.evolveum.midpoint.schema.expression.VariablesMap; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ReportTypeUtil; -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.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.JasperReportEngineConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.JasperReportTypeType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; - -public class MidPointLocalQueryExecutor extends MidPointQueryExecutor { - - private static final Trace LOGGER = TraceManager.getTrace(MidPointLocalQueryExecutor.class); - private ReportService reportService; - private PrismObject report; - private Task task; - private OperationResult operationResult; - - - public MidPointLocalQueryExecutor(JasperReportsContext jasperReportsContext, JRDataset dataset, - Map parametersMap, ReportService reportService){ - super(jasperReportsContext, dataset, parametersMap); - } - - protected MidPointLocalQueryExecutor(JasperReportsContext jasperReportsContext, JRDataset dataset, - Map parametersMap) { - super(jasperReportsContext, dataset, parametersMap); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Creating MidPointLocalQueryExecutor, params:\n{}", ReportUtils.dumpParams(parametersMap, 1)); - } - - //JRFillParameter fillparam = (JRFillParameter) parametersMap.get(JRParameter.REPORT_PARAMETERS_MAP); - //Map reportParams = (Map) fillparam.getValue(); - reportService = getParameterValue(parametersMap, ReportService.PARAMETER_REPORT_SERVICE); - report = getParameterValue(parametersMap, ReportTypeUtil.PARAMETER_REPORT_OBJECT); - task = getParameterValue(parametersMap, ReportTypeUtil.PARAMETER_TASK); - - // The PARAMETER_OPERATION_RESULT will not make it here. It is properly set in the task, but it won't arrive here. - // No idea why. -// operationResult = getParameterValue(parametersMap, ReportCreateTaskHandler.PARAMETER_OPERATION_RESULT); - operationResult = task.getResult(); // WORKAROUND - - parseQuery(); - } - - private T getParameterValue(Map parametersMap, String name) { - JRValueParameter jrValueParameter = parametersMap.get(name); - if (jrValueParameter == null) { - throw new IllegalArgumentException("No parameter '"+name+"' in JasperReport parameters"); - } - return (T) jrValueParameter.getValue(); - } - - @Override - protected TypedValue createTypedPropertyValue(T realValue, Class valueClass) { - PrismPropertyValue pval = reportService.getPrismContext().itemFactory().createPropertyValue(realValue); - return new TypedValue<>(pval, valueClass); - } - - @Override - protected Object getParsedQuery(String query, VariablesMap expressionParameters) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return reportService.parseQuery(report, query, expressionParameters, task, operationResult); - } - - @Override - protected Collection> searchObjects(Object query, Collection> options) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException{ - return reportService.searchObjects((ObjectQuery) query, SelectorOptions.createCollection(GetOperationOptions.createRaw()), task, operationResult); - } - - @Override - protected Collection> evaluateScript(String script, VariablesMap parameters) - throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - return reportService.evaluateScript(report, script, getParameters(), task, operationResult); - } - - @Override - protected boolean isAuditReport() { - JasperReportEngineConfigurationType jasperConfig = report.asObjectable().getJasper(); - if (jasperConfig != null) { - JasperReportTypeType reportType = jasperConfig.getReportType(); - if (reportType != null) { - return reportType.equals(JasperReportTypeType.AUDIT_SQL); - } - } - // legacy - return getScript().contains("AuditEventRecord") || getScript().contains("m_audit_event"); - } - - @Override - protected Collection searchAuditRecords(String script, VariablesMap parameters) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - return reportService.evaluateAuditScript(report, script, parameters, task, operationResult); - } - - @Override - protected JRDataSource createDataSourceFromObjects(Collection> results) { - return new MidPointDataSource(toPcvList(results)); - } - - private Collection> toPcvList(Collection> objects) { - ArrayList> pcvList = new ArrayList<>(objects.size()); - for (PrismObject object : objects) { - pcvList.add(object.asObjectable().asPrismContainerValue()); - } - return pcvList; - } - - @Override - protected JRDataSource createDataSourceFromContainerValues(Collection> results) { - return new MidPointDataSource(results); - } - - - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import net.sf.jasperreports.engine.JRDataSource; +import net.sf.jasperreports.engine.JRDataset; +import net.sf.jasperreports.engine.JRValueParameter; +import net.sf.jasperreports.engine.JasperReportsContext; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.report.api.ReportService; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.expression.TypedValue; +import com.evolveum.midpoint.schema.expression.VariablesMap; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ReportTypeUtil; +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.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.JasperReportEngineConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.JasperReportTypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; + +public class MidPointLocalQueryExecutor extends MidPointQueryExecutor { + + private static final Trace LOGGER = TraceManager.getTrace(MidPointLocalQueryExecutor.class); + private ReportService reportService; + private PrismObject report; + private Task task; + private OperationResult operationResult; + + + public MidPointLocalQueryExecutor(JasperReportsContext jasperReportsContext, JRDataset dataset, + Map parametersMap, ReportService reportService){ + super(jasperReportsContext, dataset, parametersMap); + } + + protected MidPointLocalQueryExecutor(JasperReportsContext jasperReportsContext, JRDataset dataset, + Map parametersMap) { + super(jasperReportsContext, dataset, parametersMap); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Creating MidPointLocalQueryExecutor, params:\n{}", ReportUtils.dumpParams(parametersMap, 1)); + } + + //JRFillParameter fillparam = (JRFillParameter) parametersMap.get(JRParameter.REPORT_PARAMETERS_MAP); + //Map reportParams = (Map) fillparam.getValue(); + reportService = getParameterValue(parametersMap, ReportService.PARAMETER_REPORT_SERVICE); + report = getParameterValue(parametersMap, ReportTypeUtil.PARAMETER_REPORT_OBJECT); + task = getParameterValue(parametersMap, ReportTypeUtil.PARAMETER_TASK); + + // The PARAMETER_OPERATION_RESULT will not make it here. It is properly set in the task, but it won't arrive here. + // No idea why. +// operationResult = getParameterValue(parametersMap, ReportCreateTaskHandler.PARAMETER_OPERATION_RESULT); + operationResult = task.getResult(); // WORKAROUND + + parseQuery(); + } + + private T getParameterValue(Map parametersMap, String name) { + JRValueParameter jrValueParameter = parametersMap.get(name); + if (jrValueParameter == null) { + throw new IllegalArgumentException("No parameter '"+name+"' in JasperReport parameters"); + } + return (T) jrValueParameter.getValue(); + } + + @Override + protected TypedValue createTypedPropertyValue(T realValue, Class valueClass) { + PrismPropertyValue pval = reportService.getPrismContext().itemFactory().createPropertyValue(realValue); + return new TypedValue<>(pval, valueClass); + } + + @Override + protected Object getParsedQuery(String query, VariablesMap expressionParameters) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + return reportService.parseQuery(report, query, expressionParameters, task, operationResult); + } + + @Override + protected Collection> searchObjects(Object query, Collection> options) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException{ + return reportService.searchObjects((ObjectQuery) query, SelectorOptions.createCollection(GetOperationOptions.createRaw()), task, operationResult); + } + + @Override + protected Collection> evaluateScript(String script, VariablesMap parameters) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + return reportService.evaluateScript(report, script, getParameters(), task, operationResult); + } + + @Override + protected boolean isAuditReport() { + JasperReportEngineConfigurationType jasperConfig = report.asObjectable().getJasper(); + if (jasperConfig != null) { + JasperReportTypeType reportType = jasperConfig.getReportType(); + if (reportType != null) { + return reportType.equals(JasperReportTypeType.AUDIT_SQL); + } + } + // legacy + return getScript().contains("AuditEventRecord") || getScript().contains("m_audit_event"); + } + + @Override + protected Collection searchAuditRecords(String script, VariablesMap parameters) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + return reportService.evaluateAuditScript(report, script, parameters, task, operationResult); + } + + @Override + protected JRDataSource createDataSourceFromObjects(Collection> results) { + return new MidPointDataSource(toPcvList(results)); + } + + private Collection> toPcvList(Collection> objects) { + ArrayList> pcvList = new ArrayList<>(objects.size()); + for (PrismObject object : objects) { + pcvList.add(object.asObjectable().asPrismContainerValue()); + } + return pcvList; + } + + @Override + protected JRDataSource createDataSourceFromContainerValues(Collection> results) { + return new MidPointDataSource(results); + } + + + +} diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointQueryExecutor.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointQueryExecutor.java index f542455e44b..4f5dfc0bf53 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointQueryExecutor.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/MidPointQueryExecutor.java @@ -1,205 +1,203 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; - -import com.evolveum.midpoint.prism.*; -import net.sf.jasperreports.engine.JRDataSource; -import net.sf.jasperreports.engine.JRDataset; -import net.sf.jasperreports.engine.JRException; -import net.sf.jasperreports.engine.JRParameter; -import net.sf.jasperreports.engine.JRValueParameter; -import net.sf.jasperreports.engine.JasperReportsContext; -import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; -import net.sf.jasperreports.engine.query.JRAbstractQueryExecuter; - -import org.apache.commons.lang.StringUtils; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.report.api.ReportService; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.expression.TypedValue; -import com.evolveum.midpoint.schema.expression.VariablesMap; -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.exception.SystemException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventRecordType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; - -public abstract class MidPointQueryExecutor extends JRAbstractQueryExecuter { - - private static final Trace LOGGER = TraceManager.getTrace(MidPointLocalQueryExecutor.class); - - private Object query; - private String script; - private Class type; - - public String getScript() { - return script; - } - public Object getQuery() { - return query; - } - public Class getType() { - return type; - } - - protected abstract TypedValue createTypedPropertyValue(T realValue, Class valueClass); - - protected VariablesMap getParameters(){ - JRParameter[] params = dataset.getParameters(); - VariablesMap expressionParameters = new VariablesMap(); - for (JRParameter param : params){ - if (param.isSystemDefined()){ - continue; - } - //LOGGER.trace(((JRBaseParameter)param).getName()); - Object v = getParameterValue(param.getName()); - try{ - expressionParameters.put(param.getName(), createTypedPropertyValue(v, (Class)param.getValueClass())); - } catch (Exception e){ - //just skip properties that are not important for midpoint - } - - LOGGER.trace("p.val: {}", v); - } - return expressionParameters; - } - - protected VariablesMap getPromptingParameters() { - JRParameter[] params = dataset.getParameters(); - VariablesMap expressionParameters = new VariablesMap(); - for (JRParameter param : params) { - if (param.isSystemDefined()) { - continue; - } - if (!param.isForPrompting()) { - continue; - } - //LOGGER.trace(((JRBaseParameter)param).getName()); - Object v = getParameterValue(param.getName()); - try{ - expressionParameters.put(param.getName(), createTypedPropertyValue(v, (Class)param.getValueClass())); - } catch (Exception e){ - //just skip properties that are not important for midpoint - } - - LOGGER.trace("p.val: {}", v); - } - return expressionParameters; - } - - protected abstract Object getParsedQuery(String query, VariablesMap expressionParameters) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; - - protected String getParsedScript(String script){ - String normalized = script.replace("", ""); - return normalized.replace("", ""); - } - - protected MidPointQueryExecutor(JasperReportsContext jasperReportsContext, JRDataset dataset, - Map parametersMap) { - super(jasperReportsContext, dataset, parametersMap); - } - - protected abstract Collection> searchObjects(Object query, Collection> options) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; - - protected abstract Collection> evaluateScript(String script, VariablesMap parameters) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; - - protected abstract Collection searchAuditRecords(String script, VariablesMap parameters) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; - - protected abstract JRDataSource createDataSourceFromObjects(Collection> results); - - protected abstract JRDataSource createDataSourceFromContainerValues(Collection> results); - - @Override - protected void parseQuery() { - try { - - String s = dataset.getQuery().getText(); - LOGGER.trace("query: " + s); - if (StringUtils.isEmpty(s)) { - query = null; - } else { - if (s.startsWith("> results; - results = searchObjects(query, SelectorOptions.createCollection(GetOperationOptions.createRaw())); - return createDataSourceFromObjects(results); - } else { - if (isAuditReport()) { - Collection audtiEventRecords = searchAuditRecords(script, getPromptingParameters()); - Collection auditEventRecordsType = new ArrayList<>(); - for (AuditEventRecord aer : audtiEventRecords){ - AuditEventRecordType aerType = aer.createAuditEventRecordType(true); - auditEventRecordsType.add(aerType); - } - return new JRBeanCollectionDataSource(auditEventRecordsType); - } else { - Collection> results; - results = evaluateScript(script, getParameters()); - return createDataSourceFromContainerValues(results); - } - } - } catch (SchemaException | ObjectNotFoundException | SecurityViolationException - | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - // TODO Auto-generated catch block - throw new JRException(e); - } - } - - protected abstract boolean isAuditReport(); - - @Override - public void close() { -// throw new UnsupportedOperationException("QueryExecutor.close() not supported"); - //nothing to DO - } - - @Override - public boolean cancelQuery() throws JRException { - throw new UnsupportedOperationException("QueryExecutor.cancelQuery() not supported"); - } - - @Override - protected String getParameterReplacement(String parameterName) { - throw new UnsupportedOperationException("QueryExecutor.getParameterReplacement() not supported"); - } - - - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import com.evolveum.midpoint.prism.*; +import net.sf.jasperreports.engine.JRDataSource; +import net.sf.jasperreports.engine.JRDataset; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JRParameter; +import net.sf.jasperreports.engine.JRValueParameter; +import net.sf.jasperreports.engine.JasperReportsContext; +import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource; +import net.sf.jasperreports.engine.query.JRAbstractQueryExecuter; + +import org.apache.commons.lang.StringUtils; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.expression.TypedValue; +import com.evolveum.midpoint.schema.expression.VariablesMap; +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.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventRecordType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +public abstract class MidPointQueryExecutor extends JRAbstractQueryExecuter { + + private static final Trace LOGGER = TraceManager.getTrace(MidPointLocalQueryExecutor.class); + + private Object query; + private String script; + private Class type; + + public String getScript() { + return script; + } + public Object getQuery() { + return query; + } + public Class getType() { + return type; + } + + protected abstract TypedValue createTypedPropertyValue(T realValue, Class valueClass); + + protected VariablesMap getParameters(){ + JRParameter[] params = dataset.getParameters(); + VariablesMap expressionParameters = new VariablesMap(); + for (JRParameter param : params){ + if (param.isSystemDefined()){ + continue; + } + //LOGGER.trace(((JRBaseParameter)param).getName()); + Object v = getParameterValue(param.getName()); + try{ + expressionParameters.put(param.getName(), createTypedPropertyValue(v, (Class)param.getValueClass())); + } catch (Exception e){ + //just skip properties that are not important for midpoint + } + + LOGGER.trace("p.val: {}", v); + } + return expressionParameters; + } + + protected VariablesMap getPromptingParameters() { + JRParameter[] params = dataset.getParameters(); + VariablesMap expressionParameters = new VariablesMap(); + for (JRParameter param : params) { + if (param.isSystemDefined()) { + continue; + } + if (!param.isForPrompting()) { + continue; + } + //LOGGER.trace(((JRBaseParameter)param).getName()); + Object v = getParameterValue(param.getName()); + try{ + expressionParameters.put(param.getName(), createTypedPropertyValue(v, (Class)param.getValueClass())); + } catch (Exception e){ + //just skip properties that are not important for midpoint + } + + LOGGER.trace("p.val: {}", v); + } + return expressionParameters; + } + + protected abstract Object getParsedQuery(String query, VariablesMap expressionParameters) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; + + protected String getParsedScript(String script){ + String normalized = script.replace("", ""); + return normalized.replace("", ""); + } + + protected MidPointQueryExecutor(JasperReportsContext jasperReportsContext, JRDataset dataset, + Map parametersMap) { + super(jasperReportsContext, dataset, parametersMap); + } + + protected abstract Collection> searchObjects(Object query, Collection> options) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; + + protected abstract Collection> evaluateScript(String script, VariablesMap parameters) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException; + + protected abstract Collection searchAuditRecords(String script, VariablesMap parameters) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException; + + protected abstract JRDataSource createDataSourceFromObjects(Collection> results); + + protected abstract JRDataSource createDataSourceFromContainerValues(Collection> results); + + @Override + protected void parseQuery() { + try { + + String s = dataset.getQuery().getText(); + LOGGER.trace("query: " + s); + if (StringUtils.isEmpty(s)) { + query = null; + } else { + if (s.startsWith("> results; + results = (Collection) searchObjects(query, SelectorOptions.createCollection(GetOperationOptions.createRaw())); + return createDataSourceFromObjects(results); + } else { + if (isAuditReport()) { + Collection audtiEventRecords = searchAuditRecords(script, getPromptingParameters()); + Collection auditEventRecordsType = new ArrayList<>(); + for (AuditEventRecord aer : audtiEventRecords){ + AuditEventRecordType aerType = aer.createAuditEventRecordType(true); + auditEventRecordsType.add(aerType); + } + return new JRBeanCollectionDataSource(auditEventRecordsType); + } else { + Collection> results; + results = evaluateScript(script, getParameters()); + return createDataSourceFromContainerValues(results); + } + } + } catch (SchemaException | ObjectNotFoundException | SecurityViolationException + | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + // TODO Auto-generated catch block + throw new JRException(e); + } + } + + protected abstract boolean isAuditReport(); + + @Override + public void close() { +// throw new UnsupportedOperationException("QueryExecutor.close() not supported"); + //nothing to DO + } + + @Override + public boolean cancelQuery() throws JRException { + throw new UnsupportedOperationException("QueryExecutor.cancelQuery() not supported"); + } + + @Override + protected String getParameterReplacement(String parameterName) { + throw new UnsupportedOperationException("QueryExecutor.getParameterReplacement() not supported"); + } + + + +} diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportFunctions.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportFunctions.java index 8e65b9be9c6..5fdb2319a66 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportFunctions.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportFunctions.java @@ -1,558 +1,554 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.impl; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.audit.api.AuditEventStage; -import com.evolveum.midpoint.audit.api.AuditEventType; -import com.evolveum.midpoint.audit.api.AuditService; -import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.schema.*; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.TypedValue; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.wf.api.WorkflowConstants; -import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventStageType; -import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventTypeType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; - -import javax.xml.namespace.QName; -import java.util.*; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.stream.Collectors; - -import static com.evolveum.midpoint.schema.util.CertCampaignTypeUtil.norm; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignStateType.CLOSED; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType.F_STATE; -import static com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType.F_NAME; -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; - -public class ReportFunctions { - - private static final Trace LOGGER = TraceManager.getTrace(ReportFunctions.class); - - private final PrismContext prismContext; - private final SchemaHelper schemaHelper; - private final ModelService model; - private final TaskManager taskManager; - private final AuditService auditService; - - public ReportFunctions(PrismContext prismContext, SchemaHelper schemaHelper, - ModelService modelService, TaskManager taskManager, AuditService auditService) { - this.prismContext = prismContext; - this.schemaHelper = schemaHelper; - this.model = modelService; - this.taskManager = taskManager; - this.auditService = auditService; - } - - public O resolveObject(ObjectReferenceType ref) { - Validate.notNull(ref.getOid(), "Object oid must not be null"); - Validate.notNull(ref.getType(), "Object type must not be null"); - - Class type = prismContext.getSchemaRegistry().determineCompileTimeClass(ref.getType()); - return resolveObject(type, ref.getOid()); - } - - public O resolveObject(Class type, String oid) { - Task task = taskManager.createTaskInstance(); - OperationResult parentResult = task.getResult(); - PrismObject obj; - try { - obj = model.getObject(type, oid, SelectorOptions.createCollection(GetOperationOptions.createResolveNames()), task, parentResult); - return obj.asObjectable(); - } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - // TODO Auto-generated catch block - LOGGER.error("Could not get object with oid " + oid + ". Reason: " + e.getMessage()); - - } - return null; - } - - public List> resolveLinkRefs(Collection refs, Class type) { - - List> objects = new ArrayList<>(); - - for (ObjectReferenceType ref : refs) { - Class clazz = getClassForType(ref.getType()); - if (!clazz.equals(type)) { - continue; - } - Task task = taskManager.createTaskInstance(); - OperationResult parentResult = task.getResult(); - try { - PrismObject obj = model.getObject(type, ref.getOid(), SelectorOptions.createCollection(GetOperationOptions.createResolveNames()), task, parentResult); - objects.add(obj); - } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - // TODO Auto-generated catch block - LOGGER.error("Could not get object with oid " + ref.getOid() + ". Reason: " + e.getMessage()); - - } - - } - return objects; - } - - public String resolveRefName(ObjectReferenceType ref) { - if (ref == null) { - return null; - } - PrismReferenceValue refValue = ref.asReferenceValue(); - Object name = refValue.getTargetName() != null ? ref.getTargetName().getOrig() : null; - if (!(name instanceof String)) { - LOGGER.error("Couldn't resolve object name"); - } - - return (String) name; - } - - public List> resolveRoles(Collection assignments) { - return resolveAssignments(assignments, RoleType.class); - } - - public List> resolveRoles(Collection assignments, - Collection filterOids) { - - Collection toResolve = assignments; - if (CollectionUtils.isNotEmpty(filterOids) && CollectionUtils.isNotEmpty(assignments)) { - toResolve = assignments.stream().filter(Objects::nonNull) - .filter(as -> as.getTargetRef() != null && as.getTargetRef().getOid() != null - && filterOids.contains(as.getTargetRef().getOid())) - // filter to default relation only - ignores approvers etc - .filter(as -> prismContext.isDefaultRelation(as.getTargetRef().getRelation())) - .collect(Collectors.toList()); - } - - return resolveRoles(toResolve); - } - - public List> resolveOrgs(Collection assignments) { - return resolveAssignments(assignments, OrgType.class); - } - - public List> resolveRoles(AssignmentType assignments) { - return resolveAssignments(assignments, RoleType.class); - } - - public List> resolveOrgs(AssignmentType assignments) { - return resolveAssignments(assignments, OrgType.class); - } - - public List> resolveAssignments(AssignmentType assignment, Class type) { - List assignments = new ArrayList<>(); - assignments.add(assignment); - return resolveAssignments(assignments, type); - } - - public List> resolveAssignments(Collection assignments, Class type) { - List> resolvedAssignments = new ArrayList<>(); - if (assignments == null) { - return resolvedAssignments; - } - for (AssignmentType assignment : assignments) { - Class clazz = null; - String oid = null; - if (assignment.getTargetRef() != null) { - clazz = getClassForType(assignment.getTargetRef().getType()); - oid = assignment.getTargetRef().getOid(); - } else if (assignment.getTenantRef() != null) { - clazz = getClassForType(assignment.getTenantRef().getType()); - oid = assignment.getTenantRef().getOid(); - } - - if (clazz == null && assignment.getConstruction() != null) { - continue; - } else { - LOGGER.debug("Could not resolve assignment for type {}. No target type defined.", type); - } - - if (clazz == null || !clazz.equals(type)) { - continue; - } - - if (assignment.getTargetRef() != null && assignment.getTargetRef().asReferenceValue().getObject() != null) { - resolvedAssignments.add((PrismObject) assignment.getTargetRef().asReferenceValue().getObject()); - continue; - } - - Task task = taskManager.createTaskInstance(); - try { - PrismObject obj = model.getObject(type, oid, null, task, task.getResult()); - resolvedAssignments.add(obj); - } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - LOGGER.error("Could not get object with oid " + oid + ". Reason: " + e.getMessage()); - - } - - } - - return resolvedAssignments; - } - - public List searchAuditRecords(String query, Map jasperParams) { - - if (StringUtils.isBlank(query)) { - return new ArrayList<>(); - } - OperationResult result = new OperationResult("searchAuditRecords"); - - List records = auditService.listRecords(query, ReportUtils.jasperParamsToAuditParams(jasperParams), result); - result.computeStatus(); - return records; - } - - public List searchAuditRecordsAsWorkflows(String query, Map params) { - return transformToWorkflows(searchAuditRecords(query, params)); - } - - private List transformToWorkflows(List auditEvents) { - if (auditEvents == null || auditEvents.isEmpty()) { - return auditEvents; - } - - // group all records by property/wf.processInstanceId - Map> workflows = auditEvents.stream().collect(Collectors.groupingBy(event -> { - Set processInstanceIds = event.getPropertyValues(WorkflowConstants.AUDIT_PROCESS_INSTANCE_ID); - - Iterator it = processInstanceIds.iterator(); - return it.hasNext() ? it.next() : "default workflow"; - })); - - // map of workflows in order of first request timestamp - Map> workflowsFiltered = new TreeMap<>(); - workflows.entrySet().stream().forEach(entry -> { - List wf = entry.getValue(); - // leave only the first request in each workflow - List filtered = new ArrayList<>(); - wf.stream().filter(record -> record.getEventStage() == AuditEventStage.REQUEST).findFirst() - .ifPresent(filtered::add); - // and all executions with decision - wf.stream().filter(record -> record.getEventStage() == AuditEventStage.EXECUTION) - .filter(record -> record.getMessage() == null || !record.getMessage().contains("(no decision)")) - .forEach(filtered::add); - - wf.stream().findFirst().ifPresent(record -> { - - workflowsFiltered.put(record.getTimestamp(), filtered); - }); - }); - - return workflowsFiltered.entrySet().stream().map(Entry::getValue).flatMap(List::stream) - .collect(Collectors.toList()); - } - - public UserType getShadowOwner(String shadowOid) { - Task task = taskManager.createTaskInstance(); - try { - PrismObject owner = model.findShadowOwner(shadowOid, task, task.getResult()); - return owner.asObjectable(); - } catch (ObjectNotFoundException | SecurityViolationException | SchemaException | ConfigurationException | ExpressionEvaluationException | CommunicationException e) { - // TODO Auto-generated catch block - LOGGER.error("Could not find owner for shadow with oid " + shadowOid + ". Reason: " + e.getMessage()); - } - - return null; - - } - - private Class getClassForType(QName type) { - return prismContext.getSchemaRegistry().determineCompileTimeClass(type); - } - - List searchObjects(Class type, ObjectQuery query) { - List ret = new ArrayList<>(); - Task task = taskManager.createTaskInstance(); - try { - List> list = model.searchObjects(type, query, null, task, task.getResult()).getList(); - for (PrismObject po : list) { - ret.add(po.asObjectable()); - } - } catch (SchemaException | ObjectNotFoundException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - LOGGER.error("Could not search objects of type: " + type + " with query " + query + ". Reason: " + e.getMessage()); - } - return ret; - } - - ObjectFilter createEqualFilter(QName propertyName, Class type, T realValue) throws SchemaException { - return prismContext.queryFor(type) - .item(propertyName).eq(realValue) - .buildFilter(); - } - - ObjectFilter createEqualFilter(ItemPath propertyPath, Class type, T realValue) throws SchemaException { - return prismContext.queryFor(type) - .item(propertyPath).eq(realValue) - .buildFilter(); - } - - // TODO implement if needed -// RefFilter createReferenceEqualFilter(QName propertyName, Class type, String... oids) { -// return RefFilter.createReferenceEqual(propertyName, type, prismContext, oids); -// } - -// RefFilter createReferenceEqualFilter(ItemPath propertyPath, Class type, String... oids) throws SchemaException { -// return RefFilter.createReferenceEqual(propertyPath, type, prismContext, oids); -// } - -// Object parseObjectFromXML (String xml) throws SchemaException { -// return prismContext.parserFor(xml).xml().parseAnyData(); -// } - public List> searchApprovalWorkItems() - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException, DatatypeConfigurationException { - return searchApprovalWorkItems(0, null); - } - - /* - * @param days - return only workitems with createTimestamp older than (now-days), 0 to return all - * @sortColumn - optionally AbstractWorkItemType QName to asc sort results (e.g. AbstractWorkItemType.F_CREATE_TIMESTAMP) - */ - public List> searchApprovalWorkItems(int days, QName sortColumn) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException, DatatypeConfigurationException { - Task task = taskManager.createTaskInstance(); - OperationResult result = task.getResult(); - ObjectQuery query = prismContext.queryFor(AbstractWorkItemType.class).build(); - - if (days > 0) { - XMLGregorianCalendar since = (new Clock()).currentTimeXMLGregorianCalendar(); - DatatypeFactory df = DatatypeFactory.newInstance(); - since.add (df.newDuration(false, 0, 0, days, 0, 0, 0)); - - query.addFilter(prismContext.queryFor(AbstractWorkItemType.class) - .item(AbstractWorkItemType.F_CREATE_TIMESTAMP).lt(since).buildFilter()); - } - - if (sortColumn != null) { - query.addFilter(prismContext.queryFor(AbstractWorkItemType.class) - .asc(sortColumn) - .buildFilter()); - } - Object[] itemsToResolve = { CaseWorkItemType.F_ASSIGNEE_REF, - ItemPath.create(PrismConstants.T_PARENT, CaseType.F_OBJECT_REF), - ItemPath.create(PrismConstants.T_PARENT, CaseType.F_TARGET_REF), - ItemPath.create(PrismConstants.T_PARENT, CaseType.F_REQUESTOR_REF) }; - SearchResultList workItems = model.searchContainers(CaseWorkItemType.class, query, - schemaHelper.getOperationOptionsBuilder().items(itemsToResolve).resolve().build(), task, result); - return PrismContainerValue.toPcvList(workItems); - } - - /** - * Retrieves all definitions. - * Augments them by count of campaigns (all + open ones). - * - * TODO query parameters, customizable sorting - * definitions and campaigns counts are expected to be low, so we can afford to go through all of them here - */ - public Collection> searchCertificationDefinitions() throws ConfigurationException, SchemaException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException { - - Task task = taskManager.createTaskInstance(); - OperationResult result = task.getResult(); - Collection> options = - SelectorOptions.createCollection(GetOperationOptions.createResolveNames()); - List> definitions = model.searchObjects(AccessCertificationDefinitionType.class, null, options, task, result); - final Map> definitionsForReportMap = new HashMap<>(); - for (PrismObject definition : definitions) { - // create subclass with the values copied from the superclass - PrismObject definitionForReport = prismContext.createObjectable(AccessCertificationDefinitionForReportType.class).asPrismObject(); - for (Item item : definition.getValue().getItems()) { - definitionForReport.getValue().add(item.clone()); - } - definitionsForReportMap.put(definition.getOid(), definitionForReport); - } - - ResultHandler handler = (campaignObject, parentResult) -> { - AccessCertificationCampaignType campaign = campaignObject.asObjectable(); - if (campaign.getDefinitionRef() != null) { - String definitionOid = campaign.getDefinitionRef().getOid(); - PrismObject definitionObject = definitionsForReportMap.get(definitionOid); - if (definitionObject != null) { - AccessCertificationDefinitionForReportType definition = definitionObject.asObjectable(); - int campaigns = definition.getCampaigns() != null ? definition.getCampaigns() : 0; - definition.setCampaigns(campaigns+1); - AccessCertificationCampaignStateType state = campaign.getState(); - if (state != AccessCertificationCampaignStateType.CREATED && state != CLOSED) { - int openCampaigns = definition.getOpenCampaigns() != null ? definition.getOpenCampaigns() : 0; - definition.setOpenCampaigns(openCampaigns+1); - } - } - } - return true; - }; - model.searchObjectsIterative(AccessCertificationCampaignType.class, null, handler, null, task, result); - - List> rv = new ArrayList<>(definitionsForReportMap.values()); - Collections.sort(rv, (o1, o2) -> { - String n1 = o1.asObjectable().getName().getOrig(); - String n2 = o2.asObjectable().getName().getOrig(); - if (n1 == null) { - n1 = ""; - } - return n1.compareTo(n2); - }); - for (PrismObject defObject : rv) { - AccessCertificationDefinitionForReportType def = defObject.asObjectable(); - if (def.getCampaigns() == null) { - def.setCampaigns(0); - } - if (def.getOpenCampaigns() == null) { - def.setOpenCampaigns(0); - } - } - return rv; - } - - public List> getCertificationCampaignCases(String campaignName) throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { - List cases = getCertificationCampaignCasesAsBeans(campaignName); - return PrismContainerValue.toPcvList(cases); - } - - private List getCertificationCampaignCasesAsBeans(String campaignName) throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { - Task task = taskManager.createTaskInstance(); - ObjectQuery query; - if (StringUtils.isEmpty(campaignName)) { - //query = null; - return new ArrayList<>(); - } else { - query = prismContext.queryFor(AccessCertificationCaseType.class) - .item(PrismConstants.T_PARENT, F_NAME).eqPoly(campaignName, "").matchingOrig() - // TODO first by object/target type then by name (not supported by the repository as of now) - .asc(AccessCertificationCaseType.F_OBJECT_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) - .asc(AccessCertificationCaseType.F_TARGET_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) - .build(); - } - Collection> options = - SelectorOptions.createCollection(GetOperationOptions.createResolveNames()); - return model.searchContainers(AccessCertificationCaseType.class, query, options, task, task.getResult()); - } - - private List getCertificationCampaignNotRespondedCasesAsBeans(String campaignName) throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { - Task task = taskManager.createTaskInstance(); - ObjectQuery query; - if (StringUtils.isEmpty(campaignName)) { - //query = null; - return new ArrayList<>(); - } else { - query = prismContext.queryFor(AccessCertificationCaseType.class) - .item(PrismConstants.T_PARENT, F_NAME).eqPoly(campaignName, "").matchingOrig() - .and().item(AccessCertificationCaseType.F_CURRENT_STAGE_OUTCOME).eq(SchemaConstants.MODEL_CERTIFICATION_OUTCOME_NO_RESPONSE) - // TODO first by object/target type then by name (not supported by the repository as of now) - .asc(AccessCertificationCaseType.F_OBJECT_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) - .asc(AccessCertificationCaseType.F_TARGET_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) - .build(); - } - Collection> options = - SelectorOptions.createCollection(GetOperationOptions.createResolveNames()); - return model.searchContainers(AccessCertificationCaseType.class, query, options, task, task.getResult()); - } - - public List> getCertificationCampaignDecisions(String campaignName, Integer stageNumber) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException { - return getCertificationCampaignDecisions(campaignName, stageNumber, null); - } - - public List> getCertificationCampaignDecisions(String campaignName, Integer stageNumber, Integer iteration) - throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { - List cases = getCertificationCampaignCasesAsBeans(campaignName); - List workItems = new ArrayList<>(); - for (AccessCertificationCaseType aCase : cases) { - for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) { - if (stageNumber != null && !Objects.equals(workItem.getStageNumber(), stageNumber)) { - continue; - } - if (iteration != null && norm(workItem.getIteration()) != iteration) { - continue; - } - workItems.add(workItem); - } - } - return PrismContainerValue.toPcvList(workItems); - } - - private AccessCertificationCampaignType getCampaignByName(String campaignName) - throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, - ConfigurationException, ExpressionEvaluationException { - Task task = taskManager.createTaskInstance(); - if (StringUtils.isEmpty(campaignName)) { - return null; - } - ObjectQuery query = prismContext.queryFor(AccessCertificationCampaignType.class) - .item(AccessCertificationCampaignType.F_NAME).eqPoly(campaignName).matchingOrig() - .build(); - List> objects = model - .searchObjects(AccessCertificationCampaignType.class, query, null, task, task.getResult()); - if (objects.isEmpty()) { - return null; - } else if (objects.size() == 1) { - return objects.get(0).asObjectable(); - } else { - throw new IllegalStateException("More than one certification campaign found by name '" + campaignName + "': " + objects); - } - } - - @SuppressWarnings("unused") - public List> getCertificationCampaignNonResponders(String campaignName, Integer stageNumber) - throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { - List workItems = new ArrayList<>(); - AccessCertificationCampaignType campaign = getCampaignByName(campaignName); - if (campaign != null) { - List cases = getCertificationCampaignNotRespondedCasesAsBeans(campaignName); - for (AccessCertificationCaseType aCase : cases) { - for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) { - if (norm(workItem.getIteration()) == norm(campaign.getIteration()) - && (workItem.getOutput() == null || workItem.getOutput().getOutcome() == null) - && (stageNumber == null || Objects.equals(workItem.getStageNumber(), stageNumber))) { - workItems.add(workItem); - } - } - } - } else { - LOGGER.debug("No campaign named '{}' was found", campaignName); - } - return PrismContainerValue.toPcvList(workItems); - } - - public List> getCertificationCampaigns(Boolean alsoClosedCampaigns) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException { - Task task = taskManager.createTaskInstance(); - - ObjectQuery query = prismContext.queryFor(AccessCertificationCampaignType.class) - .asc(F_NAME) - .build(); - if (!Boolean.TRUE.equals(alsoClosedCampaigns)) { - query.addFilter( - prismContext.queryFor(AccessCertificationCampaignType.class) - .not().item(F_STATE).eq(CLOSED) - .buildFilter() - ); - } - - Collection> options = schemaHelper.getOperationOptionsBuilder() - .root().resolveNames() - .item(AccessCertificationCampaignType.F_CASE).retrieve() - .build(); - return model.searchObjects(AccessCertificationCampaignType.class, query, options, task, task.getResult()); - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.impl; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.audit.api.AuditEventStage; +import com.evolveum.midpoint.audit.api.AuditService; +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.schema.*; +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.task.api.TaskManager; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.api.WorkflowConstants; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; + +import javax.xml.namespace.QName; +import java.util.*; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.stream.Collectors; + +import static com.evolveum.midpoint.schema.util.CertCampaignTypeUtil.norm; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignStateType.CLOSED; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType.F_STATE; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType.F_NAME; +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +public class ReportFunctions { + + private static final Trace LOGGER = TraceManager.getTrace(ReportFunctions.class); + + private final PrismContext prismContext; + private final SchemaHelper schemaHelper; + private final ModelService model; + private final TaskManager taskManager; + private final AuditService auditService; + + public ReportFunctions(PrismContext prismContext, SchemaHelper schemaHelper, + ModelService modelService, TaskManager taskManager, AuditService auditService) { + this.prismContext = prismContext; + this.schemaHelper = schemaHelper; + this.model = modelService; + this.taskManager = taskManager; + this.auditService = auditService; + } + + public O resolveObject(ObjectReferenceType ref) { + Validate.notNull(ref.getOid(), "Object oid must not be null"); + Validate.notNull(ref.getType(), "Object type must not be null"); + + Class type = prismContext.getSchemaRegistry().determineCompileTimeClass(ref.getType()); + return resolveObject(type, ref.getOid()); + } + + public O resolveObject(Class type, String oid) { + Task task = taskManager.createTaskInstance(); + OperationResult parentResult = task.getResult(); + PrismObject obj; + try { + obj = model.getObject(type, oid, SelectorOptions.createCollection(GetOperationOptions.createResolveNames()), task, parentResult); + return obj.asObjectable(); + } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + // TODO Auto-generated catch block + LOGGER.error("Could not get object with oid " + oid + ". Reason: " + e.getMessage()); + + } + return null; + } + + public List> resolveLinkRefs(Collection refs, Class type) { + + List> objects = new ArrayList<>(); + + for (ObjectReferenceType ref : refs) { + Class clazz = getClassForType(ref.getType()); + if (!clazz.equals(type)) { + continue; + } + Task task = taskManager.createTaskInstance(); + OperationResult parentResult = task.getResult(); + try { + PrismObject obj = model.getObject(type, ref.getOid(), SelectorOptions.createCollection(GetOperationOptions.createResolveNames()), task, parentResult); + objects.add(obj); + } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + // TODO Auto-generated catch block + LOGGER.error("Could not get object with oid " + ref.getOid() + ". Reason: " + e.getMessage()); + + } + + } + return objects; + } + + public String resolveRefName(ObjectReferenceType ref) { + if (ref == null) { + return null; + } + PrismReferenceValue refValue = ref.asReferenceValue(); + Object name = refValue.getTargetName() != null ? ref.getTargetName().getOrig() : null; + if (!(name instanceof String)) { + LOGGER.error("Couldn't resolve object name"); + } + + return (String) name; + } + + public List> resolveRoles(Collection assignments) { + return resolveAssignments(assignments, RoleType.class); + } + + public List> resolveRoles(Collection assignments, + Collection filterOids) { + + Collection toResolve = assignments; + if (CollectionUtils.isNotEmpty(filterOids) && CollectionUtils.isNotEmpty(assignments)) { + toResolve = assignments.stream().filter(Objects::nonNull) + .filter(as -> as.getTargetRef() != null && as.getTargetRef().getOid() != null + && filterOids.contains(as.getTargetRef().getOid())) + // filter to default relation only - ignores approvers etc + .filter(as -> prismContext.isDefaultRelation(as.getTargetRef().getRelation())) + .collect(Collectors.toList()); + } + + return resolveRoles(toResolve); + } + + public List> resolveOrgs(Collection assignments) { + return resolveAssignments(assignments, OrgType.class); + } + + public List> resolveRoles(AssignmentType assignments) { + return resolveAssignments(assignments, RoleType.class); + } + + public List> resolveOrgs(AssignmentType assignments) { + return resolveAssignments(assignments, OrgType.class); + } + + public List> resolveAssignments(AssignmentType assignment, Class type) { + List assignments = new ArrayList<>(); + assignments.add(assignment); + return resolveAssignments(assignments, type); + } + + public List> resolveAssignments(Collection assignments, Class type) { + List> resolvedAssignments = new ArrayList<>(); + if (assignments == null) { + return resolvedAssignments; + } + for (AssignmentType assignment : assignments) { + Class clazz = null; + String oid = null; + if (assignment.getTargetRef() != null) { + clazz = getClassForType(assignment.getTargetRef().getType()); + oid = assignment.getTargetRef().getOid(); + } else if (assignment.getTenantRef() != null) { + clazz = getClassForType(assignment.getTenantRef().getType()); + oid = assignment.getTenantRef().getOid(); + } + + if (clazz == null && assignment.getConstruction() != null) { + continue; + } else { + LOGGER.debug("Could not resolve assignment for type {}. No target type defined.", type); + } + + if (clazz == null || !clazz.equals(type)) { + continue; + } + + if (assignment.getTargetRef() != null && assignment.getTargetRef().asReferenceValue().getObject() != null) { + resolvedAssignments.add((PrismObject) assignment.getTargetRef().asReferenceValue().getObject()); + continue; + } + + Task task = taskManager.createTaskInstance(); + try { + PrismObject obj = model.getObject(type, oid, null, task, task.getResult()); + resolvedAssignments.add(obj); + } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + LOGGER.error("Could not get object with oid " + oid + ". Reason: " + e.getMessage()); + + } + + } + + return resolvedAssignments; + } + + public List searchAuditRecords(String query, Map jasperParams) { + + if (StringUtils.isBlank(query)) { + return new ArrayList<>(); + } + OperationResult result = new OperationResult("searchAuditRecords"); + + List records = auditService.listRecords(query, ReportUtils.jasperParamsToAuditParams(jasperParams), result); + result.computeStatus(); + return records; + } + + public List searchAuditRecordsAsWorkflows(String query, Map params) { + return transformToWorkflows(searchAuditRecords(query, params)); + } + + private List transformToWorkflows(List auditEvents) { + if (auditEvents == null || auditEvents.isEmpty()) { + return auditEvents; + } + + // group all records by property/wf.processInstanceId + Map> workflows = auditEvents.stream().collect(Collectors.groupingBy(event -> { + Set processInstanceIds = event.getPropertyValues(WorkflowConstants.AUDIT_PROCESS_INSTANCE_ID); + + Iterator it = processInstanceIds.iterator(); + return it.hasNext() ? it.next() : "default workflow"; + })); + + // map of workflows in order of first request timestamp + Map> workflowsFiltered = new TreeMap<>(); + workflows.entrySet().stream().forEach(entry -> { + List wf = entry.getValue(); + // leave only the first request in each workflow + List filtered = new ArrayList<>(); + wf.stream().filter(record -> record.getEventStage() == AuditEventStage.REQUEST).findFirst() + .ifPresent(filtered::add); + // and all executions with decision + wf.stream().filter(record -> record.getEventStage() == AuditEventStage.EXECUTION) + .filter(record -> record.getMessage() == null || !record.getMessage().contains("(no decision)")) + .forEach(filtered::add); + + wf.stream().findFirst().ifPresent(record -> { + + workflowsFiltered.put(record.getTimestamp(), filtered); + }); + }); + + return workflowsFiltered.entrySet().stream().map(Entry::getValue).flatMap(List::stream) + .collect(Collectors.toList()); + } + + public UserType getShadowOwner(String shadowOid) { + Task task = taskManager.createTaskInstance(); + try { + PrismObject owner = model.findShadowOwner(shadowOid, task, task.getResult()); + return owner.asObjectable(); + } catch (ObjectNotFoundException | SecurityViolationException | SchemaException | ConfigurationException | ExpressionEvaluationException | CommunicationException e) { + // TODO Auto-generated catch block + LOGGER.error("Could not find owner for shadow with oid " + shadowOid + ". Reason: " + e.getMessage()); + } + + return null; + + } + + private Class getClassForType(QName type) { + return prismContext.getSchemaRegistry().determineCompileTimeClass(type); + } + + List searchObjects(Class type, ObjectQuery query) { + List ret = new ArrayList<>(); + Task task = taskManager.createTaskInstance(); + try { + List> list = model.searchObjects(type, query, null, task, task.getResult()).getList(); + for (PrismObject po : list) { + ret.add(po.asObjectable()); + } + } catch (SchemaException | ObjectNotFoundException | SecurityViolationException | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + LOGGER.error("Could not search objects of type: " + type + " with query " + query + ". Reason: " + e.getMessage()); + } + return ret; + } + + ObjectFilter createEqualFilter(QName propertyName, Class type, T realValue) throws SchemaException { + return prismContext.queryFor(type) + .item(propertyName).eq(realValue) + .buildFilter(); + } + + ObjectFilter createEqualFilter(ItemPath propertyPath, Class type, T realValue) throws SchemaException { + return prismContext.queryFor(type) + .item(propertyPath).eq(realValue) + .buildFilter(); + } + + // TODO implement if needed +// RefFilter createReferenceEqualFilter(QName propertyName, Class type, String... oids) { +// return RefFilter.createReferenceEqual(propertyName, type, prismContext, oids); +// } + +// RefFilter createReferenceEqualFilter(ItemPath propertyPath, Class type, String... oids) throws SchemaException { +// return RefFilter.createReferenceEqual(propertyPath, type, prismContext, oids); +// } + +// Object parseObjectFromXML (String xml) throws SchemaException { +// return prismContext.parserFor(xml).xml().parseAnyData(); +// } + public List> searchApprovalWorkItems() + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException, DatatypeConfigurationException { + return searchApprovalWorkItems(0, null); + } + + /* + * @param days - return only workitems with createTimestamp older than (now-days), 0 to return all + * @sortColumn - optionally AbstractWorkItemType QName to asc sort results (e.g. AbstractWorkItemType.F_CREATE_TIMESTAMP) + */ + public List> searchApprovalWorkItems(int days, QName sortColumn) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException, DatatypeConfigurationException { + Task task = taskManager.createTaskInstance(); + OperationResult result = task.getResult(); + ObjectQuery query = prismContext.queryFor(AbstractWorkItemType.class).build(); + + if (days > 0) { + XMLGregorianCalendar since = (new Clock()).currentTimeXMLGregorianCalendar(); + DatatypeFactory df = DatatypeFactory.newInstance(); + since.add (df.newDuration(false, 0, 0, days, 0, 0, 0)); + + query.addFilter(prismContext.queryFor(AbstractWorkItemType.class) + .item(AbstractWorkItemType.F_CREATE_TIMESTAMP).lt(since).buildFilter()); + } + + if (sortColumn != null) { + query.addFilter(prismContext.queryFor(AbstractWorkItemType.class) + .asc(sortColumn) + .buildFilter()); + } + Object[] itemsToResolve = { CaseWorkItemType.F_ASSIGNEE_REF, + ItemPath.create(PrismConstants.T_PARENT, CaseType.F_OBJECT_REF), + ItemPath.create(PrismConstants.T_PARENT, CaseType.F_TARGET_REF), + ItemPath.create(PrismConstants.T_PARENT, CaseType.F_REQUESTOR_REF) }; + SearchResultList workItems = model.searchContainers(CaseWorkItemType.class, query, + schemaHelper.getOperationOptionsBuilder().items(itemsToResolve).resolve().build(), task, result); + return PrismContainerValue.toPcvList(workItems); + } + + /** + * Retrieves all definitions. + * Augments them by count of campaigns (all + open ones). + * + * TODO query parameters, customizable sorting + * definitions and campaigns counts are expected to be low, so we can afford to go through all of them here + */ + public Collection> searchCertificationDefinitions() throws ConfigurationException, SchemaException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException { + + Task task = taskManager.createTaskInstance(); + OperationResult result = task.getResult(); + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createResolveNames()); + List> definitions = model.searchObjects(AccessCertificationDefinitionType.class, null, options, task, result); + final Map> definitionsForReportMap = new HashMap<>(); + for (PrismObject definition : definitions) { + // create subclass with the values copied from the superclass + PrismObject definitionForReport = prismContext.createObjectable(AccessCertificationDefinitionForReportType.class).asPrismObject(); + for (Item item : definition.getValue().getItems()) { + definitionForReport.getValue().add(item.clone()); + } + definitionsForReportMap.put(definition.getOid(), definitionForReport); + } + + ResultHandler handler = (campaignObject, parentResult) -> { + AccessCertificationCampaignType campaign = campaignObject.asObjectable(); + if (campaign.getDefinitionRef() != null) { + String definitionOid = campaign.getDefinitionRef().getOid(); + PrismObject definitionObject = definitionsForReportMap.get(definitionOid); + if (definitionObject != null) { + AccessCertificationDefinitionForReportType definition = definitionObject.asObjectable(); + int campaigns = definition.getCampaigns() != null ? definition.getCampaigns() : 0; + definition.setCampaigns(campaigns+1); + AccessCertificationCampaignStateType state = campaign.getState(); + if (state != AccessCertificationCampaignStateType.CREATED && state != CLOSED) { + int openCampaigns = definition.getOpenCampaigns() != null ? definition.getOpenCampaigns() : 0; + definition.setOpenCampaigns(openCampaigns+1); + } + } + } + return true; + }; + model.searchObjectsIterative(AccessCertificationCampaignType.class, null, handler, null, task, result); + + List> rv = new ArrayList<>(definitionsForReportMap.values()); + Collections.sort(rv, (o1, o2) -> { + String n1 = o1.asObjectable().getName().getOrig(); + String n2 = o2.asObjectable().getName().getOrig(); + if (n1 == null) { + n1 = ""; + } + return n1.compareTo(n2); + }); + for (PrismObject defObject : rv) { + AccessCertificationDefinitionForReportType def = defObject.asObjectable(); + if (def.getCampaigns() == null) { + def.setCampaigns(0); + } + if (def.getOpenCampaigns() == null) { + def.setOpenCampaigns(0); + } + } + return rv; + } + + public List> getCertificationCampaignCases(String campaignName) throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { + List cases = getCertificationCampaignCasesAsBeans(campaignName); + return PrismContainerValue.toPcvList(cases); + } + + private List getCertificationCampaignCasesAsBeans(String campaignName) throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { + Task task = taskManager.createTaskInstance(); + ObjectQuery query; + if (StringUtils.isEmpty(campaignName)) { + //query = null; + return new ArrayList<>(); + } else { + query = prismContext.queryFor(AccessCertificationCaseType.class) + .item(PrismConstants.T_PARENT, F_NAME).eqPoly(campaignName, "").matchingOrig() + // TODO first by object/target type then by name (not supported by the repository as of now) + .asc(AccessCertificationCaseType.F_OBJECT_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) + .asc(AccessCertificationCaseType.F_TARGET_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) + .build(); + } + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createResolveNames()); + return model.searchContainers(AccessCertificationCaseType.class, query, options, task, task.getResult()); + } + + private List getCertificationCampaignNotRespondedCasesAsBeans(String campaignName) throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { + Task task = taskManager.createTaskInstance(); + ObjectQuery query; + if (StringUtils.isEmpty(campaignName)) { + //query = null; + return new ArrayList<>(); + } else { + query = prismContext.queryFor(AccessCertificationCaseType.class) + .item(PrismConstants.T_PARENT, F_NAME).eqPoly(campaignName, "").matchingOrig() + .and().item(AccessCertificationCaseType.F_CURRENT_STAGE_OUTCOME).eq(SchemaConstants.MODEL_CERTIFICATION_OUTCOME_NO_RESPONSE) + // TODO first by object/target type then by name (not supported by the repository as of now) + .asc(AccessCertificationCaseType.F_OBJECT_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) + .asc(AccessCertificationCaseType.F_TARGET_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME) + .build(); + } + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createResolveNames()); + return model.searchContainers(AccessCertificationCaseType.class, query, options, task, task.getResult()); + } + + public List> getCertificationCampaignDecisions(String campaignName, Integer stageNumber) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + return getCertificationCampaignDecisions(campaignName, stageNumber, null); + } + + public List> getCertificationCampaignDecisions(String campaignName, Integer stageNumber, Integer iteration) + throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { + List cases = getCertificationCampaignCasesAsBeans(campaignName); + List workItems = new ArrayList<>(); + for (AccessCertificationCaseType aCase : cases) { + for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) { + if (stageNumber != null && !Objects.equals(workItem.getStageNumber(), stageNumber)) { + continue; + } + if (iteration != null && norm(workItem.getIteration()) != iteration) { + continue; + } + workItems.add(workItem); + } + } + return PrismContainerValue.toPcvList(workItems); + } + + private AccessCertificationCampaignType getCampaignByName(String campaignName) + throws CommunicationException, ObjectNotFoundException, SchemaException, SecurityViolationException, + ConfigurationException, ExpressionEvaluationException { + Task task = taskManager.createTaskInstance(); + if (StringUtils.isEmpty(campaignName)) { + return null; + } + ObjectQuery query = prismContext.queryFor(AccessCertificationCampaignType.class) + .item(AccessCertificationCampaignType.F_NAME).eqPoly(campaignName).matchingOrig() + .build(); + List> objects = model + .searchObjects(AccessCertificationCampaignType.class, query, null, task, task.getResult()); + if (objects.isEmpty()) { + return null; + } else if (objects.size() == 1) { + return objects.get(0).asObjectable(); + } else { + throw new IllegalStateException("More than one certification campaign found by name '" + campaignName + "': " + objects); + } + } + + @SuppressWarnings("unused") + public List> getCertificationCampaignNonResponders(String campaignName, Integer stageNumber) + throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException { + List workItems = new ArrayList<>(); + AccessCertificationCampaignType campaign = getCampaignByName(campaignName); + if (campaign != null) { + List cases = getCertificationCampaignNotRespondedCasesAsBeans(campaignName); + for (AccessCertificationCaseType aCase : cases) { + for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) { + if (norm(workItem.getIteration()) == norm(campaign.getIteration()) + && (workItem.getOutput() == null || workItem.getOutput().getOutcome() == null) + && (stageNumber == null || Objects.equals(workItem.getStageNumber(), stageNumber))) { + workItems.add(workItem); + } + } + } + } else { + LOGGER.debug("No campaign named '{}' was found", campaignName); + } + return PrismContainerValue.toPcvList(workItems); + } + + public List> getCertificationCampaigns(Boolean alsoClosedCampaigns) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException { + Task task = taskManager.createTaskInstance(); + + ObjectQuery query = prismContext.queryFor(AccessCertificationCampaignType.class) + .asc(F_NAME) + .build(); + if (!Boolean.TRUE.equals(alsoClosedCampaigns)) { + query.addFilter( + prismContext.queryFor(AccessCertificationCampaignType.class) + .not().item(F_STATE).eq(CLOSED) + .buildFilter() + ); + } + + Collection> options = schemaHelper.getOperationOptionsBuilder() + .root().resolveNames() + .item(AccessCertificationCampaignType.F_CASE).retrieve() + .build(); + return model.searchObjects(AccessCertificationCampaignType.class, query, options, task, task.getResult()); + } + +} diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java index b5aa7e5cf5d..2e3774f0076 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportHTMLCreateTaskHandler.java @@ -1,1066 +1,1067 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.impl; - -import java.io.File; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.report.api.ReportService; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.audit.api.AuditService; -import com.evolveum.midpoint.common.Clock; -import com.evolveum.midpoint.common.LocalizationService; -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.api.interaction.DashboardService; -import com.evolveum.midpoint.model.api.interaction.DashboardWidget; -import com.evolveum.midpoint.model.api.util.DashboardUtils; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.Item; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismContainer; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.PrismProperty; -import com.evolveum.midpoint.prism.PrismReference; -import com.evolveum.midpoint.prism.Referencable; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.schema.ObjectDeltaOperation; -import com.evolveum.midpoint.schema.constants.AuditLocalizationConstants; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.RunningTask; -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.util.QNameUtil; -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.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardWidgetPresentationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardWidgetSourceTypeType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardWidgetType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.DisplayType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExportType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.GuiObjectColumnType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.GuiObjectListViewType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectCollectionType; -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.OrgType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportEngineSelectionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ServiceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskPartitionDefinitionType; -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.ItemPathType; - -import j2html.TagCreator; -import j2html.tags.ContainerTag; - -/** - * @author skublik - */ - -@Component -public class ReportHTMLCreateTaskHandler extends ReportJasperCreateTaskHandler { - - static final String REPORT_HTML_CREATE_TASK_URI = "http://midpoint.evolveum.com/xml/ns/public/report/html/create/handler-3"; - private static final Trace LOGGER = TraceManager.getTrace(ReportHTMLCreateTaskHandler.class); - - private static final String REPORT_CSS_STYLE_FILE_NAME = "dashboard-report-style.css"; - - private static final String LABEL_COLUMN = "label"; - private static final String NUMBER_COLUMN = "number"; - private static final String STATUS_COLUMN = "status"; - - private static final String TIME_COLUMN = "time"; - private static final String INITIATOR_COLUMN = "initiator"; - private static final String EVENT_STAGE_COLUMN = "eventStage"; - private static final String EVENT_TYPE_COLUMN = "eventType"; - private static final String TARGET_COLUMN = "target"; - private static final String DELTA_COLUMN = "delta"; - private static final String MESSAGE_COLUMN = "message"; - private static final String TARGET_OWNER_COLUMN = "targetOwner"; - private static final String CHANNEL_COLUMN = "channel"; - private static final String OUTCOME_COLUMN = "outcome"; - private static final String TASK_OID_COLUMN = "taskOid"; - private static final String NODE_IDENTIFIER_COLUMN = "nodeIdentifier"; - private static final String ATTORNEY_COLUMN = "attorney"; - private static final String RESULT_COLUMN = "result"; - private static final String RESOURCE_OID_COLUMN = "resourceOid"; - -// private static final String NAME_COLUMN = "Name"; -// private static final String CONNECTOR_TYPE_COLUMN = "Connector type"; -// private static final String VERSION_COLUMN = "Version"; -// private static final String GIVEN_NAME_COLUMN = "Given name"; -// private static final String FAMILY_NAME_COLUMN = "Family name"; -// private static final String FULL_NAME_COLUMN = "Full name"; -// private static final String EMAIL_COLUMN = "Email"; -// private static final String ACCOUNTS_COLUMN = "Accounts"; -// private static final String DISPLAY_NAME_COLUMN = "Display name"; -// private static final String DESCRIPTION_COLUMN = "Description"; -// private static final String IDENTIFIER_COLUMN = "Identifier"; -// private static final String CATEGORY_COLUMN = "Category"; -// private static final String OBJECT_REFERENCE_COLUMN = "Object reference"; -// private static final String EXECUTION_COLUMN = "Execution"; -// private static final String EXECUTING_AT_COLUMN = "Executing at"; -// private static final String PROGRES_COLUMN = "Progress"; -// private static final String CURRENT_RUN_TIME_COLUMN = "Current run time"; -// private static final String SCHEDULED_TO_START_AGAIN_COLUMN = "Scheduled to start again"; - - private static final String NAME_COLUMN = "name"; - private static final String CONNECTOR_TYPE_COLUMN = "connectorType"; - private static final String CONNECTOR_VERSION_COLUMN = "connectorVersion"; - private static final String GIVEN_NAME_COLUMN = "givenName"; - private static final String FAMILY_NAME_COLUMN = "familyName"; - private static final String FULL_NAME_COLUMN = "fullName"; - private static final String EMAIL_COLUMN = "email"; - private static final String ACCOUNTS_COLUMN = "accounts"; - private static final String DISPLAY_NAME_COLUMN = "displayName"; - private static final String DESCRIPTION_COLUMN = "description"; - private static final String IDENTIFIER_COLUMN = "identifier"; - private static final String CATEGORY_COLUMN = "category"; - private static final String OBJECT_REFERENCE_COLUMN = "objectReference"; - private static final String EXECUTION_COLUMN = "execution"; - private static final String EXECUTING_AT_COLUMN = "executingAt"; - private static final String PROGRES_COLUMN = "progress"; - private static final String CURRENT_RUN_TIME_COLUMN = "currentRunTime"; - - private static final String REPORT_GENERATED_ON = "Widget.generatedOn"; - private static final String NUMBER_OF_RECORDS = "Widget.numberOfRecords"; - - private static final String UNDEFINED_NAME = "Widget.column.undefinedName"; - - private static final QName CUSTOM = new QName("customPerformedColumn"); - - @Autowired private Clock clock; - @Autowired private TaskManager taskManager; - @Autowired private AuditService auditService; - @Autowired private ReportService reportService; - @Autowired private ModelService modelService; - @Autowired private PrismContext prismContext; - @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; - @Autowired private DashboardService dashboardService; - @Autowired private LocalizationService localizationService; - @Autowired private ExpressionFactory expressionFactory; - - private static LinkedHashMap, LinkedHashMap> columnDef; - private static Set headsOfWidget; - private static Set headsOfAuditEventRecords; - private static HashMap specialKeyLocalization; - - static { - columnDef = new LinkedHashMap, LinkedHashMap>() { - private static final long serialVersionUID = 1L; - { - put(ResourceType.class, new LinkedHashMap() { - private static final long serialVersionUID = 1L; - { - put(NAME_COLUMN, ResourceType.F_NAME); - put(CONNECTOR_TYPE_COLUMN, - ItemPath.create(ResourceType.F_CONNECTOR_REF, ConnectorType.F_CONNECTOR_TYPE)); - put(CONNECTOR_VERSION_COLUMN, - ItemPath.create(ResourceType.F_CONNECTOR_REF, ConnectorType.F_CONNECTOR_VERSION)); - } - }); - - put(UserType.class, new LinkedHashMap() { - private static final long serialVersionUID = 1L; - { - put(NAME_COLUMN, UserType.F_NAME); - put(GIVEN_NAME_COLUMN, UserType.F_GIVEN_NAME); - put(FAMILY_NAME_COLUMN, UserType.F_FAMILY_NAME); - put(FULL_NAME_COLUMN, UserType.F_FULL_NAME); - put(EMAIL_COLUMN, UserType.F_EMAIL_ADDRESS); - put(ACCOUNTS_COLUMN, ItemPath.create(AbstractRoleType.F_LINK_REF, CUSTOM)); - } - }); - - put(AbstractRoleType.class, new LinkedHashMap() { - private static final long serialVersionUID = 1L; - { - put(NAME_COLUMN, AbstractRoleType.F_NAME); - put(DISPLAY_NAME_COLUMN, AbstractRoleType.F_DISPLAY_NAME); - put(DESCRIPTION_COLUMN, AbstractRoleType.F_DESCRIPTION); - put(IDENTIFIER_COLUMN, AbstractRoleType.F_IDENTIFIER); - put(ACCOUNTS_COLUMN, ItemPath.create(AbstractRoleType.F_LINK_REF, CUSTOM)); - } - }); - - put(TaskType.class, new LinkedHashMap() { - private static final long serialVersionUID = 1L; - { - put(NAME_COLUMN, TaskType.F_NAME); - put(CATEGORY_COLUMN, TaskType.F_CATEGORY); - put(OBJECT_REFERENCE_COLUMN, TaskType.F_OBJECT_REF); - put(EXECUTION_COLUMN, TaskType.F_EXECUTION_STATUS); - put(EXECUTING_AT_COLUMN, TaskType.F_NODE_AS_OBSERVED); - put(PROGRES_COLUMN, TaskType.F_PROGRESS); - put(CURRENT_RUN_TIME_COLUMN, ItemPath.create(CUSTOM)); - put(STATUS_COLUMN, TaskType.F_RESULT_STATUS); - } - }); - } - - }; - - headsOfWidget = new LinkedHashSet() { - { - add(LABEL_COLUMN); - add(NUMBER_COLUMN); - add(STATUS_COLUMN); - } - }; - - headsOfAuditEventRecords = new LinkedHashSet() { - { - add(TIME_COLUMN); - add(INITIATOR_COLUMN); - add(EVENT_STAGE_COLUMN); - add(EVENT_TYPE_COLUMN); - add(TARGET_COLUMN); -// add(TARGET_OWNER_COLUMN); -// add(CHANNEL_COLUMN); - add(OUTCOME_COLUMN); - add(MESSAGE_COLUMN); - add(DELTA_COLUMN); - } - }; - - specialKeyLocalization = new HashMap() { - { - put(CONNECTOR_TYPE_COLUMN, "ConnectorType.connectorType"); - put(CONNECTOR_VERSION_COLUMN, "ConnectorType.connectorVersion"); - put(TIME_COLUMN, AuditLocalizationConstants.TIME_COLUMN_KEY); - put(INITIATOR_COLUMN, AuditLocalizationConstants.INITIATOR_COLUMN_KEY); - put(EVENT_STAGE_COLUMN, AuditLocalizationConstants.EVENT_STAGE_COLUMN_KEY); - put(EVENT_TYPE_COLUMN, AuditLocalizationConstants.EVENT_TYPE_COLUMN_KEY); - put(TARGET_COLUMN, AuditLocalizationConstants.TARGET_COLUMN_KEY); - put(DELTA_COLUMN, AuditLocalizationConstants.DELTA_COLUMN_KEY); - put(MESSAGE_COLUMN, AuditLocalizationConstants.MESSAGE_COLUMN_KEY); - put(TARGET_OWNER_COLUMN, AuditLocalizationConstants.TARGET_OWNER_COLUMN_KEY); - put(CHANNEL_COLUMN, AuditLocalizationConstants.CHANNEL_COLUMN_KEY); - put(OUTCOME_COLUMN, AuditLocalizationConstants.OUTCOME_COLUMN_KEY); - put(TASK_OID_COLUMN, AuditLocalizationConstants.TASK_OID_COLUMN_KEY); - put(NODE_IDENTIFIER_COLUMN, AuditLocalizationConstants.NODE_IDENTIFIER_COLUMN_KEY); - put(ATTORNEY_COLUMN, AuditLocalizationConstants.ATTORNEY_COLUMN_KEY); - put(RESULT_COLUMN, AuditLocalizationConstants.RESULT_COLUMN_KEY); - put(RESOURCE_OID_COLUMN, AuditLocalizationConstants.RESOURCE_OID_COLUMN_KEY); - } - }; - } - - @Override - protected void initialize() { - LOGGER.trace("Registering with taskManager as a handler for {}", REPORT_HTML_CREATE_TASK_URI); - taskManager.registerHandler(REPORT_HTML_CREATE_TASK_URI, this); - } - - @Override - public TaskRunResult run(RunningTask task, TaskPartitionDefinitionType partition) { - OperationResult parentResult = task.getResult(); - OperationResult result = parentResult - .createSubresult(ReportHTMLCreateTaskHandler.class.getSimpleName() + ".run"); - TaskRunResult runResult = new TaskRunResult(); - runResult.setOperationResult(result); - super.recordProgress(task, 0, result); - try { - ReportType parentReport = objectResolver.resolve(task.getObjectRefOrClone(), ReportType.class, null, - "resolving report", task, result); - - if (!reportService.isAuthorizedToRunReport(parentReport.asPrismObject(), task, parentResult)) { - LOGGER.error("Task {} is not authorized to run report {}", task, parentReport); - throw new SecurityViolationException("Not authorized"); - } - - if (parentReport.getReportEngine() == null) { - throw new IllegalArgumentException("Report Object doesn't have ReportEngine attribute"); - } - if (parentReport.getReportEngine().equals(ReportEngineSelectionType.JASPER)) { - parentReport.setExport(ExportType.HTML); - return super.run(task, partition); - - } else if (parentReport.getReportEngine().equals(ReportEngineSelectionType.DASHBOARD)) { - - if (parentReport.getDashboard() != null && parentReport.getDashboard().getDashboardRef() != null) { - ObjectReferenceType ref = parentReport.getDashboard().getDashboardRef(); - Class type = prismContext.getSchemaRegistry().determineClassForType(ref.getType()); - Task taskSearchDashboard = taskManager.createTaskInstance("Search dashboard"); - DashboardType dashboard = (DashboardType) modelService - .getObject(type, ref.getOid(), null, taskSearchDashboard, taskSearchDashboard.getResult()) - .asObjectable(); - ClassLoader classLoader = getClass().getClassLoader(); - InputStream in = classLoader.getResourceAsStream(REPORT_CSS_STYLE_FILE_NAME); - if (in == null) { - throw new IllegalStateException("Resource " + REPORT_CSS_STYLE_FILE_NAME + " couldn't be found"); - } - byte[] data = IOUtils.toByteArray(in); - String style = new String(data, Charset.defaultCharset()); - - String reportFilePath = getDestinationFileName(parentReport); - FileUtils.writeByteArrayToFile(new File(reportFilePath), getBody(dashboard, style, task, result).getBytes()); - super.saveReportOutputType(reportFilePath, parentReport, task, result); - LOGGER.trace("create report output type : {}", reportFilePath); - - if (parentReport.getPostReportScript() != null) { - super.processPostReportScript(parentReport, reportFilePath, task, result); - } - result.computeStatus(); - } else { - LOGGER.error("Dashboard or DashboardRef is null"); - throw new IllegalArgumentException("Dashboard or DashboardRef is null"); - } - - } - } catch (Exception ex) { - LOGGER.error("CreateReport: {}", ex.getMessage(), ex); - result.recordFatalError(ex.getMessage(), ex); - runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); - return runResult; - } - - // This "run" is finished. But the task goes on ... - runResult.setRunResultStatus(TaskRunResultStatus.FINISHED); - LOGGER.trace("CreateReportTaskHandler.run stopping"); - return runResult; - } - - private String getBody(DashboardType dashboard, String cssStyle, Task task, OperationResult result) throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, ObjectNotFoundException { - StringBuilder body = new StringBuilder(); - body.append("

"); - - ContainerTag widgetTable = createTable(); - widgetTable.with(createTHead("Widget.", getHeadsOfWidget())); - - ContainerTag widgetTBody = TagCreator.tbody(); - List tableboxesFromWidgets = new ArrayList<>(); - long startMillis = clock.currentTimeMillis(); - for (DashboardWidgetType widget : dashboard.getWidget()) { - DashboardWidget widgetData = dashboardService.createWidgetData(widget, task, result); - widgetTBody.with(createTBodyRow(widgetData)); - ContainerTag tableBox = createTableBoxForWidget(widgetData, task, result); - if (tableBox != null) { - tableboxesFromWidgets.add(tableBox); - } - } - widgetTable.with(widgetTBody); - - body.append(createTableBox(widgetTable, "Widgets", dashboard.getWidget().size(), - convertMillisToString(startMillis), null).render()); - appendSpace(body); - tableboxesFromWidgets.forEach(table -> { - body.append(table.render()); - appendSpace(body); - }); - body.append("
"); - - return body.toString(); - } - - private String convertMillisToString(long millis) { - SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d. MMM yyyy HH:mm:ss", Locale.getDefault()); - return dateFormat.format(millis); - } - - private void appendSpace(StringBuilder body) { - body.append("
"); - } - - private ContainerTag createTableBoxForWidget(DashboardWidget widgetData, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - DashboardWidgetType widget = widgetData.getWidget(); - if (widget == null) { - throw new IllegalArgumentException("Widget in DashboardWidget is null"); - } - DashboardWidgetPresentationType presentation = widget.getPresentation(); - DashboardWidgetSourceTypeType sourceType = DashboardUtils.getSourceType(widget); - if (sourceType == null) { - throw new IllegalStateException("No source type specified in " + widget); - } - switch (sourceType) { - case OBJECT_COLLECTION: - if (!DashboardUtils.isDataFieldsOfPresentationNullOrEmpty(presentation)) { - ObjectCollectionType collection = dashboardService.getObjectCollectionType(widget, task, result); - long startMillis = clock.currentTimeMillis(); - List> values = dashboardService.searchObjectFromCollection(collection, true, task, result); - if (values == null || values.isEmpty()) { - return null; - } - ContainerTag table = createTable(); - PrismObjectDefinition def = values.get(0).getDefinition(); - //noinspection unchecked - Class type = (Class) prismContext.getSchemaRegistry() - .getCompileTimeClassForObjectType(collection.getType()); - DisplayType display = null; - if(!useDefaultColumn(widget)) { - table = createTable(widget.getPresentation().getView(), values, def, type, - task, result); - display = widget.getPresentation().getView().getDisplay(); - } else { - table = createTable(values, def, type, task, result); - } - return createTableBox(table, widgetData.getLabel(), values.size(), - convertMillisToString(startMillis), display); - } - break; - case AUDIT_SEARCH: - if (!DashboardUtils.isDataFieldsOfPresentationNullOrEmpty(presentation)) { - Map parameters = new HashMap<>(); - - ObjectCollectionType collection = dashboardService.getObjectCollectionType(widget, task, result); - long startMillis = clock.currentTimeMillis(); - String query = DashboardUtils - .getQueryForListRecords(DashboardUtils.createQuery(collection, parameters, false, clock)); - List records = auditService.listRecords(query, parameters, result); - if (records == null || records.isEmpty()) { - return null; - } - - ContainerTag table = createTable(); - DisplayType display = null; - if(!useDefaultColumn(widget)) { - table = createTable(widget.getPresentation().getView(), records, task, result); - display = widget.getPresentation().getView().getDisplay(); - } else { - table.with(createTHead(getHeadsOfAuditEventRecords())); - ContainerTag tBody = TagCreator.tbody(); - records.forEach(record -> { - ContainerTag tr = TagCreator.tr(); - getHeadsOfAuditEventRecords().forEach(column -> { - tr.with(TagCreator.th(TagCreator.div(getStringForColumnOfAuditRecord(column, record, null)).withStyle("white-space: pre-wrap"))); - }); - tBody.with(tr); - }); - table.with(tBody); - } - return createTableBox(table, widgetData.getLabel(), records.size(), - convertMillisToString(startMillis), null); - } - break; - } - return null; - } - - private boolean useDefaultColumn(DashboardWidgetType widget) { - return widget.getPresentation() == null || widget.getPresentation().getView() == null - || widget.getPresentation().getView().getColumn() == null || widget.getPresentation().getView().getColumn().isEmpty(); - } - - private String getStringForColumnOfAuditRecord(String column, AuditEventRecord record, ItemPathType path, - ExpressionType expression, Task task, OperationResult result) { - if(expression == null) { - return getStringForColumnOfAuditRecord(column, record, path); - } - Object object = null; - switch (column) { - case TIME_COLUMN: - object = record.getTimestamp(); - break; - case INITIATOR_COLUMN: - object = record.getInitiator(); - break; - case EVENT_STAGE_COLUMN: - object = record.getEventStage(); - break; - case EVENT_TYPE_COLUMN: - object = record.getEventType(); - break; - case TARGET_COLUMN: - object = record.getTarget(); - break; - case TARGET_OWNER_COLUMN: - object = record.getTargetOwner(); - break; - case CHANNEL_COLUMN: - object = record.getChannel(); - break; - case OUTCOME_COLUMN: - object = record.getOutcome(); - break; - case MESSAGE_COLUMN: - object = record.getMessage(); - break; - case DELTA_COLUMN: - object = record.getDeltas(); - break; - case TASK_OID_COLUMN: - object = record.getTaskOid(); - break; - case NODE_IDENTIFIER_COLUMN: - object = record.getNodeIdentifier(); - break; - case ATTORNEY_COLUMN: - object = record.getAttorney(); - break; - case RESULT_COLUMN: - object = record.getResult(); - break; - case RESOURCE_OID_COLUMN: - object = record.getResourceOids(); - break; - default: - if(record.getCustomColumnProperty().containsKey(path)) { - object = record.getCustomColumnProperty().get(path); - } else { - LOGGER.error("Unknown name of column for AuditReport " + column); - } - break; - } - return evaluateExpression(expression, object, task, result); - } - - private String getStringForColumnOfAuditRecord(String column, AuditEventRecord record, ItemPathType path) { - switch (column) { - case TIME_COLUMN: - SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d. MMM yyyy HH:mm:ss", Locale.US); - return dateFormat.format(record.getTimestamp()); - case INITIATOR_COLUMN: - return record.getInitiator() == null ? "" : record.getInitiator().getName().getOrig(); - case EVENT_STAGE_COLUMN: - return record.getEventStage() == null ? "" : getMessage(record.getEventStage()); - case EVENT_TYPE_COLUMN: - return record.getEventType() == null ? "" : getMessage(record.getEventType()); - case TARGET_COLUMN: - return record.getTarget() == null ? "" : getObjectNameFromRef(record.getTarget().getRealValue()); - case TARGET_OWNER_COLUMN: - return record.getTargetOwner() == null ? "" :record.getTargetOwner().getName().getOrig(); - case CHANNEL_COLUMN: - return record.getChannel() == null ? "" : QNameUtil.uriToQName(record.getChannel()).getLocalPart(); - case OUTCOME_COLUMN: - return record.getOutcome() == null ? "" : getMessage(record.getOutcome()); - case MESSAGE_COLUMN: - return record.getMessage() == null ? "" : record.getMessage(); - case DELTA_COLUMN: - if (record.getDeltas() == null || record.getDeltas().isEmpty()) { - return ""; - } - StringBuilder sbDelta = new StringBuilder(); - Collection> deltas = record.getDeltas(); - Iterator> iterator = deltas.iterator(); - int index = 0; - while (iterator.hasNext()) { - ObjectDeltaOperation delta = iterator.next(); - sbDelta.append(ReportUtils.printDelta(delta)); - if ((index+1)!=deltas.size()) { - sbDelta.append("\n"); - } - index++; - } - return sbDelta.toString(); - case TASK_OID_COLUMN: - return record.getTaskOid() == null ? "" : record.getTaskOid(); - case NODE_IDENTIFIER_COLUMN: - return record.getNodeIdentifier() == null ? "" : record.getNodeIdentifier(); - case ATTORNEY_COLUMN: - return record.getAttorney() == null ? "" : record.getAttorney().getName().getOrig(); - case RESULT_COLUMN: - return record.getResult() == null ? "" : record.getResult(); - case RESOURCE_OID_COLUMN: - Set resourceOids = record.getResourceOids(); - if(resourceOids == null || resourceOids.isEmpty()) { - return ""; - } - StringBuilder sb = new StringBuilder(); - int i = 1; - for(String oid : resourceOids) { - sb.append(oid); - if(i != resourceOids.size()) { - sb.append("\n"); - } - i++; - } - return sb.toString(); - default: - if(record.getCustomColumnProperty().containsKey(path)) { - return record.getCustomColumnProperty().get(path); - } - } - - return ""; - - } - - private String getObjectNameFromRef(Referencable ref) { - if (ref == null) { - return ""; - } - if (ref.getTargetName() != null && ref.getTargetName().getOrig() != null) { - return ref.getTargetName().getOrig(); - } - PrismObject object = getObjectFromReference(ref); - - if (object == null) { - return ref.getOid(); - } - - if (object.getName() == null || object.getName().getOrig() == null) { - return ""; - } - return object.getName().getOrig(); - } - - private String getRealValueAsString(String nameOfColumn, PrismObject object, ItemPath itemPath, - ExpressionType expression, Task task, OperationResult result) { - Iterator iterator = (Iterator) itemPath.getSegments().iterator(); - Item valueObject = object; - - - while (iterator.hasNext()) { - QName name = iterator.next(); - if (QNameUtil.match(name, CUSTOM)) { - return getCustomValueForColumn(valueObject, nameOfColumn); - } - valueObject = (Item) valueObject.find(ItemPath.create(name)); - if (valueObject instanceof PrismProperty && iterator.hasNext()) { - throw new IllegalArgumentException("Found object is PrismProperty, but ItemPath isn't empty"); - } - if (expression == null && valueObject instanceof PrismContainer && !iterator.hasNext()) { - throw new IllegalArgumentException("Found object is PrismContainer, but ItemPath is empty"); - } - if (valueObject instanceof PrismReference) { - Referencable ref = ((PrismReference) valueObject).getRealValue(); - if (!iterator.hasNext()) { - if(expression == null) { - return getObjectNameFromRef(ref); - } - return evaluateExpression(expression, valueObject, task, result); - } - - valueObject = getObjectFromReference(ref); - } - if (valueObject == null) { - if(nameOfColumn.equals(ACCOUNTS_COLUMN)) { - return "0"; - } - return ""; - } - } - if(expression != null) { - return evaluateExpression(expression, valueObject, task, result); - } - Object realValue = ((PrismProperty) valueObject).getRealValue(); - if (realValue == null){ - return ""; - } else if (realValue instanceof Enum) { - return getMessage((Enum)realValue); - } - return realValue.toString(); - } - - private String evaluateExpression(ExpressionType expression, Item valueObject, Task task, OperationResult result) { - Object object; - if(valueObject == null) { - object = null; - } else { - object = valueObject.getRealValue(); - } - return evaluateExpression(expression, object, task, result); - } - -private String evaluateExpression(ExpressionType expression, Object valueObject, Task task, OperationResult result) { - - ExpressionVariables variables = new ExpressionVariables(); - if(valueObject == null) { - variables.put(ExpressionConstants.VAR_OBJECT, null, Object.class); - } else { - variables.put(ExpressionConstants.VAR_OBJECT, valueObject, valueObject.getClass()); - } - Collection values = null; - try { - values = ExpressionUtil.evaluateStringExpression(variables, prismContext, expression, null, expressionFactory, "value for column", task, result); - } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException - | ConfigurationException | SecurityViolationException e) { - LOGGER.error("Couldn't execute expression " + expression, e); - } - if (values == null || values.isEmpty()){ - return ""; - } - if(values.size() != 1) { - throw new IllegalArgumentException("Expected collection with one value, but it is " + values); - } - return values.iterator().next(); - } - - private boolean isCustomPerformedColumn(ItemPath itemPath) { - List segments = itemPath.getSegments(); - return segments.size() > 0 && QNameUtil.match((QName)segments.get(segments.size() - 1), CUSTOM); - } - - private String getCustomValueForColumn(Item valueObject, String nameOfColumn) { - switch (nameOfColumn) { - case ACCOUNTS_COLUMN: - if(!(valueObject instanceof PrismObject)){ - return ""; - } - return String.valueOf(((PrismObject)valueObject).getRealValues().size()); - case CURRENT_RUN_TIME_COLUMN: - if(!(valueObject instanceof PrismObject) - && !(((PrismObject)valueObject).getRealValue() instanceof TaskType)){ - return ""; - } - TaskType task = (TaskType)((PrismObject)valueObject).getRealValue(); - XMLGregorianCalendar timestapm = task.getCompletionTimestamp(); - if(timestapm != null && task.getExecutionStatus().equals(TaskExecutionStatusType.CLOSED)) { - SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d. MMM yyyy HH:mm:ss", Locale.US); - return "closed at " + dateFormat.format(task.getCompletionTimestamp().toGregorianCalendar().getTime()); - } - return ""; - } - return ""; - } - - private PrismObject getObjectFromReference(Referencable ref) { - Task task = taskManager.createTaskInstance("Get object"); - Class type = prismContext.getSchemaRegistry().determineClassForType(ref.getType()); - - if (ref.asReferenceValue().getObject() != null) { - return ref.asReferenceValue().getObject(); - } - - PrismObject object = null; - try { - object = modelService.getObject(type, ref.getOid(), null, task, task.getResult()); - } catch (Exception e) { - LOGGER.error("Couldn't get object from objectRef " + ref, e); - } - return object; - } - - private ContainerTag createTable() { - return TagCreator.table().withClasses("table", "table-striped", "table-hover", "table-bordered"); - } - - private ContainerTag createTableBox(ContainerTag table, String nameOfTable, int countOfTableRecords, - String createdTime, DisplayType display) { - ContainerTag div = TagCreator.div().withClasses("box-body", "no-padding").with(TagCreator.h1(nameOfTable)) - .with(TagCreator.p(getMessage(REPORT_GENERATED_ON, createdTime))) - .with(TagCreator.p(getMessage(NUMBER_OF_RECORDS, countOfTableRecords))).with(table); - String style = ""; - String classes = ""; - if(display != null) { - if(display.getCssStyle() != null) { - style = display.getCssStyle(); - } - if(display.getCssClass() != null) { - classes = display.getCssClass(); - } - } - return TagCreator.div().withClasses("box", "boxed-table", classes).withStyle(style).with(div); - } - - private ContainerTag createTable(GuiObjectListViewType view, List records, Task task, - OperationResult result) { - ContainerTag table = createTable(); - ContainerTag tHead = TagCreator.thead(); - ContainerTag tBody = TagCreator.tbody(); - List columns = ModelImplUtils.orderCustomColumns(view.getColumn()); - ContainerTag trForHead = TagCreator.tr().withStyle("width: 100%;"); - columns.forEach(column -> { - Validate.notNull(column.getName(), "Name of column is null"); - - DisplayType columnDisplay = column.getDisplay(); - String label; - if(columnDisplay != null && columnDisplay.getLabel() != null) { - label = getMessage(columnDisplay.getLabel().getOrig()); - } else if (specialKeyLocalization.containsKey(column.getName())) { - label = getMessage(specialKeyLocalization.get(column.getName())); - } else { - label = column.getName(); - } - ContainerTag th = TagCreator.th(TagCreator.div(TagCreator.span(label).withClass("sortableLabel"))); - if(columnDisplay != null) { - if(StringUtils.isNotBlank(columnDisplay.getCssClass())) { - th.withClass(columnDisplay.getCssClass()); - } - if(StringUtils.isNotBlank(columnDisplay.getCssStyle())) { - th.withStyle(columnDisplay.getCssStyle()); - } - } - trForHead.with(th); - }); - tHead.with(trForHead); - table.with(tHead); - - records.forEach(record -> { - ContainerTag tr = TagCreator.tr(); - columns.forEach(column -> { - ExpressionType expression = column.getExpression(); - tr.with(TagCreator - .th(TagCreator.div(getStringForColumnOfAuditRecord(column.getName(), record, column.getPath(), expression, task, result)).withStyle("white-space: pre-wrap"))); - }); - tBody.with(tr); - }); - table.with(tBody); - return table; - } - - private ContainerTag createTable(GuiObjectListViewType view, List> values, - PrismObjectDefinition def, Class type, - Task task, OperationResult result) { - ContainerTag table = createTable(); - ContainerTag tHead = TagCreator.thead(); - ContainerTag tBody = TagCreator.tbody(); - List columns = ModelImplUtils.orderCustomColumns(view.getColumn()); - ContainerTag trForHead = TagCreator.tr().withStyle("width: 100%;"); - columns.forEach(column -> { - Validate.notNull(column.getName(), "Name of column is null"); - ItemPath path = ItemPath.create(column.getPath()); - if(path == null) { - LinkedHashMap columnForType = getColumnsForType(type); - if (columnForType == null) { - LOGGER.error("Couldn't found default columns for class {}", type.getName()); - } - path = columnForType.get(column.getName()); - if(path == null) { - LOGGER.error("Couldn't found path for column {} in default columns for class {}", column.getName(), type.getName()); - } - } - - DisplayType columnDisplay = column.getDisplay(); - String label; - if(columnDisplay != null && columnDisplay.getLabel() != null) { - label = getMessage(columnDisplay.getLabel().getOrig()); - } else { - - label = getColumnLabel(column.getName(), def, path); - } - ContainerTag th = TagCreator.th(TagCreator.div(TagCreator.span(label).withClass("sortableLabel"))); - if(columnDisplay != null) { - if(StringUtils.isNotBlank(columnDisplay.getCssClass())) { - th.withClass(columnDisplay.getCssClass()); - } - if(StringUtils.isNotBlank(columnDisplay.getCssStyle())) { - th.withStyle(columnDisplay.getCssStyle()); - } - } - trForHead.with(th); - }); - tHead.with(trForHead); - table.with(tHead); - - values.forEach(value -> { - ContainerTag tr = TagCreator.tr(); - columns.forEach(column -> { - ItemPath path = ItemPath.create(column.getPath()); - if(path == null) { - path = getColumnsForType(type).get(column.getName()); - } - ExpressionType expression = column.getExpression(); - tr.with(TagCreator - .th(TagCreator.div(getRealValueAsString(column.getName(), value, path, expression, task, result)).withStyle("white-space: pre-wrap"))); - }); - tBody.with(tr); - }); - table.with(tBody); - return table; - } - - private ContainerTag createTable(List> values, PrismObjectDefinition def, - Class type, Task task, OperationResult result) { - HashMap columns = getColumnsForType(type); - if (columns == null) { - LOGGER.error("Couldn't create table for objects with class {}", type.getName()); - } - ContainerTag table = createTable(); - table.with(createTHead(columns, def)); - ContainerTag tBody = TagCreator.tbody(); - values.forEach(value -> { - ContainerTag tr = TagCreator.tr(); - columns.keySet().forEach(column -> { - tr.with(TagCreator - .th(TagCreator.div(getRealValueAsString(column, value, columns.get(column), null, task, result)).withStyle("white-space: pre-wrap"))); - }); - tBody.with(tr); - }); - table.with(tBody); - return table; - } - - private String getLabelNameForCustom(String nameOfColumn) { - String key = ""; - switch (nameOfColumn) { - case ACCOUNTS_COLUMN: - key = "FocusType.accounts"; - case CURRENT_RUN_TIME_COLUMN: - key = "TaskType.currentRunTime"; - } - return getMessage(key); - } - - private ContainerTag createTHead(Set set) { - return TagCreator.thead(TagCreator.tr(TagCreator.each(set, - header -> TagCreator.th(TagCreator.div(TagCreator.span(getMessage(specialKeyLocalization.get(header))).withClass("sortableLabel"))) - .withStyle(getStyleForColumn(header)))).withStyle("width: 100%;")); - } - - private ContainerTag createTHead(String prefix, Set set) { - return TagCreator.thead(TagCreator.tr(TagCreator.each(set, - header -> TagCreator.th(TagCreator.div(TagCreator.span(getMessage(prefix+header)).withClass("sortableLabel"))) - .withStyle(getStyleForColumn(header)))).withStyle("width: 100%;")); - } - - private ContainerTag createTHead(HashMap columns, PrismObjectDefinition objectDefinition) { - ContainerTag tr = TagCreator.tr().withStyle("width: 100%;"); - columns.keySet().forEach(columnName -> { - tr.with(TagCreator.th(TagCreator.div(TagCreator.span(getColumnLabel(columnName, objectDefinition, columns.get(columnName))) - .withClass("sortableLabel")))); - }); - return TagCreator.thead(tr); - } - - private String getColumnLabel(String name, PrismObjectDefinition objectDefinition, ItemPath path) { - if(specialKeyLocalization.containsKey(name)) { - return getMessage(specialKeyLocalization.get(name)); - } - if(isCustomPerformedColumn(path)) { - return getLabelNameForCustom(name); - } - ItemDefinition def = objectDefinition.findItemDefinition(path); - if(def == null) { - throw new IllegalArgumentException("Could'n find item for path " + path); - } - String displayName = def.getDisplayName(); - return getMessage(displayName); - } - - private String getStyleForColumn(String column) { - switch (column) { - case TIME_COLUMN: - return "width: 10%;"; - case INITIATOR_COLUMN: - return "width: 8%;"; - case EVENT_STAGE_COLUMN: - return "width: 5%;"; - case EVENT_TYPE_COLUMN: - return "width: 10%;"; - case TARGET_COLUMN: - return "width: 8%;"; - case OUTCOME_COLUMN: - return "width: 7%;"; - case DELTA_COLUMN: - return "width: 35%;"; - } - return ""; - } - - private ContainerTag createTBodyRow(DashboardWidget data) { - return TagCreator.tr(TagCreator.each(getHeadsOfWidget(), header -> getContainerTagForWidgetHeader(header, data) - - )); - - } - - private ContainerTag getContainerTagForWidgetHeader(String header, DashboardWidget data) { - if (header.equals(LABEL_COLUMN)) { - ContainerTag div = TagCreator.div(data.getLabel()); - return TagCreator.th().with(div); - } - if (header.equals(NUMBER_COLUMN)) { - ContainerTag div = TagCreator.div(data.getNumberMessage()); - return TagCreator.th().with(div); - } - if (header.equals(STATUS_COLUMN)) { - ContainerTag div = TagCreator.div().withStyle("width: 100%; height: 20px; "); - ContainerTag th = TagCreator.th(); - addStatusColor(th, data.getDisplay()); - th.with(div); - return th; - } - return TagCreator.th(); - } - - private void addStatusColor(ContainerTag div, DisplayType display) { - if (display != null && StringUtils.isNoneBlank(display.getColor())) { - div.withStyle("background-color: " + display.getColor() + "; !important;"); - } - - } - - private Set getHeadsOfWidget() { - return headsOfWidget; - } - - private static Set getHeadsOfAuditEventRecords() { - return headsOfAuditEventRecords; - } - - private static LinkedHashMap, LinkedHashMap> getColumnDef() { - return columnDef; - } - - private static LinkedHashMap getColumnsForType(Class type ) { - if(type.equals(RoleType.class) - || type.equals(OrgType.class) - || type.equals(ServiceType.class)) { - return getColumnDef().get(AbstractRoleType.class); - } - return getColumnDef().get(type); - } - - @Override - protected ExportType getExport(ReportType report) { - return ExportType.HTML; - } - - private String getMessage(Enum e) { - StringBuilder sb = new StringBuilder(); - sb.append(e.getDeclaringClass().getSimpleName()).append('.'); - sb.append(e.name()); - return getMessage(sb.toString()); - } - - private String getMessage(String key) { - return getMessage(key, null); - } - - private String getMessage(String key, Object... params) { - return localizationService.translate(key, params, Locale.getDefault(), key); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.impl; + +import java.io.File; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.report.api.ReportService; + +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.audit.api.AuditService; +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.api.interaction.DashboardService; +import com.evolveum.midpoint.model.api.interaction.DashboardWidget; +import com.evolveum.midpoint.model.api.util.DashboardUtils; +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.Referencable; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.schema.constants.AuditLocalizationConstants; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.RunningTask; +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.util.QNameUtil; +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.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardWidgetPresentationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardWidgetSourceTypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.DashboardWidgetType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.DisplayType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExportType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GuiObjectColumnType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GuiObjectListViewType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectCollectionType; +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.OrgType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportEngineSelectionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ServiceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskExecutionStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskPartitionDefinitionType; +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.ItemPathType; + +import j2html.TagCreator; +import j2html.tags.ContainerTag; + +/** + * @author skublik + */ + +@Component +public class ReportHTMLCreateTaskHandler extends ReportJasperCreateTaskHandler { + + static final String REPORT_HTML_CREATE_TASK_URI = "http://midpoint.evolveum.com/xml/ns/public/report/html/create/handler-3"; + private static final Trace LOGGER = TraceManager.getTrace(ReportHTMLCreateTaskHandler.class); + + private static final String REPORT_CSS_STYLE_FILE_NAME = "dashboard-report-style.css"; + + private static final String LABEL_COLUMN = "label"; + private static final String NUMBER_COLUMN = "number"; + private static final String STATUS_COLUMN = "status"; + + private static final String TIME_COLUMN = "time"; + private static final String INITIATOR_COLUMN = "initiator"; + private static final String EVENT_STAGE_COLUMN = "eventStage"; + private static final String EVENT_TYPE_COLUMN = "eventType"; + private static final String TARGET_COLUMN = "target"; + private static final String DELTA_COLUMN = "delta"; + private static final String MESSAGE_COLUMN = "message"; + private static final String TARGET_OWNER_COLUMN = "targetOwner"; + private static final String CHANNEL_COLUMN = "channel"; + private static final String OUTCOME_COLUMN = "outcome"; + private static final String TASK_OID_COLUMN = "taskOid"; + private static final String NODE_IDENTIFIER_COLUMN = "nodeIdentifier"; + private static final String ATTORNEY_COLUMN = "attorney"; + private static final String RESULT_COLUMN = "result"; + private static final String RESOURCE_OID_COLUMN = "resourceOid"; + +// private static final String NAME_COLUMN = "Name"; +// private static final String CONNECTOR_TYPE_COLUMN = "Connector type"; +// private static final String VERSION_COLUMN = "Version"; +// private static final String GIVEN_NAME_COLUMN = "Given name"; +// private static final String FAMILY_NAME_COLUMN = "Family name"; +// private static final String FULL_NAME_COLUMN = "Full name"; +// private static final String EMAIL_COLUMN = "Email"; +// private static final String ACCOUNTS_COLUMN = "Accounts"; +// private static final String DISPLAY_NAME_COLUMN = "Display name"; +// private static final String DESCRIPTION_COLUMN = "Description"; +// private static final String IDENTIFIER_COLUMN = "Identifier"; +// private static final String CATEGORY_COLUMN = "Category"; +// private static final String OBJECT_REFERENCE_COLUMN = "Object reference"; +// private static final String EXECUTION_COLUMN = "Execution"; +// private static final String EXECUTING_AT_COLUMN = "Executing at"; +// private static final String PROGRES_COLUMN = "Progress"; +// private static final String CURRENT_RUN_TIME_COLUMN = "Current run time"; +// private static final String SCHEDULED_TO_START_AGAIN_COLUMN = "Scheduled to start again"; + + private static final String NAME_COLUMN = "name"; + private static final String CONNECTOR_TYPE_COLUMN = "connectorType"; + private static final String CONNECTOR_VERSION_COLUMN = "connectorVersion"; + private static final String GIVEN_NAME_COLUMN = "givenName"; + private static final String FAMILY_NAME_COLUMN = "familyName"; + private static final String FULL_NAME_COLUMN = "fullName"; + private static final String EMAIL_COLUMN = "email"; + private static final String ACCOUNTS_COLUMN = "accounts"; + private static final String DISPLAY_NAME_COLUMN = "displayName"; + private static final String DESCRIPTION_COLUMN = "description"; + private static final String IDENTIFIER_COLUMN = "identifier"; + private static final String CATEGORY_COLUMN = "category"; + private static final String OBJECT_REFERENCE_COLUMN = "objectReference"; + private static final String EXECUTION_COLUMN = "execution"; + private static final String EXECUTING_AT_COLUMN = "executingAt"; + private static final String PROGRES_COLUMN = "progress"; + private static final String CURRENT_RUN_TIME_COLUMN = "currentRunTime"; + + private static final String REPORT_GENERATED_ON = "Widget.generatedOn"; + private static final String NUMBER_OF_RECORDS = "Widget.numberOfRecords"; + + private static final String UNDEFINED_NAME = "Widget.column.undefinedName"; + + private static final QName CUSTOM = new QName("customPerformedColumn"); + + @Autowired private Clock clock; + @Autowired private TaskManager taskManager; + @Autowired private AuditService auditService; + @Autowired private ReportService reportService; + @Autowired private ModelService modelService; + @Autowired private PrismContext prismContext; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; + @Autowired private DashboardService dashboardService; + @Autowired private LocalizationService localizationService; + @Autowired private ExpressionFactory expressionFactory; + + private static LinkedHashMap, LinkedHashMap> columnDef; + private static Set headsOfWidget; + private static Set headsOfAuditEventRecords; + private static HashMap specialKeyLocalization; + + static { + columnDef = new LinkedHashMap, LinkedHashMap>() { + private static final long serialVersionUID = 1L; + { + put(ResourceType.class, new LinkedHashMap() { + private static final long serialVersionUID = 1L; + { + put(NAME_COLUMN, ResourceType.F_NAME); + put(CONNECTOR_TYPE_COLUMN, + ItemPath.create(ResourceType.F_CONNECTOR_REF, ConnectorType.F_CONNECTOR_TYPE)); + put(CONNECTOR_VERSION_COLUMN, + ItemPath.create(ResourceType.F_CONNECTOR_REF, ConnectorType.F_CONNECTOR_VERSION)); + } + }); + + put(UserType.class, new LinkedHashMap() { + private static final long serialVersionUID = 1L; + { + put(NAME_COLUMN, UserType.F_NAME); + put(GIVEN_NAME_COLUMN, UserType.F_GIVEN_NAME); + put(FAMILY_NAME_COLUMN, UserType.F_FAMILY_NAME); + put(FULL_NAME_COLUMN, UserType.F_FULL_NAME); + put(EMAIL_COLUMN, UserType.F_EMAIL_ADDRESS); + put(ACCOUNTS_COLUMN, ItemPath.create(AbstractRoleType.F_LINK_REF, CUSTOM)); + } + }); + + put(AbstractRoleType.class, new LinkedHashMap() { + private static final long serialVersionUID = 1L; + { + put(NAME_COLUMN, AbstractRoleType.F_NAME); + put(DISPLAY_NAME_COLUMN, AbstractRoleType.F_DISPLAY_NAME); + put(DESCRIPTION_COLUMN, AbstractRoleType.F_DESCRIPTION); + put(IDENTIFIER_COLUMN, AbstractRoleType.F_IDENTIFIER); + put(ACCOUNTS_COLUMN, ItemPath.create(AbstractRoleType.F_LINK_REF, CUSTOM)); + } + }); + + put(TaskType.class, new LinkedHashMap() { + private static final long serialVersionUID = 1L; + { + put(NAME_COLUMN, TaskType.F_NAME); + put(CATEGORY_COLUMN, TaskType.F_CATEGORY); + put(OBJECT_REFERENCE_COLUMN, TaskType.F_OBJECT_REF); + put(EXECUTION_COLUMN, TaskType.F_EXECUTION_STATUS); + put(EXECUTING_AT_COLUMN, TaskType.F_NODE_AS_OBSERVED); + put(PROGRES_COLUMN, TaskType.F_PROGRESS); + put(CURRENT_RUN_TIME_COLUMN, ItemPath.create(CUSTOM)); + put(STATUS_COLUMN, TaskType.F_RESULT_STATUS); + } + }); + } + + }; + + headsOfWidget = new LinkedHashSet() { + { + add(LABEL_COLUMN); + add(NUMBER_COLUMN); + add(STATUS_COLUMN); + } + }; + + headsOfAuditEventRecords = new LinkedHashSet() { + { + add(TIME_COLUMN); + add(INITIATOR_COLUMN); + add(EVENT_STAGE_COLUMN); + add(EVENT_TYPE_COLUMN); + add(TARGET_COLUMN); +// add(TARGET_OWNER_COLUMN); +// add(CHANNEL_COLUMN); + add(OUTCOME_COLUMN); + add(MESSAGE_COLUMN); + add(DELTA_COLUMN); + } + }; + + specialKeyLocalization = new HashMap() { + { + put(CONNECTOR_TYPE_COLUMN, "ConnectorType.connectorType"); + put(CONNECTOR_VERSION_COLUMN, "ConnectorType.connectorVersion"); + put(TIME_COLUMN, AuditLocalizationConstants.TIME_COLUMN_KEY); + put(INITIATOR_COLUMN, AuditLocalizationConstants.INITIATOR_COLUMN_KEY); + put(EVENT_STAGE_COLUMN, AuditLocalizationConstants.EVENT_STAGE_COLUMN_KEY); + put(EVENT_TYPE_COLUMN, AuditLocalizationConstants.EVENT_TYPE_COLUMN_KEY); + put(TARGET_COLUMN, AuditLocalizationConstants.TARGET_COLUMN_KEY); + put(DELTA_COLUMN, AuditLocalizationConstants.DELTA_COLUMN_KEY); + put(MESSAGE_COLUMN, AuditLocalizationConstants.MESSAGE_COLUMN_KEY); + put(TARGET_OWNER_COLUMN, AuditLocalizationConstants.TARGET_OWNER_COLUMN_KEY); + put(CHANNEL_COLUMN, AuditLocalizationConstants.CHANNEL_COLUMN_KEY); + put(OUTCOME_COLUMN, AuditLocalizationConstants.OUTCOME_COLUMN_KEY); + put(TASK_OID_COLUMN, AuditLocalizationConstants.TASK_OID_COLUMN_KEY); + put(NODE_IDENTIFIER_COLUMN, AuditLocalizationConstants.NODE_IDENTIFIER_COLUMN_KEY); + put(ATTORNEY_COLUMN, AuditLocalizationConstants.ATTORNEY_COLUMN_KEY); + put(RESULT_COLUMN, AuditLocalizationConstants.RESULT_COLUMN_KEY); + put(RESOURCE_OID_COLUMN, AuditLocalizationConstants.RESOURCE_OID_COLUMN_KEY); + } + }; + } + + @Override + protected void initialize() { + LOGGER.trace("Registering with taskManager as a handler for {}", REPORT_HTML_CREATE_TASK_URI); + taskManager.registerHandler(REPORT_HTML_CREATE_TASK_URI, this); + } + + @Override + public TaskRunResult run(RunningTask task, TaskPartitionDefinitionType partition) { + OperationResult parentResult = task.getResult(); + OperationResult result = parentResult + .createSubresult(ReportHTMLCreateTaskHandler.class.getSimpleName() + ".run"); + TaskRunResult runResult = new TaskRunResult(); + runResult.setOperationResult(result); + super.recordProgress(task, 0, result); + try { + ReportType parentReport = objectResolver.resolve(task.getObjectRefOrClone(), ReportType.class, null, + "resolving report", task, result); + + if (!reportService.isAuthorizedToRunReport(parentReport.asPrismObject(), task, parentResult)) { + LOGGER.error("Task {} is not authorized to run report {}", task, parentReport); + throw new SecurityViolationException("Not authorized"); + } + + if (parentReport.getReportEngine() == null) { + throw new IllegalArgumentException("Report Object doesn't have ReportEngine attribute"); + } + if (parentReport.getReportEngine().equals(ReportEngineSelectionType.JASPER)) { + parentReport.setExport(ExportType.HTML); + return super.run(task, partition); + + } else if (parentReport.getReportEngine().equals(ReportEngineSelectionType.DASHBOARD)) { + + if (parentReport.getDashboard() != null && parentReport.getDashboard().getDashboardRef() != null) { + ObjectReferenceType ref = parentReport.getDashboard().getDashboardRef(); + Class type = prismContext.getSchemaRegistry().determineClassForType(ref.getType()); + Task taskSearchDashboard = taskManager.createTaskInstance("Search dashboard"); + DashboardType dashboard = (DashboardType) modelService + .getObject(type, ref.getOid(), null, taskSearchDashboard, taskSearchDashboard.getResult()) + .asObjectable(); + ClassLoader classLoader = getClass().getClassLoader(); + InputStream in = classLoader.getResourceAsStream(REPORT_CSS_STYLE_FILE_NAME); + if (in == null) { + throw new IllegalStateException("Resource " + REPORT_CSS_STYLE_FILE_NAME + " couldn't be found"); + } + byte[] data = IOUtils.toByteArray(in); + String style = new String(data, Charset.defaultCharset()); + + String reportFilePath = getDestinationFileName(parentReport); + FileUtils.writeByteArrayToFile(new File(reportFilePath), getBody(dashboard, style, task, result).getBytes()); + super.saveReportOutputType(reportFilePath, parentReport, task, result); + LOGGER.trace("create report output type : {}", reportFilePath); + + if (parentReport.getPostReportScript() != null) { + super.processPostReportScript(parentReport, reportFilePath, task, result); + } + result.computeStatus(); + } else { + LOGGER.error("Dashboard or DashboardRef is null"); + throw new IllegalArgumentException("Dashboard or DashboardRef is null"); + } + + } + } catch (Exception ex) { + LOGGER.error("CreateReport: {}", ex.getMessage(), ex); + result.recordFatalError(ex.getMessage(), ex); + runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR); + return runResult; + } + + // This "run" is finished. But the task goes on ... + runResult.setRunResultStatus(TaskRunResultStatus.FINISHED); + LOGGER.trace("CreateReportTaskHandler.run stopping"); + return runResult; + } + + private String getBody(DashboardType dashboard, String cssStyle, Task task, OperationResult result) throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, ObjectNotFoundException { + StringBuilder body = new StringBuilder(); + body.append("
"); + + ContainerTag widgetTable = createTable(); + widgetTable.with(createTHead("Widget.", getHeadsOfWidget())); + + ContainerTag widgetTBody = TagCreator.tbody(); + List tableboxesFromWidgets = new ArrayList<>(); + long startMillis = clock.currentTimeMillis(); + for (DashboardWidgetType widget : dashboard.getWidget()) { + DashboardWidget widgetData = dashboardService.createWidgetData(widget, task, result); + widgetTBody.with(createTBodyRow(widgetData)); + ContainerTag tableBox = createTableBoxForWidget(widgetData, task, result); + if (tableBox != null) { + tableboxesFromWidgets.add(tableBox); + } + } + widgetTable.with(widgetTBody); + + body.append(createTableBox(widgetTable, "Widgets", dashboard.getWidget().size(), + convertMillisToString(startMillis), null).render()); + appendSpace(body); + tableboxesFromWidgets.forEach(table -> { + body.append(table.render()); + appendSpace(body); + }); + body.append("
"); + + return body.toString(); + } + + private String convertMillisToString(long millis) { + SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d. MMM yyyy HH:mm:ss", Locale.getDefault()); + return dateFormat.format(millis); + } + + private void appendSpace(StringBuilder body) { + body.append("
"); + } + + private ContainerTag createTableBoxForWidget(DashboardWidget widgetData, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, + CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + DashboardWidgetType widget = widgetData.getWidget(); + if (widget == null) { + throw new IllegalArgumentException("Widget in DashboardWidget is null"); + } + DashboardWidgetPresentationType presentation = widget.getPresentation(); + DashboardWidgetSourceTypeType sourceType = DashboardUtils.getSourceType(widget); + if (sourceType == null) { + throw new IllegalStateException("No source type specified in " + widget); + } + switch (sourceType) { + case OBJECT_COLLECTION: + if (!DashboardUtils.isDataFieldsOfPresentationNullOrEmpty(presentation)) { + ObjectCollectionType collection = dashboardService.getObjectCollectionType(widget, task, result); + long startMillis = clock.currentTimeMillis(); + List> values = dashboardService.searchObjectFromCollection(collection, true, task, result); + if (values == null || values.isEmpty()) { + return null; + } + ContainerTag table = createTable(); + PrismObjectDefinition def = values.get(0).getDefinition(); + //noinspection unchecked + Class type = (Class) prismContext.getSchemaRegistry() + .getCompileTimeClassForObjectType(collection.getType()); + DisplayType display = null; + if(!useDefaultColumn(widget)) { + table = createTable(widget.getPresentation().getView(), values, def, type, + task, result); + display = widget.getPresentation().getView().getDisplay(); + } else { + table = createTable(values, def, type, task, result); + } + return createTableBox(table, widgetData.getLabel(), values.size(), + convertMillisToString(startMillis), display); + } + break; + case AUDIT_SEARCH: + if (!DashboardUtils.isDataFieldsOfPresentationNullOrEmpty(presentation)) { + Map parameters = new HashMap<>(); + + ObjectCollectionType collection = dashboardService.getObjectCollectionType(widget, task, result); + long startMillis = clock.currentTimeMillis(); + String query = DashboardUtils + .getQueryForListRecords(DashboardUtils.createQuery(collection, parameters, false, clock)); + List records = auditService.listRecords(query, parameters, result); + if (records == null || records.isEmpty()) { + return null; + } + + ContainerTag table = createTable(); + DisplayType display = null; + if(!useDefaultColumn(widget)) { + table = createTable(widget.getPresentation().getView(), records, task, result); + display = widget.getPresentation().getView().getDisplay(); + } else { + table.with(createTHead(getHeadsOfAuditEventRecords())); + ContainerTag tBody = TagCreator.tbody(); + records.forEach(record -> { + ContainerTag tr = TagCreator.tr(); + getHeadsOfAuditEventRecords().forEach(column -> { + tr.with(TagCreator.th(TagCreator.div(getStringForColumnOfAuditRecord(column, record, null)).withStyle("white-space: pre-wrap"))); + }); + tBody.with(tr); + }); + table.with(tBody); + } + return createTableBox(table, widgetData.getLabel(), records.size(), + convertMillisToString(startMillis), null); + } + break; + } + return null; + } + + private boolean useDefaultColumn(DashboardWidgetType widget) { + return widget.getPresentation() == null || widget.getPresentation().getView() == null + || widget.getPresentation().getView().getColumn() == null || widget.getPresentation().getView().getColumn().isEmpty(); + } + + private String getStringForColumnOfAuditRecord(String column, AuditEventRecord record, ItemPathType path, + ExpressionType expression, Task task, OperationResult result) { + if(expression == null) { + return getStringForColumnOfAuditRecord(column, record, path); + } + Object object = null; + switch (column) { + case TIME_COLUMN: + object = record.getTimestamp(); + break; + case INITIATOR_COLUMN: + object = record.getInitiator(); + break; + case EVENT_STAGE_COLUMN: + object = record.getEventStage(); + break; + case EVENT_TYPE_COLUMN: + object = record.getEventType(); + break; + case TARGET_COLUMN: + object = record.getTarget(); + break; + case TARGET_OWNER_COLUMN: + object = record.getTargetOwner(); + break; + case CHANNEL_COLUMN: + object = record.getChannel(); + break; + case OUTCOME_COLUMN: + object = record.getOutcome(); + break; + case MESSAGE_COLUMN: + object = record.getMessage(); + break; + case DELTA_COLUMN: + object = record.getDeltas(); + break; + case TASK_OID_COLUMN: + object = record.getTaskOid(); + break; + case NODE_IDENTIFIER_COLUMN: + object = record.getNodeIdentifier(); + break; + case ATTORNEY_COLUMN: + object = record.getAttorney(); + break; + case RESULT_COLUMN: + object = record.getResult(); + break; + case RESOURCE_OID_COLUMN: + object = record.getResourceOids(); + break; + default: + if(record.getCustomColumnProperty().containsKey(path)) { + object = record.getCustomColumnProperty().get(path); + } else { + LOGGER.error("Unknown name of column for AuditReport " + column); + } + break; + } + return evaluateExpression(expression, object, task, result); + } + + private String getStringForColumnOfAuditRecord(String column, AuditEventRecord record, ItemPathType path) { + switch (column) { + case TIME_COLUMN: + SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d. MMM yyyy HH:mm:ss", Locale.US); + return dateFormat.format(record.getTimestamp()); + case INITIATOR_COLUMN: + return record.getInitiator() == null ? "" : record.getInitiator().getName().getOrig(); + case EVENT_STAGE_COLUMN: + return record.getEventStage() == null ? "" : getMessage(record.getEventStage()); + case EVENT_TYPE_COLUMN: + return record.getEventType() == null ? "" : getMessage(record.getEventType()); + case TARGET_COLUMN: + return record.getTarget() == null ? "" : getObjectNameFromRef(record.getTarget().getRealValue()); + case TARGET_OWNER_COLUMN: + return record.getTargetOwner() == null ? "" :record.getTargetOwner().getName().getOrig(); + case CHANNEL_COLUMN: + return record.getChannel() == null ? "" : QNameUtil.uriToQName(record.getChannel()).getLocalPart(); + case OUTCOME_COLUMN: + return record.getOutcome() == null ? "" : getMessage(record.getOutcome()); + case MESSAGE_COLUMN: + return record.getMessage() == null ? "" : record.getMessage(); + case DELTA_COLUMN: + if (record.getDeltas() == null || record.getDeltas().isEmpty()) { + return ""; + } + StringBuilder sbDelta = new StringBuilder(); + Collection> deltas = record.getDeltas(); + Iterator> iterator = deltas.iterator(); + int index = 0; + while (iterator.hasNext()) { + ObjectDeltaOperation delta = iterator.next(); + sbDelta.append(ReportUtils.printDelta(delta)); + if ((index+1)!=deltas.size()) { + sbDelta.append("\n"); + } + index++; + } + return sbDelta.toString(); + case TASK_OID_COLUMN: + return record.getTaskOid() == null ? "" : record.getTaskOid(); + case NODE_IDENTIFIER_COLUMN: + return record.getNodeIdentifier() == null ? "" : record.getNodeIdentifier(); + case ATTORNEY_COLUMN: + return record.getAttorney() == null ? "" : record.getAttorney().getName().getOrig(); + case RESULT_COLUMN: + return record.getResult() == null ? "" : record.getResult(); + case RESOURCE_OID_COLUMN: + Set resourceOids = record.getResourceOids(); + if(resourceOids == null || resourceOids.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + int i = 1; + for(String oid : resourceOids) { + sb.append(oid); + if(i != resourceOids.size()) { + sb.append("\n"); + } + i++; + } + return sb.toString(); + default: + if(record.getCustomColumnProperty().containsKey(path)) { + return record.getCustomColumnProperty().get(path); + } + } + + return ""; + + } + + private String getObjectNameFromRef(Referencable ref) { + if (ref == null) { + return ""; + } + if (ref.getTargetName() != null && ref.getTargetName().getOrig() != null) { + return ref.getTargetName().getOrig(); + } + PrismObject object = getObjectFromReference(ref); + + if (object == null) { + return ref.getOid(); + } + + if (object.getName() == null || object.getName().getOrig() == null) { + return ""; + } + return object.getName().getOrig(); + } + + private String getRealValueAsString(String nameOfColumn, PrismObject object, ItemPath itemPath, + ExpressionType expression, Task task, OperationResult result) { + Iterator iterator = (Iterator) itemPath.getSegments().iterator(); + Item valueObject = object; + + + while (iterator.hasNext()) { + QName name = iterator.next(); + if (QNameUtil.match(name, CUSTOM)) { + return getCustomValueForColumn(valueObject, nameOfColumn); + } + valueObject = (Item) valueObject.find(ItemPath.create(name)); + if (valueObject instanceof PrismProperty && iterator.hasNext()) { + throw new IllegalArgumentException("Found object is PrismProperty, but ItemPath isn't empty"); + } + if (expression == null && valueObject instanceof PrismContainer && !iterator.hasNext()) { + throw new IllegalArgumentException("Found object is PrismContainer, but ItemPath is empty"); + } + if (valueObject instanceof PrismReference) { + Referencable ref = ((PrismReference) valueObject).getRealValue(); + if (!iterator.hasNext()) { + if(expression == null) { + return getObjectNameFromRef(ref); + } + return evaluateExpression(expression, valueObject, task, result); + } + + valueObject = getObjectFromReference(ref); + } + if (valueObject == null) { + if(nameOfColumn.equals(ACCOUNTS_COLUMN)) { + return "0"; + } + return ""; + } + } + if(expression != null) { + return evaluateExpression(expression, valueObject, task, result); + } + Object realValue = ((PrismProperty) valueObject).getRealValue(); + if (realValue == null){ + return ""; + } else if (realValue instanceof Enum) { + return getMessage((Enum)realValue); + } + return realValue.toString(); + } + + private String evaluateExpression(ExpressionType expression, Item valueObject, Task task, OperationResult result) { + Object object; + if(valueObject == null) { + object = null; + } else { + object = valueObject.getRealValue(); + } + return evaluateExpression(expression, object, task, result); + } + +private String evaluateExpression(ExpressionType expression, Object valueObject, Task task, OperationResult result) { + + ExpressionVariables variables = new ExpressionVariables(); + if(valueObject == null) { + variables.put(ExpressionConstants.VAR_OBJECT, null, Object.class); + } else { + variables.put(ExpressionConstants.VAR_OBJECT, valueObject, valueObject.getClass()); + } + Collection values = null; + try { + values = ExpressionUtil.evaluateStringExpression(variables, prismContext, expression, null, expressionFactory, "value for column", task, result); + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | CommunicationException + | ConfigurationException | SecurityViolationException e) { + LOGGER.error("Couldn't execute expression " + expression, e); + } + if (values == null || values.isEmpty()){ + return ""; + } + if(values.size() != 1) { + throw new IllegalArgumentException("Expected collection with one value, but it is " + values); + } + return values.iterator().next(); + } + + private boolean isCustomPerformedColumn(ItemPath itemPath) { + List segments = itemPath.getSegments(); + return segments.size() > 0 && QNameUtil.match((QName)segments.get(segments.size() - 1), CUSTOM); + } + + private String getCustomValueForColumn(Item valueObject, String nameOfColumn) { + switch (nameOfColumn) { + case ACCOUNTS_COLUMN: + if(!(valueObject instanceof PrismObject)){ + return ""; + } + return String.valueOf(((PrismObject)valueObject).getRealValues().size()); + case CURRENT_RUN_TIME_COLUMN: + if(!(valueObject instanceof PrismObject) + && !(((PrismObject)valueObject).getRealValue() instanceof TaskType)){ + return ""; + } + TaskType task = (TaskType)((PrismObject)valueObject).getRealValue(); + XMLGregorianCalendar timestapm = task.getCompletionTimestamp(); + if(timestapm != null && task.getExecutionStatus().equals(TaskExecutionStatusType.CLOSED)) { + SimpleDateFormat dateFormat = new SimpleDateFormat("EEEE, d. MMM yyyy HH:mm:ss", Locale.US); + return "closed at " + dateFormat.format(task.getCompletionTimestamp().toGregorianCalendar().getTime()); + } + return ""; + } + return ""; + } + + private PrismObject getObjectFromReference(Referencable ref) { + Task task = taskManager.createTaskInstance("Get object"); + Class type = prismContext.getSchemaRegistry().determineClassForType(ref.getType()); + + if (ref.asReferenceValue().getObject() != null) { + return ref.asReferenceValue().getObject(); + } + + PrismObject object = null; + try { + object = modelService.getObject(type, ref.getOid(), null, task, task.getResult()); + } catch (Exception e) { + LOGGER.error("Couldn't get object from objectRef " + ref, e); + } + return object; + } + + private ContainerTag createTable() { + return TagCreator.table().withClasses("table", "table-striped", "table-hover", "table-bordered"); + } + + private ContainerTag createTableBox(ContainerTag table, String nameOfTable, int countOfTableRecords, + String createdTime, DisplayType display) { + ContainerTag div = TagCreator.div().withClasses("box-body", "no-padding").with(TagCreator.h1(nameOfTable)) + .with(TagCreator.p(getMessage(REPORT_GENERATED_ON, createdTime))) + .with(TagCreator.p(getMessage(NUMBER_OF_RECORDS, countOfTableRecords))).with(table); + String style = ""; + String classes = ""; + if(display != null) { + if(display.getCssStyle() != null) { + style = display.getCssStyle(); + } + if(display.getCssClass() != null) { + classes = display.getCssClass(); + } + } + return TagCreator.div().withClasses("box", "boxed-table", classes).withStyle(style).with(div); + } + + private ContainerTag createTable(GuiObjectListViewType view, List records, Task task, + OperationResult result) { + ContainerTag table = createTable(); + ContainerTag tHead = TagCreator.thead(); + ContainerTag tBody = TagCreator.tbody(); + List columns = MiscSchemaUtil.orderCustomColumns(view.getColumn()); + ContainerTag trForHead = TagCreator.tr().withStyle("width: 100%;"); + columns.forEach(column -> { + Validate.notNull(column.getName(), "Name of column is null"); + + DisplayType columnDisplay = column.getDisplay(); + String label; + if(columnDisplay != null && columnDisplay.getLabel() != null) { + label = getMessage(columnDisplay.getLabel().getOrig()); + } else if (specialKeyLocalization.containsKey(column.getName())) { + label = getMessage(specialKeyLocalization.get(column.getName())); + } else { + label = column.getName(); + } + ContainerTag th = TagCreator.th(TagCreator.div(TagCreator.span(label).withClass("sortableLabel"))); + if(columnDisplay != null) { + if(StringUtils.isNotBlank(columnDisplay.getCssClass())) { + th.withClass(columnDisplay.getCssClass()); + } + if(StringUtils.isNotBlank(columnDisplay.getCssStyle())) { + th.withStyle(columnDisplay.getCssStyle()); + } + } + trForHead.with(th); + }); + tHead.with(trForHead); + table.with(tHead); + + records.forEach(record -> { + ContainerTag tr = TagCreator.tr(); + columns.forEach(column -> { + ExpressionType expression = column.getExpression(); + tr.with(TagCreator + .th(TagCreator.div(getStringForColumnOfAuditRecord(column.getName(), record, column.getPath(), expression, task, result)).withStyle("white-space: pre-wrap"))); + }); + tBody.with(tr); + }); + table.with(tBody); + return table; + } + + private ContainerTag createTable(GuiObjectListViewType view, List> values, + PrismObjectDefinition def, Class type, + Task task, OperationResult result) { + ContainerTag table = createTable(); + ContainerTag tHead = TagCreator.thead(); + ContainerTag tBody = TagCreator.tbody(); + List columns = MiscSchemaUtil.orderCustomColumns(view.getColumn()); + ContainerTag trForHead = TagCreator.tr().withStyle("width: 100%;"); + columns.forEach(column -> { + Validate.notNull(column.getName(), "Name of column is null"); + ItemPath path = ItemPath.create(column.getPath()); + if(path == null) { + LinkedHashMap columnForType = getColumnsForType(type); + if (columnForType == null) { + LOGGER.error("Couldn't found default columns for class {}", type.getName()); + } + path = columnForType.get(column.getName()); + if(path == null) { + LOGGER.error("Couldn't found path for column {} in default columns for class {}", column.getName(), type.getName()); + } + } + + DisplayType columnDisplay = column.getDisplay(); + String label; + if(columnDisplay != null && columnDisplay.getLabel() != null) { + label = getMessage(columnDisplay.getLabel().getOrig()); + } else { + + label = getColumnLabel(column.getName(), def, path); + } + ContainerTag th = TagCreator.th(TagCreator.div(TagCreator.span(label).withClass("sortableLabel"))); + if(columnDisplay != null) { + if(StringUtils.isNotBlank(columnDisplay.getCssClass())) { + th.withClass(columnDisplay.getCssClass()); + } + if(StringUtils.isNotBlank(columnDisplay.getCssStyle())) { + th.withStyle(columnDisplay.getCssStyle()); + } + } + trForHead.with(th); + }); + tHead.with(trForHead); + table.with(tHead); + + values.forEach(value -> { + ContainerTag tr = TagCreator.tr(); + columns.forEach(column -> { + ItemPath path = ItemPath.create(column.getPath()); + if(path == null) { + path = getColumnsForType(type).get(column.getName()); + } + ExpressionType expression = column.getExpression(); + tr.with(TagCreator + .th(TagCreator.div(getRealValueAsString(column.getName(), value, path, expression, task, result)).withStyle("white-space: pre-wrap"))); + }); + tBody.with(tr); + }); + table.with(tBody); + return table; + } + + private ContainerTag createTable(List> values, PrismObjectDefinition def, + Class type, Task task, OperationResult result) { + HashMap columns = getColumnsForType(type); + if (columns == null) { + LOGGER.error("Couldn't create table for objects with class {}", type.getName()); + } + ContainerTag table = createTable(); + table.with(createTHead(columns, def)); + ContainerTag tBody = TagCreator.tbody(); + values.forEach(value -> { + ContainerTag tr = TagCreator.tr(); + columns.keySet().forEach(column -> { + tr.with(TagCreator + .th(TagCreator.div(getRealValueAsString(column, value, columns.get(column), null, task, result)).withStyle("white-space: pre-wrap"))); + }); + tBody.with(tr); + }); + table.with(tBody); + return table; + } + + private String getLabelNameForCustom(String nameOfColumn) { + String key = ""; + switch (nameOfColumn) { + case ACCOUNTS_COLUMN: + key = "FocusType.accounts"; + case CURRENT_RUN_TIME_COLUMN: + key = "TaskType.currentRunTime"; + } + return getMessage(key); + } + + private ContainerTag createTHead(Set set) { + return TagCreator.thead(TagCreator.tr(TagCreator.each(set, + header -> TagCreator.th(TagCreator.div(TagCreator.span(getMessage(specialKeyLocalization.get(header))).withClass("sortableLabel"))) + .withStyle(getStyleForColumn(header)))).withStyle("width: 100%;")); + } + + private ContainerTag createTHead(String prefix, Set set) { + return TagCreator.thead(TagCreator.tr(TagCreator.each(set, + header -> TagCreator.th(TagCreator.div(TagCreator.span(getMessage(prefix+header)).withClass("sortableLabel"))) + .withStyle(getStyleForColumn(header)))).withStyle("width: 100%;")); + } + + private ContainerTag createTHead(HashMap columns, PrismObjectDefinition objectDefinition) { + ContainerTag tr = TagCreator.tr().withStyle("width: 100%;"); + columns.keySet().forEach(columnName -> { + tr.with(TagCreator.th(TagCreator.div(TagCreator.span(getColumnLabel(columnName, objectDefinition, columns.get(columnName))) + .withClass("sortableLabel")))); + }); + return TagCreator.thead(tr); + } + + private String getColumnLabel(String name, PrismObjectDefinition objectDefinition, ItemPath path) { + if(specialKeyLocalization.containsKey(name)) { + return getMessage(specialKeyLocalization.get(name)); + } + if(isCustomPerformedColumn(path)) { + return getLabelNameForCustom(name); + } + ItemDefinition def = objectDefinition.findItemDefinition(path); + if(def == null) { + throw new IllegalArgumentException("Could'n find item for path " + path); + } + String displayName = def.getDisplayName(); + return getMessage(displayName); + } + + private String getStyleForColumn(String column) { + switch (column) { + case TIME_COLUMN: + return "width: 10%;"; + case INITIATOR_COLUMN: + return "width: 8%;"; + case EVENT_STAGE_COLUMN: + return "width: 5%;"; + case EVENT_TYPE_COLUMN: + return "width: 10%;"; + case TARGET_COLUMN: + return "width: 8%;"; + case OUTCOME_COLUMN: + return "width: 7%;"; + case DELTA_COLUMN: + return "width: 35%;"; + } + return ""; + } + + private ContainerTag createTBodyRow(DashboardWidget data) { + return TagCreator.tr(TagCreator.each(getHeadsOfWidget(), header -> getContainerTagForWidgetHeader(header, data) + + )); + + } + + private ContainerTag getContainerTagForWidgetHeader(String header, DashboardWidget data) { + if (header.equals(LABEL_COLUMN)) { + ContainerTag div = TagCreator.div(data.getLabel()); + return TagCreator.th().with(div); + } + if (header.equals(NUMBER_COLUMN)) { + ContainerTag div = TagCreator.div(data.getNumberMessage()); + return TagCreator.th().with(div); + } + if (header.equals(STATUS_COLUMN)) { + ContainerTag div = TagCreator.div().withStyle("width: 100%; height: 20px; "); + ContainerTag th = TagCreator.th(); + addStatusColor(th, data.getDisplay()); + th.with(div); + return th; + } + return TagCreator.th(); + } + + private void addStatusColor(ContainerTag div, DisplayType display) { + if (display != null && StringUtils.isNoneBlank(display.getColor())) { + div.withStyle("background-color: " + display.getColor() + "; !important;"); + } + + } + + private Set getHeadsOfWidget() { + return headsOfWidget; + } + + private static Set getHeadsOfAuditEventRecords() { + return headsOfAuditEventRecords; + } + + private static LinkedHashMap, LinkedHashMap> getColumnDef() { + return columnDef; + } + + private static LinkedHashMap getColumnsForType(Class type ) { + if(type.equals(RoleType.class) + || type.equals(OrgType.class) + || type.equals(ServiceType.class)) { + return getColumnDef().get(AbstractRoleType.class); + } + return getColumnDef().get(type); + } + + @Override + protected ExportType getExport(ReportType report) { + return ExportType.HTML; + } + + private String getMessage(Enum e) { + StringBuilder sb = new StringBuilder(); + sb.append(e.getDeclaringClass().getSimpleName()).append('.'); + sb.append(e.name()); + return getMessage(sb.toString()); + } + + private String getMessage(String key) { + return getMessage(key, null); + } + + private String getMessage(String key, Object... params) { + return localizationService.translate(key, params, Locale.getDefault(), key); + } +} diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java index 723098a4aa8..6ccb3a3d822 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java @@ -1,485 +1,485 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.report.impl; - -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.api.context.ModelElementContext; -import com.evolveum.midpoint.model.api.context.ModelState; -import com.evolveum.midpoint.model.api.hooks.ChangeHook; -import com.evolveum.midpoint.model.api.hooks.HookOperationMode; -import com.evolveum.midpoint.model.api.hooks.HookRegistry; -import com.evolveum.midpoint.model.api.hooks.ReadHook; -import com.evolveum.midpoint.model.impl.ClusterRestService; -import com.evolveum.midpoint.prism.PrismContainer; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.xml.XmlTypeConverter; -import com.evolveum.midpoint.report.api.ReportManager; -import com.evolveum.midpoint.report.api.ReportService; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; -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.ReportTypeUtil; -import com.evolveum.midpoint.task.api.ClusterExecutionHelper; -import com.evolveum.midpoint.task.api.ClusterExecutionOptions; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.Holder; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import net.sf.jasperreports.engine.JRException; -import net.sf.jasperreports.engine.JasperCompileManager; -import net.sf.jasperreports.engine.JasperReport; -import net.sf.jasperreports.engine.design.JasperDesign; -import net.sf.jasperreports.engine.xml.JRXmlLoader; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.annotation.PostConstruct; -import javax.ws.rs.core.Response; -import javax.xml.datatype.Duration; -import javax.xml.datatype.XMLGregorianCalendar; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; - - -/** - * @author lazyman, garbika - */ -@Service(value = "reportManager") -public class ReportManagerImpl implements ReportManager, ChangeHook, ReadHook { - - public static final String HOOK_URI = "http://midpoint.evolveum.com/model/report-hook-1"; - - private static final Trace LOGGER = TraceManager.getTrace(ReportManagerImpl.class); - - private static final String CLASS_NAME_WITH_DOT = ReportManagerImpl.class.getSimpleName() + "."; - private static final String CLEANUP_REPORT_OUTPUTS = CLASS_NAME_WITH_DOT + "cleanupReportOutputs"; - private static final String DELETE_REPORT_OUTPUT = CLASS_NAME_WITH_DOT + "deleteReportOutput"; - private static final String REPORT_OUTPUT_DATA = CLASS_NAME_WITH_DOT + "getReportOutputData"; - - @Autowired private HookRegistry hookRegistry; - @Autowired private TaskManager taskManager; - @Autowired private PrismContext prismContext; - @Autowired private ReportService reportService; - @Autowired private ModelService modelService; - @Autowired private ClusterExecutionHelper clusterExecutionHelper; - - @PostConstruct - public void init() { - hookRegistry.registerChangeHook(HOOK_URI, this); - hookRegistry.registerReadHook(HOOK_URI, this); - } - - @Override - public void invoke(PrismObject object, - Collection> options, Task task, - OperationResult parentResult) throws SchemaException, - ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException { - - if (!ReportType.class.equals(object.getCompileTimeClass())) { - return; - } - - boolean raw = isRaw(options); - if (!raw) { - ReportTypeUtil.applyDefinition((PrismObject) object, prismContext); - } - } - - private boolean isRaw(Collection> options) { - GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); - return rootOptions == null ? false : GetOperationOptions.isRaw(rootOptions); - } - - /** - * Creates and starts task with proper handler, also adds necessary information to task - * (like ReportType reference and so on). - * - * @param object - * @param task - * @param parentResult describes report which has to be created - */ - - @Override - public void runReport(PrismObject object, PrismContainer paramContainer, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (!reportService.isAuthorizedToRunReport(object, task, parentResult)) { - LOGGER.error("User is not authorized to run report {}", object); - throw new SecurityViolationException("Not authorized"); - } - - if(isDashboarReport(object)) { - task.setHandlerUri(ReportHTMLCreateTaskHandler.REPORT_HTML_CREATE_TASK_URI); - } else { - task.setHandlerUri(ReportJasperCreateTaskHandler.REPORT_CREATE_TASK_URI); - } - task.setObjectRef(object.getOid(), ReportType.COMPLEX_TYPE); - try { - if (paramContainer != null && !paramContainer.isEmpty()){ - task.setExtensionContainer(paramContainer); - } - } catch (SchemaException e) { - throw new SystemException(e); - } - - task.setThreadStopAction(ThreadStopActionType.CLOSE); - task.makeSingle(); - - taskManager.switchToBackground(task, parentResult); - parentResult.setBackgroundTaskOid(task.getOid()); - } - - - - private boolean isDashboarReport(PrismObject object) { - if(object.getRealValue() != null && object.getRealValue().getReportEngine() != null - && object.getRealValue().getReportEngine().equals(ReportEngineSelectionType.DASHBOARD)) { - return true; - } - return false; - } - - /** - * Transforms change: - * 1/ ReportOutputType DELETE to MODIFY some attribute to mark it for deletion. - * 2/ ReportType ADD and MODIFY should compute jasper design and styles if necessary - * - * @param context - * @param task - * @param result - * @return - * @throws UnsupportedEncodingException - */ - @Override - public HookOperationMode invoke(@NotNull ModelContext context, @NotNull Task task, @NotNull OperationResult parentResult) { - ModelState state = context.getState(); - if (state != ModelState.FINAL) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report manager called in state = " + state + ", exiting."); - } - return HookOperationMode.FOREGROUND; - } else { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report manager called in state = " + state + ", proceeding."); - } - } - - boolean relatesToReport = false; - boolean isDeletion = false; - PrismObject object = null; - for (Object o : context.getProjectionContexts()) { - boolean deletion = false; - object = ((ModelElementContext) o).getObjectNew(); - if (object == null) { - deletion = true; - object = ((ModelElementContext) o).getObjectOld(); - } - if (object == null) { - LOGGER.warn("Probably invalid projection context: both old and new objects are null"); - } else if (object.getCompileTimeClass().isAssignableFrom(ReportType.class)) { - relatesToReport = true; - isDeletion = deletion; - } - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("change relates to report: " + relatesToReport + ", is deletion: " + isDeletion); - } - - if (!relatesToReport) { - LOGGER.trace("invoke() EXITING: Changes not related to report"); - return HookOperationMode.FOREGROUND; - } - - if (isDeletion) { - LOGGER.trace("invoke() EXITING because operation is DELETION"); - return HookOperationMode.FOREGROUND; - } - - OperationResult result = parentResult.createSubresult(CLASS_NAME_WITH_DOT + "invoke"); - try { - ReportType reportType = (ReportType) object.asObjectable(); - JasperReportEngineConfigurationType jasperConfig = reportType.getJasper(); - JasperDesign jasperDesign = null; - - byte[] reportTemplateBase64; - if (jasperConfig == null) { - reportTemplateBase64 = reportType.getTemplate(); - } else { - reportTemplateBase64 = jasperConfig.getTemplate(); - } - - if (reportTemplateBase64 == null){ - String message = "Report template must not be null"; - LOGGER.error(message); - result.recordFatalError(message, new SystemException()); - } - else - { - byte[] reportTemplate = ReportUtils.decodeIfNeeded(reportTemplateBase64); - InputStream inputStreamJRXML = new ByteArrayInputStream(reportTemplate); - jasperDesign = JRXmlLoader.load(inputStreamJRXML); - LOGGER.trace("load jasper design : {}", jasperDesign); - } - // Compile template - JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign); - LOGGER.trace("compile jasper design, create jasper report : {}", jasperReport); - - //result.computeStatus(); - result.recordSuccessIfUnknown(); - - } - catch (JRException ex) { - String message = "Cannot load or compile jasper report: " + ex.getMessage(); - LOGGER.error(message); - result.recordFatalError(message, ex); - } - - - return HookOperationMode.FOREGROUND; - } - - - @Override - public void invokeOnException(@NotNull ModelContext context, @NotNull Throwable throwable, @NotNull Task task, @NotNull OperationResult result) { - - } - - @Override - public void cleanupReports(CleanupPolicyType cleanupPolicy, OperationResult parentResult) { - OperationResult result = parentResult.createSubresult(CLEANUP_REPORT_OUTPUTS); - - // This operation does not need any extra authorization check. All model operations carried out by this - // method are executed through modelService. Therefore usual object authorizations are checked. - - if (cleanupPolicy.getMaxAge() == null) { - return; - } - - Duration duration = cleanupPolicy.getMaxAge(); - if (duration.getSign() > 0) { - duration = duration.negate(); - } - Date deleteReportOutputsTo = new Date(); - duration.addTo(deleteReportOutputsTo); - - LOGGER.info("Starting cleanup for report outputs deleting up to {} (duration '{}').", - new Object[]{deleteReportOutputsTo, duration}); - - XMLGregorianCalendar timeXml = XmlTypeConverter.createXMLGregorianCalendar(deleteReportOutputsTo.getTime()); - - List> obsoleteReportOutputs = new ArrayList<>(); - try { - ObjectQuery obsoleteReportOutputsQuery = prismContext.queryFor(ReportOutputType.class) - .item(ReportOutputType.F_METADATA, MetadataType.F_CREATE_TIMESTAMP).le(timeXml) - .build(); - obsoleteReportOutputs = modelService.searchObjects(ReportOutputType.class, obsoleteReportOutputsQuery, null, null, result); - } catch (Exception e) { - throw new SystemException("Couldn't get the list of obsolete report outputs: " + e.getMessage(), e); - } - - LOGGER.debug("Found {} report output(s) to be cleaned up", obsoleteReportOutputs.size()); - - boolean interrupted = false; - int deleted = 0; - int problems = 0; - - for (PrismObject reportOutputPrism : obsoleteReportOutputs){ - ReportOutputType reportOutput = reportOutputPrism.asObjectable(); - - LOGGER.trace("Removing report output {} along with {} file.", reportOutput.getName().getOrig(), - reportOutput.getFilePath()); - boolean problem = false; - try { - deleteReportOutput(reportOutput, result); - } catch (Exception e) { - LoggingUtils.logException(LOGGER, "Couldn't delete obsolete report output {} due to a exception", e, reportOutput); - problem = true; - } - - if (problem) { - problems++; - } else { - deleted++; - } - } - result.computeStatusIfUnknown(); - - LOGGER.info("Report cleanup procedure " + - (interrupted ? "was interrupted" : "finished") + - ". Successfully deleted {} report outputs; there were problems with deleting {} report ouptuts.", deleted, problems); - String suffix = interrupted ? " Interrupted." : ""; - if (problems == 0) { - parentResult.createSubresult(CLEANUP_REPORT_OUTPUTS + ".statistics").recordStatus(OperationResultStatus.SUCCESS, - "Successfully deleted " + deleted + " report output(s)." + suffix); - } else { - parentResult.createSubresult(CLEANUP_REPORT_OUTPUTS + ".statistics").recordPartialError("Successfully deleted " + - deleted + " report output(s), " - + "there was problems with deleting " + problems + " report outputs."); - } - } - - @Override - public void deleteReportOutput(ReportOutputType reportOutput, OperationResult parentResult) throws Exception { - String oid = reportOutput.getOid(); - - // This operation does not need any extra authorization check. All model operations carried out by this - // method are executed through modelService. Therefore usual object authorizations are checked. - - Task task = taskManager.createTaskInstance(DELETE_REPORT_OUTPUT); - parentResult.addSubresult(task.getResult()); - OperationResult result = parentResult.createSubresult(DELETE_REPORT_OUTPUT); - - String filePath = reportOutput.getFilePath(); - result.addParam("oid", oid); - try { - File file = new File(filePath); - - if (file.exists()) { - if (!file.delete()) { - LOGGER.error("Couldn't delete report file {}", file); - } - } else { - String fileName = checkNodeAndFileName(file, reportOutput, result); - if (fileName == null) { - return; - } - clusterExecutionHelper.execute(reportOutput.getNodeRef().getOid(), (client, result1) -> { - client.path(ClusterRestService.REPORT_FILE_PATH); - client.query(ClusterRestService.REPORT_FILE_FILENAME_PARAMETER, fileName); - Response response = client.delete(); - Response.StatusType statusInfo = response.getStatusInfo(); - LOGGER.debug("Deleting report output file ({}) from {} finished with status {}: {}", - fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); - if (statusInfo.getFamily() != Response.Status.Family.SUCCESSFUL) { - LOGGER.warn("Deleting report output file ({}) from {} finished with status {}: {}", - fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); - result1.recordFatalError("Could not delete report output file: Got " + statusInfo.getStatusCode() + ": " + statusInfo.getReasonPhrase()); - } - response.close(); - }, new ClusterExecutionOptions().tryNodesInTransition(), "get report output", result); - result.computeStatusIfUnknown(); - } - - ObjectDelta delta = prismContext.deltaFactory().object() - .createDeleteDelta(ReportOutputType.class, oid); - Collection> deltas = MiscSchemaUtil.createCollection(delta); - modelService.executeChanges(deltas, null, task, result); - - result.computeStatusIfUnknown(); - } - catch (Exception e) { - result.recordFatalError("Cannot delete the report output because of a exception.", e); - throw e; - } - } - - //TODO re-throw exceptions? - @Override - public InputStream getReportOutputData(String reportOutputOid, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException, IOException { - Task task = taskManager.createTaskInstance(REPORT_OUTPUT_DATA); - - OperationResult result = parentResult.createSubresult(REPORT_OUTPUT_DATA); - result.addParam("oid", reportOutputOid); - - // This operation does not need any extra authorization check. All model operations carried out by this - // method are executed through modelService. Therefore usual object authorizations are checked. - // Here we assume that anyone that can read the ReportOutputType object can also read report data. Which is a fair assumption. - - try { - ReportOutputType reportOutput = modelService.getObject(ReportOutputType.class, reportOutputOid, null, task, - result).asObjectable(); - - String filePath = reportOutput.getFilePath(); - if (StringUtils.isEmpty(filePath)) { - result.recordFatalError("Report output file path is not defined."); - return null; - } - File file = new File(filePath); - if (file.exists()) { - return FileUtils.openInputStream(file); - } else { - String fileName = checkNodeAndFileName(file, reportOutput, result); - if (fileName == null) { - return null; - } - Holder inputStreamHolder = new Holder<>(); - clusterExecutionHelper.execute(reportOutput.getNodeRef().getOid(), (client, result1) -> { - client.path(ClusterRestService.REPORT_FILE_PATH); - client.query(ClusterRestService.REPORT_FILE_FILENAME_PARAMETER, fileName); - Response response = client.get(); - Response.StatusType statusInfo = response.getStatusInfo(); - LOGGER.debug("Retrieving report output file ({}) from {} finished with status {}: {}", - fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); - if (statusInfo.getFamily() == Response.Status.Family.SUCCESSFUL) { - Object entity = response.getEntity(); - if (entity == null || entity instanceof InputStream) { - inputStreamHolder.setValue((InputStream) entity); - // do NOT close the response; input stream will be closed later by the caller(s) - } else { - LOGGER.error("Content of the report output file retrieved from the remote node is not an InputStream; " - + "it is {} instead -- this is not currently supported", entity.getClass()); - response.close(); - } - } else { - LOGGER.warn("Retrieving report output file ({}) from {} finished with status {}: {}", - fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); - result1.recordFatalError("Could not retrieve report output file: Got " + statusInfo.getStatusCode() + ": " + statusInfo.getReasonPhrase()); - response.close(); - } - }, new ClusterExecutionOptions().tryNodesInTransition(), "get report output", result); - result.computeStatusIfUnknown(); - return inputStreamHolder.getValue(); - } - } catch (IOException ex) { - LoggingUtils.logException(LOGGER, "Error while fetching file. File might not exist on the corresponding file system", ex); - result.recordPartialError("Error while fetching file. File might not exist on the corresponding file system. Reason: " + ex.getMessage(), ex); - throw ex; - } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException - | ConfigurationException | ExpressionEvaluationException e) { - result.recordFatalError("Problem with reading report output. Reason: " + e.getMessage(), e); - throw e; - } finally { - result.computeStatusIfUnknown(); - } - } - - @Nullable - private String checkNodeAndFileName(File file, ReportOutputType reportOutput, OperationResult result) { - if (reportOutput.getNodeRef() == null || reportOutput.getNodeRef().getOid() == null) { - result.recordFatalError("Report output node OID is not defined."); - return null; - } - String fileName = file.getName(); - if (StringUtils.isEmpty(fileName)) { - result.recordFatalError("Report output file name is empty."); - return null; - } - return fileName; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.report.impl; + +import com.evolveum.midpoint.model.api.ModelPublicConstants; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.api.context.ModelElementContext; +import com.evolveum.midpoint.model.api.context.ModelState; +import com.evolveum.midpoint.model.api.hooks.ChangeHook; +import com.evolveum.midpoint.model.api.hooks.HookOperationMode; +import com.evolveum.midpoint.model.api.hooks.HookRegistry; +import com.evolveum.midpoint.model.api.hooks.ReadHook; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.report.api.ReportManager; +import com.evolveum.midpoint.report.api.ReportService; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +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.ReportTypeUtil; +import com.evolveum.midpoint.task.api.ClusterExecutionHelper; +import com.evolveum.midpoint.task.api.ClusterExecutionOptions; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.util.Holder; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JasperCompileManager; +import net.sf.jasperreports.engine.JasperReport; +import net.sf.jasperreports.engine.design.JasperDesign; +import net.sf.jasperreports.engine.xml.JRXmlLoader; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.ws.rs.core.Response; +import javax.xml.datatype.Duration; +import javax.xml.datatype.XMLGregorianCalendar; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + + +/** + * @author lazyman, garbika + */ +@Service(value = "reportManager") +public class ReportManagerImpl implements ReportManager, ChangeHook, ReadHook { + + public static final String HOOK_URI = "http://midpoint.evolveum.com/model/report-hook-1"; + + private static final Trace LOGGER = TraceManager.getTrace(ReportManagerImpl.class); + + private static final String CLASS_NAME_WITH_DOT = ReportManagerImpl.class.getSimpleName() + "."; + private static final String CLEANUP_REPORT_OUTPUTS = CLASS_NAME_WITH_DOT + "cleanupReportOutputs"; + private static final String DELETE_REPORT_OUTPUT = CLASS_NAME_WITH_DOT + "deleteReportOutput"; + private static final String REPORT_OUTPUT_DATA = CLASS_NAME_WITH_DOT + "getReportOutputData"; + + @Autowired private HookRegistry hookRegistry; + @Autowired private TaskManager taskManager; + @Autowired private PrismContext prismContext; + @Autowired private ReportService reportService; + @Autowired private ModelService modelService; + @Autowired private ClusterExecutionHelper clusterExecutionHelper; + + @PostConstruct + public void init() { + hookRegistry.registerChangeHook(HOOK_URI, this); + hookRegistry.registerReadHook(HOOK_URI, this); + } + + @Override + public void invoke(PrismObject object, + Collection> options, Task task, + OperationResult parentResult) throws SchemaException, + ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException { + + if (!ReportType.class.equals(object.getCompileTimeClass())) { + return; + } + + boolean raw = isRaw(options); + if (!raw) { + ReportTypeUtil.applyDefinition((PrismObject) object, prismContext); + } + } + + private boolean isRaw(Collection> options) { + GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); + return rootOptions == null ? false : GetOperationOptions.isRaw(rootOptions); + } + + /** + * Creates and starts task with proper handler, also adds necessary information to task + * (like ReportType reference and so on). + * + * @param object + * @param task + * @param parentResult describes report which has to be created + */ + + @Override + public void runReport(PrismObject object, PrismContainer paramContainer, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + if (!reportService.isAuthorizedToRunReport(object, task, parentResult)) { + LOGGER.error("User is not authorized to run report {}", object); + throw new SecurityViolationException("Not authorized"); + } + + if(isDashboarReport(object)) { + task.setHandlerUri(ReportHTMLCreateTaskHandler.REPORT_HTML_CREATE_TASK_URI); + } else { + task.setHandlerUri(ReportJasperCreateTaskHandler.REPORT_CREATE_TASK_URI); + } + task.setObjectRef(object.getOid(), ReportType.COMPLEX_TYPE); + try { + if (paramContainer != null && !paramContainer.isEmpty()){ + task.setExtensionContainer(paramContainer); + } + } catch (SchemaException e) { + throw new SystemException(e); + } + + task.setThreadStopAction(ThreadStopActionType.CLOSE); + task.makeSingle(); + + taskManager.switchToBackground(task, parentResult); + parentResult.setBackgroundTaskOid(task.getOid()); + } + + + + private boolean isDashboarReport(PrismObject object) { + if(object.getRealValue() != null && object.getRealValue().getReportEngine() != null + && object.getRealValue().getReportEngine().equals(ReportEngineSelectionType.DASHBOARD)) { + return true; + } + return false; + } + + /** + * Transforms change: + * 1/ ReportOutputType DELETE to MODIFY some attribute to mark it for deletion. + * 2/ ReportType ADD and MODIFY should compute jasper design and styles if necessary + * + * @param context + * @param task + * @param result + * @return + * @throws UnsupportedEncodingException + */ + @Override + public HookOperationMode invoke(@NotNull ModelContext context, @NotNull Task task, @NotNull OperationResult parentResult) { + ModelState state = context.getState(); + if (state != ModelState.FINAL) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("report manager called in state = " + state + ", exiting."); + } + return HookOperationMode.FOREGROUND; + } else { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("report manager called in state = " + state + ", proceeding."); + } + } + + boolean relatesToReport = false; + boolean isDeletion = false; + PrismObject object = null; + for (Object o : context.getProjectionContexts()) { + boolean deletion = false; + object = ((ModelElementContext) o).getObjectNew(); + if (object == null) { + deletion = true; + object = ((ModelElementContext) o).getObjectOld(); + } + if (object == null) { + LOGGER.warn("Probably invalid projection context: both old and new objects are null"); + } else if (object.getCompileTimeClass().isAssignableFrom(ReportType.class)) { + relatesToReport = true; + isDeletion = deletion; + } + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("change relates to report: " + relatesToReport + ", is deletion: " + isDeletion); + } + + if (!relatesToReport) { + LOGGER.trace("invoke() EXITING: Changes not related to report"); + return HookOperationMode.FOREGROUND; + } + + if (isDeletion) { + LOGGER.trace("invoke() EXITING because operation is DELETION"); + return HookOperationMode.FOREGROUND; + } + + OperationResult result = parentResult.createSubresult(CLASS_NAME_WITH_DOT + "invoke"); + try { + ReportType reportType = (ReportType) object.asObjectable(); + JasperReportEngineConfigurationType jasperConfig = reportType.getJasper(); + JasperDesign jasperDesign = null; + + byte[] reportTemplateBase64; + if (jasperConfig == null) { + reportTemplateBase64 = reportType.getTemplate(); + } else { + reportTemplateBase64 = jasperConfig.getTemplate(); + } + + if (reportTemplateBase64 == null){ + String message = "Report template must not be null"; + LOGGER.error(message); + result.recordFatalError(message, new SystemException()); + } + else + { + byte[] reportTemplate = ReportUtils.decodeIfNeeded(reportTemplateBase64); + InputStream inputStreamJRXML = new ByteArrayInputStream(reportTemplate); + jasperDesign = JRXmlLoader.load(inputStreamJRXML); + LOGGER.trace("load jasper design : {}", jasperDesign); + } + // Compile template + JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign); + LOGGER.trace("compile jasper design, create jasper report : {}", jasperReport); + + //result.computeStatus(); + result.recordSuccessIfUnknown(); + + } + catch (JRException ex) { + String message = "Cannot load or compile jasper report: " + ex.getMessage(); + LOGGER.error(message); + result.recordFatalError(message, ex); + } + + + return HookOperationMode.FOREGROUND; + } + + + @Override + public void invokeOnException(@NotNull ModelContext context, @NotNull Throwable throwable, @NotNull Task task, @NotNull OperationResult result) { + + } + + @Override + public void cleanupReports(CleanupPolicyType cleanupPolicy, OperationResult parentResult) { + OperationResult result = parentResult.createSubresult(CLEANUP_REPORT_OUTPUTS); + + // This operation does not need any extra authorization check. All model operations carried out by this + // method are executed through modelService. Therefore usual object authorizations are checked. + + if (cleanupPolicy.getMaxAge() == null) { + return; + } + + Duration duration = cleanupPolicy.getMaxAge(); + if (duration.getSign() > 0) { + duration = duration.negate(); + } + Date deleteReportOutputsTo = new Date(); + duration.addTo(deleteReportOutputsTo); + + LOGGER.info("Starting cleanup for report outputs deleting up to {} (duration '{}').", + new Object[]{deleteReportOutputsTo, duration}); + + XMLGregorianCalendar timeXml = XmlTypeConverter.createXMLGregorianCalendar(deleteReportOutputsTo.getTime()); + + List> obsoleteReportOutputs = new ArrayList<>(); + try { + ObjectQuery obsoleteReportOutputsQuery = prismContext.queryFor(ReportOutputType.class) + .item(ReportOutputType.F_METADATA, MetadataType.F_CREATE_TIMESTAMP).le(timeXml) + .build(); + obsoleteReportOutputs = modelService.searchObjects(ReportOutputType.class, obsoleteReportOutputsQuery, null, null, result); + } catch (Exception e) { + throw new SystemException("Couldn't get the list of obsolete report outputs: " + e.getMessage(), e); + } + + LOGGER.debug("Found {} report output(s) to be cleaned up", obsoleteReportOutputs.size()); + + boolean interrupted = false; + int deleted = 0; + int problems = 0; + + for (PrismObject reportOutputPrism : obsoleteReportOutputs){ + ReportOutputType reportOutput = reportOutputPrism.asObjectable(); + + LOGGER.trace("Removing report output {} along with {} file.", reportOutput.getName().getOrig(), + reportOutput.getFilePath()); + boolean problem = false; + try { + deleteReportOutput(reportOutput, result); + } catch (Exception e) { + LoggingUtils.logException(LOGGER, "Couldn't delete obsolete report output {} due to a exception", e, reportOutput); + problem = true; + } + + if (problem) { + problems++; + } else { + deleted++; + } + } + result.computeStatusIfUnknown(); + + LOGGER.info("Report cleanup procedure " + + (interrupted ? "was interrupted" : "finished") + + ". Successfully deleted {} report outputs; there were problems with deleting {} report ouptuts.", deleted, problems); + String suffix = interrupted ? " Interrupted." : ""; + if (problems == 0) { + parentResult.createSubresult(CLEANUP_REPORT_OUTPUTS + ".statistics").recordStatus(OperationResultStatus.SUCCESS, + "Successfully deleted " + deleted + " report output(s)." + suffix); + } else { + parentResult.createSubresult(CLEANUP_REPORT_OUTPUTS + ".statistics").recordPartialError("Successfully deleted " + + deleted + " report output(s), " + + "there was problems with deleting " + problems + " report outputs."); + } + } + + @Override + public void deleteReportOutput(ReportOutputType reportOutput, OperationResult parentResult) throws Exception { + String oid = reportOutput.getOid(); + + // This operation does not need any extra authorization check. All model operations carried out by this + // method are executed through modelService. Therefore usual object authorizations are checked. + + Task task = taskManager.createTaskInstance(DELETE_REPORT_OUTPUT); + parentResult.addSubresult(task.getResult()); + OperationResult result = parentResult.createSubresult(DELETE_REPORT_OUTPUT); + + String filePath = reportOutput.getFilePath(); + result.addParam("oid", oid); + try { + File file = new File(filePath); + + if (file.exists()) { + if (!file.delete()) { + LOGGER.error("Couldn't delete report file {}", file); + } + } else { + String fileName = checkNodeAndFileName(file, reportOutput, result); + if (fileName == null) { + return; + } + clusterExecutionHelper.execute(reportOutput.getNodeRef().getOid(), (client, result1) -> { + client.path(ModelPublicConstants.CLUSTER_REPORT_FILE_PATH); + client.query(ModelPublicConstants.CLUSTER_REPORT_FILE_FILENAME_PARAMETER, fileName); + Response response = client.delete(); + Response.StatusType statusInfo = response.getStatusInfo(); + LOGGER.debug("Deleting report output file ({}) from {} finished with status {}: {}", + fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); + if (statusInfo.getFamily() != Response.Status.Family.SUCCESSFUL) { + LOGGER.warn("Deleting report output file ({}) from {} finished with status {}: {}", + fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); + result1.recordFatalError("Could not delete report output file: Got " + statusInfo.getStatusCode() + ": " + statusInfo.getReasonPhrase()); + } + response.close(); + }, new ClusterExecutionOptions().tryNodesInTransition(), "get report output", result); + result.computeStatusIfUnknown(); + } + + ObjectDelta delta = prismContext.deltaFactory().object() + .createDeleteDelta(ReportOutputType.class, oid); + Collection> deltas = MiscSchemaUtil.createCollection(delta); + modelService.executeChanges(deltas, null, task, result); + + result.computeStatusIfUnknown(); + } + catch (Exception e) { + result.recordFatalError("Cannot delete the report output because of a exception.", e); + throw e; + } + } + + //TODO re-throw exceptions? + @Override + public InputStream getReportOutputData(String reportOutputOid, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException, IOException { + Task task = taskManager.createTaskInstance(REPORT_OUTPUT_DATA); + + OperationResult result = parentResult.createSubresult(REPORT_OUTPUT_DATA); + result.addParam("oid", reportOutputOid); + + // This operation does not need any extra authorization check. All model operations carried out by this + // method are executed through modelService. Therefore usual object authorizations are checked. + // Here we assume that anyone that can read the ReportOutputType object can also read report data. Which is a fair assumption. + + try { + ReportOutputType reportOutput = modelService.getObject(ReportOutputType.class, reportOutputOid, null, task, + result).asObjectable(); + + String filePath = reportOutput.getFilePath(); + if (StringUtils.isEmpty(filePath)) { + result.recordFatalError("Report output file path is not defined."); + return null; + } + File file = new File(filePath); + if (file.exists()) { + return FileUtils.openInputStream(file); + } else { + String fileName = checkNodeAndFileName(file, reportOutput, result); + if (fileName == null) { + return null; + } + Holder inputStreamHolder = new Holder<>(); + clusterExecutionHelper.execute(reportOutput.getNodeRef().getOid(), (client, result1) -> { + client.path(ModelPublicConstants.CLUSTER_REPORT_FILE_PATH); + client.query(ModelPublicConstants.CLUSTER_REPORT_FILE_FILENAME_PARAMETER, fileName); + Response response = client.get(); + Response.StatusType statusInfo = response.getStatusInfo(); + LOGGER.debug("Retrieving report output file ({}) from {} finished with status {}: {}", + fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); + if (statusInfo.getFamily() == Response.Status.Family.SUCCESSFUL) { + Object entity = response.getEntity(); + if (entity == null || entity instanceof InputStream) { + inputStreamHolder.setValue((InputStream) entity); + // do NOT close the response; input stream will be closed later by the caller(s) + } else { + LOGGER.error("Content of the report output file retrieved from the remote node is not an InputStream; " + + "it is {} instead -- this is not currently supported", entity.getClass()); + response.close(); + } + } else { + LOGGER.warn("Retrieving report output file ({}) from {} finished with status {}: {}", + fileName, reportOutput.getNodeRef().getOid(), statusInfo.getStatusCode(), statusInfo.getReasonPhrase()); + result1.recordFatalError("Could not retrieve report output file: Got " + statusInfo.getStatusCode() + ": " + statusInfo.getReasonPhrase()); + response.close(); + } + }, new ClusterExecutionOptions().tryNodesInTransition(), "get report output", result); + result.computeStatusIfUnknown(); + return inputStreamHolder.getValue(); + } + } catch (IOException ex) { + LoggingUtils.logException(LOGGER, "Error while fetching file. File might not exist on the corresponding file system", ex); + result.recordPartialError("Error while fetching file. File might not exist on the corresponding file system. Reason: " + ex.getMessage(), ex); + throw ex; + } catch (ObjectNotFoundException | SchemaException | SecurityViolationException | CommunicationException + | ConfigurationException | ExpressionEvaluationException e) { + result.recordFatalError("Problem with reading report output. Reason: " + e.getMessage(), e); + throw e; + } finally { + result.computeStatusIfUnknown(); + } + } + + @Nullable + private String checkNodeAndFileName(File file, ReportOutputType reportOutput, OperationResult result) { + if (reportOutput.getNodeRef() == null || reportOutput.getNodeRef().getOid() == null) { + result.recordFatalError("Report output node OID is not defined."); + return null; + } + String fileName = file.getName(); + if (StringUtils.isEmpty(fileName)) { + result.recordFatalError("Report output file name is empty."); + return null; + } + return fileName; + } +} 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 3b6247f059a..8252ad38d67 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 @@ -1,467 +1,440 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.impl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.LocalizationService; -import com.evolveum.midpoint.model.api.ModelAuthorizationAction; -import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.schema.SchemaHelper; -import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; -import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; -import org.apache.commons.lang.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.audit.api.AuditService; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.api.ModelService; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluatorFactory; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; -import com.evolveum.midpoint.model.common.expression.script.groovy.GroovyScriptEvaluator; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.Objectable; -import com.evolveum.midpoint.prism.PrimitiveType; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.PrismValue; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.TypeFilter; -import com.evolveum.midpoint.report.api.ReportService; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.expression.ExpressionEvaluatorProfile; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.expression.ScriptExpressionProfile; -import com.evolveum.midpoint.schema.expression.TypedValue; -import com.evolveum.midpoint.schema.expression.VariablesMap; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ObjectQueryUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskManager; -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.evolveum.midpoint.xml.ns._public.common.common_3.JasperReportEngineConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.JasperReportTypeType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectVariableModeType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ScriptExpressionEvaluatorType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; - -@Component -public class ReportServiceImpl implements ReportService { - - private static final Trace LOGGER = TraceManager.getTrace(ReportServiceImpl.class); - - @Autowired private ModelService model; - @Autowired private TaskManager taskManager; - @Autowired private PrismContext prismContext; - @Autowired private SchemaHelper schemaHelper; - @Autowired private ExpressionFactory expressionFactory; - @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; - @Autowired private AuditService auditService; - @Autowired private FunctionLibrary logFunctionLibrary; - @Autowired private FunctionLibrary basicFunctionLibrary; - @Autowired private FunctionLibrary midpointFunctionLibrary; - @Autowired private LocalizationService localizationService; - @Autowired private SecurityEnforcer securityEnforcer; - @Autowired private ScriptExpressionFactory scriptExpressionFactory; - @Autowired private ArchetypeManager archetypeManager; - - @Override - public ObjectQuery parseQuery(PrismObject report, String query, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (StringUtils.isBlank(query)) { - return null; - } - - ObjectQuery parsedQuery; - try { - - ExpressionProfile expressionProfile = determineExpressionProfile(report, result); - - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); - SearchFilterType filterType = prismContext.parserFor(query).parseRealValue(SearchFilterType.class); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("filter(SearchFilterType)\n{}", filterType.debugDump(1)); - } - ObjectFilter filter = prismContext.getQueryConverter().parseFilter(filterType, UserType.class); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("filter(ObjectFilter)\n{}", filter.debugDump(1)); - } - if (!(filter instanceof TypeFilter)) { - throw new IllegalArgumentException( - "Defined query must contain type. Use 'type filter' in your report query."); - } - - ObjectFilter subFilter = ((TypeFilter) filter).getFilter(); - ObjectQuery q = prismContext.queryFactory().createQuery(subFilter); - - ExpressionVariables variables = new ExpressionVariables(); - variables.putAll(parameters); - - q = ExpressionUtil.evaluateQueryExpressions(q, variables, expressionProfile, expressionFactory, prismContext, - "parsing expression values for report", task, result); - ((TypeFilter) filter).setFilter(q.getFilter()); - ObjectQueryUtil.simplify(filter, prismContext); - parsedQuery = prismContext.queryFactory().createQuery(filter); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report query (parsed):\n{}", parsedQuery.debugDump(1)); - } - } catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - // TODO Auto-generated catch block - throw e; - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - return parsedQuery; - - } - - @Override - public Collection> searchObjects(ObjectQuery query, Collection> options, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - // List> results = new ArrayList<>(); - - // GetOperationOptions options = GetOperationOptions.createRaw(); - - if (!(query.getFilter() instanceof TypeFilter)) { - throw new IllegalArgumentException("Query must contain type filter."); - } - - TypeFilter typeFilter = (TypeFilter) query.getFilter(); - QName type = typeFilter.getType(); - Class clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(type); - if (clazz == null) { - clazz = prismContext.getSchemaRegistry().findObjectDefinitionByType(type).getCompileTimeClass(); - } - - ObjectQuery queryForSearch = prismContext.queryFactory().createQuery(typeFilter.getFilter()); - - // options.add(new - // SelectorOptions(GetOperationOptions.createResolveNames())); - GetOperationOptions getOptions = GetOperationOptions.createResolveNames(); - if (ShadowType.class.isAssignableFrom(clazz) && securityEnforcer.isAuthorized(ModelAuthorizationAction.RAW_OPERATION.getUrl(), null, AuthorizationParameters.EMPTY, null, task, parentResult)) { - LOGGER.trace("Setting searching in raw mode."); - getOptions.setRaw(Boolean.TRUE); // shadows in non-raw mode require specifying resource OID and kind (at least) - todo research this further - } else { - LOGGER.trace("Setting searching in noFetch mode. Shadows in non-raw mode require specifying resource OID and objectClass (kind) at least."); - getOptions.setNoFetch(Boolean.TRUE); - } - options = SelectorOptions.createCollection(getOptions); - List> results; - try { - results = model.searchObjects(clazz, queryForSearch, options, task, parentResult); - return results; - } catch (SchemaException | ObjectNotFoundException | SecurityViolationException - | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - // TODO Auto-generated catch block - throw e; - } - - } - - @Override - public Collection> evaluateScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - - ExpressionVariables variables = new ExpressionVariables(); - variables.putAll(parameters); - - TypedValue auditParams = getConvertedParams(parameters); - // special variable for audit report - variables.put("auditParams", auditParams); - - ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); - context.setVariables(variables); - context.setContextDescription("report script"); // TODO: improve - context.setTask(task); - context.setResult(result); - setupExpressionProfiles(context, report); - - Object o = evaluateReportScript(script, context, report); - - List> results = new ArrayList<>(); - if (o != null) { - - if (Collection.class.isAssignableFrom(o.getClass())) { - Collection resultSet = (Collection) o; - if (resultSet != null && !resultSet.isEmpty()) { - for (Object obj : resultSet) { - results.add(convertResultingObject(obj)); - } - } - - } else { - results.add(convertResultingObject(o)); - } - } - - return results; - } - - - private Collection runAuditQuery(String sqlWhereClause, TypedValue jasperAuditParams, OperationResult result) { - if (StringUtils.isBlank(sqlWhereClause)) { - return new ArrayList<>(); - } - - String query = "select * from m_audit_event as aer " + sqlWhereClause; - LOGGER.trace("AAAAAAA: query: {}", query); - Map auditParams = ReportUtils.jasperParamsToAuditParams((VariablesMap)jasperAuditParams.getValue()); - LOGGER.trace("AAAAAAA: auditParams:\n{}", auditParams); - List auditRecords = auditService.listRecords(query, auditParams, result); - LOGGER.trace("AAAAAAA: {} records", auditRecords==null?null:auditRecords.size()); - return auditRecords; - } - - @Override - public Object evaluate(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - - ExpressionVariables variables = new ExpressionVariables(); - variables.addVariableDefinitions(parameters); - - // special variable for audit report - variables.put("auditParams", getConvertedParams(parameters)); - - ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); - context.setVariables(variables); - context.setContextDescription("report script"); // TODO: improve - context.setTask(task); - context.setResult(result); - setupExpressionProfiles(context, report); - - Object o = evaluateReportScript(script, context, report); - - return o; - - } - - protected PrismContainerValue convertResultingObject(Object obj) { - if (obj instanceof PrismObject) { - return ((PrismObject) obj).asObjectable().asPrismContainerValue(); - } else if (obj instanceof Objectable) { - return ((Objectable) obj).asPrismContainerValue(); - } else if (obj instanceof PrismContainerValue) { - return (PrismContainerValue) obj; - } else if (obj instanceof Containerable) { - return ((Containerable) obj).asPrismContainerValue(); - } else { - throw new IllegalStateException("Reporting script should return something compatible with PrismContainerValue, not a " + obj.getClass()); - } - } - - @Override - public Collection evaluateAuditScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - Collection results = new ArrayList<>(); - - TypedValue auditParams = getConvertedParams(parameters); - ExpressionVariables variables = new ExpressionVariables(); - variables.put("auditParams", auditParams); - - ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); - context.setVariables(variables); - - context.setContextDescription("report script"); // TODO: improve - context.setTask(task); - context.setResult(result); - setupExpressionProfiles(context, report); - - Object o = evaluateReportScript(script, context, report); - - // HACK to allow audit reports where query is just a plain string. - // Oh my, this code is a mess. This needs a real rewrite. MID-5572 - if (o instanceof String) { - JasperReportEngineConfigurationType jasperConfig = report.asObjectable().getJasper(); - if (jasperConfig == null) { - throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); - } - JasperReportTypeType reportType = jasperConfig.getReportType(); - if (reportType == null) { - throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); - } - if (reportType.equals(JasperReportTypeType.AUDIT_SQL)) { - return runAuditQuery((String)o, auditParams, result); - } else { - throw new SchemaException("Jasper reportType is not set to auditSql, cannot determine how to use string query"); - } - } - - if (o != null) { - - if (Collection.class.isAssignableFrom(o.getClass())) { - Collection resultSet = (Collection) o; - if (resultSet != null && !resultSet.isEmpty()) { - for (Object obj : resultSet) { - if (!(obj instanceof AuditEventRecord)) { - LOGGER.warn("Skipping result, not an audit event record " + obj); - continue; - } - results.add((AuditEventRecord) obj); - } - - } - - } else { - results.add((AuditEventRecord) o); - } - } - - return results; - } - - private TypedValue getConvertedParams(VariablesMap parameters) { - if (parameters == null) { - return new TypedValue<>(null, VariablesMap.class); - } - - VariablesMap resultParamMap = new VariablesMap(); - Set> paramEntries = parameters.entrySet(); - for (Entry e : paramEntries) { - Object value = e.getValue().getValue(); - if (value instanceof PrismPropertyValue) { - resultParamMap.put(e.getKey(), e.getValue().createTransformed(((PrismPropertyValue) value).getValue())); - } else { - resultParamMap.put(e.getKey(), e.getValue()); - } - } - - return new TypedValue<>(resultParamMap, VariablesMap.class); - } - - private Collection createFunctionLibraries() { - FunctionLibrary midPointLib = new FunctionLibrary(); - midPointLib.setVariableName("report"); - midPointLib.setNamespace("http://midpoint.evolveum.com/xml/ns/public/function/report-3"); - ReportFunctions reportFunctions = new ReportFunctions(prismContext, schemaHelper, model, taskManager, auditService); - midPointLib.setGenericFunctions(reportFunctions); - - Collection functions = new ArrayList<>(); - functions.add(basicFunctionLibrary); - functions.add(logFunctionLibrary); - functions.add(midpointFunctionLibrary); - functions.add(midPointLib); - return functions; - } - - @Override - public PrismContext getPrismContext() { - return prismContext; - } - - public Object evaluateReportScript(String codeString, ScriptExpressionEvaluationContext context, PrismObject report) throws ExpressionEvaluationException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, SchemaException { - - ScriptExpressionEvaluatorConfigurationType defaultScriptConfiguration = report.asObjectable().getDefaultScriptConfiguration(); - ScriptExpressionEvaluatorType expressionType = new ScriptExpressionEvaluatorType(); - expressionType.setCode(codeString); - expressionType.setObjectVariableMode(defaultScriptConfiguration == null ? ObjectVariableModeType.OBJECT : defaultScriptConfiguration.getObjectVariableMode()); - context.setExpressionType(expressionType); - // Be careful about output definition here. We really do NOT want to set it. - // Not setting the definition means that we want raw value without any conversion. - // This is what we really want, because there may be exotic things such as JRTemplate going through those expressions - // We do not have any reasonable prism definitions for those. - - context.setFunctions(createFunctionLibraries()); - context.setObjectResolver(objectResolver); - - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( - expressionType, context.getOutputDefinition(), context.getExpressionProfile(), expressionFactory, context.getContextDescription(), context.getTask(), context.getResult()); - - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context.getTask(), context.getResult())); - List expressionResult; - try { - expressionResult = scriptExpression.evaluate(context); - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - - if (expressionResult == null || expressionResult.isEmpty()) { - return null; - } - if (expressionResult.size() > 1) { - throw new ExpressionEvaluationException("Too many results from expression "+context.getContextDescription()); - } - return expressionResult.get(0).getRealValue(); - } - - private ExpressionProfile determineExpressionProfile(PrismObject report, OperationResult result) throws SchemaException, ConfigurationException { - if (report == null) { - throw new IllegalArgumentException("No report defined, cannot determine profile"); - } - return archetypeManager.determineExpressionProfile(report, result); - } - - private void setupExpressionProfiles(ScriptExpressionEvaluationContext context, PrismObject report) throws SchemaException, ConfigurationException { - ExpressionProfile expressionProfile = determineExpressionProfile(report, context.getResult()); - LOGGER.trace("Using expression profile '"+(expressionProfile==null?null:expressionProfile.getIdentifier())+"' for report evaluation, determined from: {}", report); - context.setExpressionProfile(expressionProfile); - context.setScriptExpressionProfile(findScriptExpressionProfile(expressionProfile, report)); - } - - private ScriptExpressionProfile findScriptExpressionProfile(ExpressionProfile expressionProfile, PrismObject report) { - if (expressionProfile == null) { - return null; - } - ExpressionEvaluatorProfile scriptEvaluatorProfile = expressionProfile.getEvaluatorProfile(ScriptExpressionEvaluatorFactory.ELEMENT_NAME); - if (scriptEvaluatorProfile == null) { - return null; - } - return scriptEvaluatorProfile.getScriptExpressionProfile(getScriptLanguageName(report)); - } - - private String getScriptLanguageName(PrismObject report) { - // Hardcoded for now - return GroovyScriptEvaluator.LANGUAGE_NAME; - } - - @Override - public PrismObject getReportDefinition(String reportOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - return model.getObject(ReportType.class, reportOid, null, task, result); - } - - @Override - public boolean isAuthorizedToRunReport(PrismObject report, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - AuthorizationParameters params = AuthorizationParameters.Builder.buildObject(report); - return securityEnforcer.isAuthorized(ModelAuthorizationAction.RUN_REPORT.getUrl(), null, params, null, task, result); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.impl; + +import java.util.*; +import java.util.Map.Entry; +import javax.xml.namespace.QName; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.audit.api.AuditService; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.model.api.ModelService; +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.common.expression.functions.FunctionLibrary; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluationContext; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionEvaluatorFactory; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; +import com.evolveum.midpoint.model.common.expression.script.groovy.GroovyScriptEvaluator; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.TypeFilter; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.report.api.ReportService; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SchemaHelper; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.expression.*; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectQueryUtil; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskManager; +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.query_3.SearchFilterType; + +@Component +public class ReportServiceImpl implements ReportService { + + private static final Trace LOGGER = TraceManager.getTrace(ReportServiceImpl.class); + + @Autowired private ModelService model; + @Autowired private TaskManager taskManager; + @Autowired private PrismContext prismContext; + @Autowired private SchemaHelper schemaHelper; + @Autowired private ExpressionFactory expressionFactory; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; + @Autowired private AuditService auditService; + @Autowired private FunctionLibrary logFunctionLibrary; + @Autowired private FunctionLibrary basicFunctionLibrary; + @Autowired private FunctionLibrary midpointFunctionLibrary; + @Autowired private SecurityEnforcer securityEnforcer; + @Autowired private ScriptExpressionFactory scriptExpressionFactory; + @Autowired private ArchetypeManager archetypeManager; + + @Override + public ObjectQuery parseQuery(PrismObject report, String query, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + if (StringUtils.isBlank(query)) { + return null; + } + + ObjectQuery parsedQuery; + try { + + ExpressionProfile expressionProfile = determineExpressionProfile(report, result); + + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); + SearchFilterType filterType = prismContext.parserFor(query).parseRealValue(SearchFilterType.class); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("filter(SearchFilterType)\n{}", filterType.debugDump(1)); + } + ObjectFilter filter = prismContext.getQueryConverter().parseFilter(filterType, UserType.class); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("filter(ObjectFilter)\n{}", filter.debugDump(1)); + } + if (!(filter instanceof TypeFilter)) { + throw new IllegalArgumentException( + "Defined query must contain type. Use 'type filter' in your report query."); + } + + ObjectFilter subFilter = ((TypeFilter) filter).getFilter(); + ObjectQuery q = prismContext.queryFactory().createQuery(subFilter); + + ExpressionVariables variables = new ExpressionVariables(); + variables.putAll(parameters); + + q = ExpressionUtil.evaluateQueryExpressions(q, variables, expressionProfile, expressionFactory, prismContext, + "parsing expression values for report", task, result); + ((TypeFilter) filter).setFilter(q.getFilter()); + ObjectQueryUtil.simplify(filter, prismContext); + parsedQuery = prismContext.queryFactory().createQuery(filter); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("report query (parsed):\n{}", parsedQuery.debugDump(1)); + } + } catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException + | CommunicationException | ConfigurationException | SecurityViolationException e) { + LOGGER.error("Cannot convert query, reason: {}", e.getMessage()); + throw e; + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + return parsedQuery; + + } + + @Override + public Collection> searchObjects(ObjectQuery query, Collection> options, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + // List> results = new ArrayList<>(); + + // GetOperationOptions options = GetOperationOptions.createRaw(); + + if (!(query.getFilter() instanceof TypeFilter)) { + throw new IllegalArgumentException("Query must contain type filter."); + } + + TypeFilter typeFilter = (TypeFilter) query.getFilter(); + QName type = typeFilter.getType(); + Class clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(type); + if (clazz == null) { + PrismObjectDefinition objectDef = prismContext.getSchemaRegistry().findObjectDefinitionByType(type); + if (objectDef == null) { + throw new SchemaException("Undefined object type used in query, type: " + type); + } + clazz = objectDef.getCompileTimeClass(); + } + + ObjectQuery queryForSearch = prismContext.queryFactory().createQuery(typeFilter.getFilter()); + + // options.add(new + // SelectorOptions(GetOperationOptions.createResolveNames())); + GetOperationOptions getOptions = GetOperationOptions.createResolveNames(); + if (ShadowType.class.isAssignableFrom(clazz) && securityEnforcer.isAuthorized(ModelAuthorizationAction.RAW_OPERATION.getUrl(), null, AuthorizationParameters.EMPTY, null, task, parentResult)) { + LOGGER.trace("Setting searching in raw mode."); + getOptions.setRaw(Boolean.TRUE); // shadows in non-raw mode require specifying resource OID and kind (at least) - todo research this further + } else { + LOGGER.trace("Setting searching in noFetch mode. Shadows in non-raw mode require specifying resource OID and objectClass (kind) at least."); + getOptions.setNoFetch(Boolean.TRUE); + } + options = SelectorOptions.createCollection(getOptions); + List> results; + try { + results = model.searchObjects(clazz, queryForSearch, options, task, parentResult); + return results; + } catch (SchemaException | ObjectNotFoundException | SecurityViolationException + | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + // TODO Auto-generated catch block + throw e; + } + + } + + @Override + public Collection> evaluateScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + ExpressionVariables variables = new ExpressionVariables(); + variables.putAll(parameters); + + TypedValue auditParams = getConvertedParams(parameters); + // special variable for audit report + variables.put("auditParams", auditParams); + + ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); + context.setVariables(variables); + context.setContextDescription("report script"); // TODO: improve + context.setTask(task); + context.setResult(result); + setupExpressionProfiles(context, report); + + Object o = evaluateReportScript(script, context, report); + + List> results = new ArrayList<>(); + if (o != null) { + + if (Collection.class.isAssignableFrom(o.getClass())) { + Collection resultSet = (Collection) o; + if (resultSet != null && !resultSet.isEmpty()) { + for (Object obj : resultSet) { + results.add(convertResultingObject(obj)); + } + } + + } else { + results.add(convertResultingObject(o)); + } + } + + return results; + } + + + private Collection runAuditQuery(String sqlWhereClause, TypedValue jasperAuditParams, OperationResult result) { + if (StringUtils.isBlank(sqlWhereClause)) { + return new ArrayList<>(); + } + + String query = "select * from m_audit_event as aer " + sqlWhereClause; + LOGGER.trace("AAAAAAA: query: {}", query); + Map auditParams = ReportUtils.jasperParamsToAuditParams((VariablesMap)jasperAuditParams.getValue()); + LOGGER.trace("AAAAAAA: auditParams:\n{}", auditParams); + List auditRecords = auditService.listRecords(query, auditParams, result); + LOGGER.trace("AAAAAAA: {} records", auditRecords==null?null:auditRecords.size()); + return auditRecords; + } + + @Override + public Object evaluate(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + + ExpressionVariables variables = new ExpressionVariables(); + variables.addVariableDefinitions(parameters); + + // special variable for audit report + variables.put("auditParams", getConvertedParams(parameters)); + + ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); + context.setVariables(variables); + context.setContextDescription("report script"); // TODO: improve + context.setTask(task); + context.setResult(result); + setupExpressionProfiles(context, report); + + Object o = evaluateReportScript(script, context, report); + + return o; + + } + + protected PrismContainerValue convertResultingObject(Object obj) { + if (obj instanceof PrismObject) { + return ((PrismObject) obj).asObjectable().asPrismContainerValue(); + } else if (obj instanceof Objectable) { + return ((Objectable) obj).asPrismContainerValue(); + } else if (obj instanceof PrismContainerValue) { + return (PrismContainerValue) obj; + } else if (obj instanceof Containerable) { + return ((Containerable) obj).asPrismContainerValue(); + } else { + throw new IllegalStateException("Reporting script should return something compatible with PrismContainerValue, not a " + obj.getClass()); + } + } + + @Override + public Collection evaluateAuditScript(PrismObject report, String script, VariablesMap parameters, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + Collection results = new ArrayList<>(); + + TypedValue auditParams = getConvertedParams(parameters); + ExpressionVariables variables = new ExpressionVariables(); + variables.put("auditParams", auditParams); + + ScriptExpressionEvaluationContext context = new ScriptExpressionEvaluationContext(); + context.setVariables(variables); + + context.setContextDescription("report script"); // TODO: improve + context.setTask(task); + context.setResult(result); + setupExpressionProfiles(context, report); + + Object o = evaluateReportScript(script, context, report); + + // HACK to allow audit reports where query is just a plain string. + // Oh my, this code is a mess. This needs a real rewrite. MID-5572 + if (o instanceof String) { + JasperReportEngineConfigurationType jasperConfig = report.asObjectable().getJasper(); + if (jasperConfig == null) { + throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); + } + JasperReportTypeType reportType = jasperConfig.getReportType(); + if (reportType == null) { + throw new SchemaException("Jasper reportType not set, cannot determine how to use string query"); + } + if (reportType.equals(JasperReportTypeType.AUDIT_SQL)) { + return runAuditQuery((String)o, auditParams, result); + } else { + throw new SchemaException("Jasper reportType is not set to auditSql, cannot determine how to use string query"); + } + } + + if (o != null) { + + if (Collection.class.isAssignableFrom(o.getClass())) { + Collection resultSet = (Collection) o; + if (resultSet != null && !resultSet.isEmpty()) { + for (Object obj : resultSet) { + if (!(obj instanceof AuditEventRecord)) { + LOGGER.warn("Skipping result, not an audit event record " + obj); + continue; + } + results.add((AuditEventRecord) obj); + } + + } + + } else { + results.add((AuditEventRecord) o); + } + } + + return results; + } + + private TypedValue getConvertedParams(VariablesMap parameters) { + if (parameters == null) { + return new TypedValue<>(null, VariablesMap.class); + } + + VariablesMap resultParamMap = new VariablesMap(); + Set> paramEntries = parameters.entrySet(); + for (Entry e : paramEntries) { + Object value = e.getValue().getValue(); + if (value instanceof PrismPropertyValue) { + resultParamMap.put(e.getKey(), e.getValue().createTransformed(((PrismPropertyValue) value).getValue())); + } else { + resultParamMap.put(e.getKey(), e.getValue()); + } + } + + return new TypedValue<>(resultParamMap, VariablesMap.class); + } + + private Collection createFunctionLibraries() { + FunctionLibrary midPointLib = new FunctionLibrary(); + midPointLib.setVariableName("report"); + midPointLib.setNamespace("http://midpoint.evolveum.com/xml/ns/public/function/report-3"); + ReportFunctions reportFunctions = new ReportFunctions(prismContext, schemaHelper, model, taskManager, auditService); + midPointLib.setGenericFunctions(reportFunctions); + + Collection functions = new ArrayList<>(); + functions.add(basicFunctionLibrary); + functions.add(logFunctionLibrary); + functions.add(midpointFunctionLibrary); + functions.add(midPointLib); + return functions; + } + + @Override + public PrismContext getPrismContext() { + return prismContext; + } + + public Object evaluateReportScript(String codeString, ScriptExpressionEvaluationContext context, PrismObject report) throws ExpressionEvaluationException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, SchemaException { + + ScriptExpressionEvaluatorConfigurationType defaultScriptConfiguration = report.asObjectable().getDefaultScriptConfiguration(); + ScriptExpressionEvaluatorType expressionType = new ScriptExpressionEvaluatorType(); + expressionType.setCode(codeString); + expressionType.setObjectVariableMode(defaultScriptConfiguration == null ? ObjectVariableModeType.OBJECT : defaultScriptConfiguration.getObjectVariableMode()); + context.setExpressionType(expressionType); + // Be careful about output definition here. We really do NOT want to set it. + // Not setting the definition means that we want raw value without any conversion. + // This is what we really want, because there may be exotic things such as JRTemplate going through those expressions + // We do not have any reasonable prism definitions for those. + + context.setFunctions(createFunctionLibraries()); + context.setObjectResolver(objectResolver); + + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression( + expressionType, context.getOutputDefinition(), context.getExpressionProfile(), expressionFactory, context.getContextDescription(), context.getTask(), context.getResult()); + + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(context.getTask(), context.getResult())); + List expressionResult; + try { + expressionResult = scriptExpression.evaluate(context); + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + + if (expressionResult == null || expressionResult.isEmpty()) { + return null; + } + if (expressionResult.size() > 1) { + throw new ExpressionEvaluationException("Too many results from expression "+context.getContextDescription()); + } + return expressionResult.get(0).getRealValue(); + } + + private ExpressionProfile determineExpressionProfile(PrismObject report, OperationResult result) throws SchemaException, ConfigurationException { + if (report == null) { + throw new IllegalArgumentException("No report defined, cannot determine profile"); + } + return archetypeManager.determineExpressionProfile(report, result); + } + + private void setupExpressionProfiles(ScriptExpressionEvaluationContext context, PrismObject report) throws SchemaException, ConfigurationException { + ExpressionProfile expressionProfile = determineExpressionProfile(report, context.getResult()); + LOGGER.trace("Using expression profile '"+(expressionProfile==null?null:expressionProfile.getIdentifier())+"' for report evaluation, determined from: {}", report); + context.setExpressionProfile(expressionProfile); + context.setScriptExpressionProfile(findScriptExpressionProfile(expressionProfile, report)); + } + + private ScriptExpressionProfile findScriptExpressionProfile(ExpressionProfile expressionProfile, PrismObject report) { + if (expressionProfile == null) { + return null; + } + ExpressionEvaluatorProfile scriptEvaluatorProfile = expressionProfile.getEvaluatorProfile(ScriptExpressionEvaluatorFactory.ELEMENT_NAME); + if (scriptEvaluatorProfile == null) { + return null; + } + return scriptEvaluatorProfile.getScriptExpressionProfile(getScriptLanguageName(report)); + } + + private String getScriptLanguageName(PrismObject report) { + // Hardcoded for now + return GroovyScriptEvaluator.LANGUAGE_NAME; + } + + @Override + public PrismObject getReportDefinition(String reportOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + return model.getObject(ReportType.class, reportOid, null, task, result); + } + + @Override + public boolean isAuthorizedToRunReport(PrismObject report, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + AuthorizationParameters params = AuthorizationParameters.Builder.buildObject(report); + return securityEnforcer.isAuthorized(ModelAuthorizationAction.RUN_REPORT.getUrl(), null, params, null, task, result); + } +} diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportWebService.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportWebService.java index e8b5a3230c0..7e7650053a0 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportWebService.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportWebService.java @@ -1,213 +1,213 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.report.impl; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.logging.Logger; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.SelectorQualifiedGetOptionsType; - -import org.apache.commons.lang3.StringUtils; -import org.apache.cxf.interceptor.Fault; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import com.evolveum.midpoint.audit.api.AuditEventRecord; -import com.evolveum.midpoint.model.api.ModelAuthorizationAction; -import com.evolveum.midpoint.model.common.util.AbstractModelWebService; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.report.api.ReportPort; -import com.evolveum.midpoint.report.api.ReportService; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.expression.VariablesMap; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; -import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; -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.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.api_types_3.ObjectListType; -import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventRecordListType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportParameterType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; -import com.evolveum.midpoint.xml.ns._public.report.report_3.RemoteReportParameterType; -import com.evolveum.midpoint.xml.ns._public.report.report_3.RemoteReportParametersType; -import com.evolveum.midpoint.xml.ns._public.report.report_3.ReportPortType; - -@Service -public class ReportWebService extends AbstractModelWebService implements ReportPortType, ReportPort { - - private static final String OP_EVALUATE_SCRIPT = ReportWebService.class.getName() + ".evaluateScript"; - private static final String OP_EVALUATE_AUDIT_SCRIPT = ReportWebService.class.getName() + ".evaluateAuditScript"; - private static final String OP_PROCESS_REPORT = ReportWebService.class.getName() + ".processReport"; - - private static final Trace LOGGER = TraceManager.getTrace(ReportWebService.class); - - @Autowired private PrismContext prismContext; - @Autowired private ReportService reportService; - - @Override - public ObjectListType evaluateScript(String reportOid, String script, RemoteReportParametersType parameters) { - - Task task = createTaskInstance(OP_EVALUATE_SCRIPT); - auditLogin(task); - OperationResult operationResult = task.getResult(); - - try { - - PrismObject report = authorizeReportProcessing("evaluateScript", reportOid, task, operationResult); - - VariablesMap params = getParamsMap(parameters); - Collection resultList = reportService.evaluateScript(report, script, params, task, operationResult); - return createObjectListType(resultList); - } catch (Throwable e) { - throw new Fault(e); - } - - } - - @Override - public AuditEventRecordListType evaluateAuditScript(String reportOid, String script, RemoteReportParametersType parameters) { - - Task task = createTaskInstance(OP_EVALUATE_AUDIT_SCRIPT); - auditLogin(task); - OperationResult operationResult = task.getResult(); - - try { - PrismObject report = authorizeReportProcessing("evaluateAuditScript", reportOid, task, operationResult); - - VariablesMap params = getParamsMap(parameters); - Collection resultList = reportService.evaluateAuditScript(report, script, params, task, operationResult); - return createAuditEventRecordListType(resultList); - } catch (Throwable e) { - // TODO Auto-generated catch block - throw new Fault(e); - } - - } - - private VariablesMap getParamsMap(RemoteReportParametersType parametersType) throws SchemaException { - - prismContext.adopt(parametersType); - VariablesMap parametersMap = new VariablesMap(); - if (parametersType == null || parametersType.getRemoteParameter() == null - || parametersType.getRemoteParameter().isEmpty()) { - return parametersMap; - } - List items = parametersType.getRemoteParameter(); - for (RemoteReportParameterType item : items) { - String paramName = item.getParameterName(); - ReportParameterType param = item.getParameterValue(); - if (param == null){ - parametersMap.put(paramName, null); - continue; - } - if (param.getAny().size() == 1) { - parametersMap.put(paramName, param.getAny().get(0), param.getAny().get(0).getClass()); - } else { - parametersMap.put(paramName, param.getAny(), List.class); - } - - } - - return parametersMap; - - - } - - private ObjectListType createObjectListType(Collection resultList) { - if (resultList == null) { - return new ObjectListType(); - } - - ObjectListType results = new ObjectListType(); - int skipped = 0; - for (Object object : resultList) { - if (object instanceof PrismObject) { - results.getObject().add(((PrismObject) object).asObjectable()); - } else if (object instanceof ObjectType) { - results.getObject().add((ObjectType) object); - } else { - skipped++; - } - } - if (skipped > 0) { - LOGGER.warn("{} non-PrismObject data objects not returned, as these are not supported by ReportWebService yet", skipped); - } - - return results; - } - - private AuditEventRecordListType createAuditEventRecordListType(Collection resultList) { - if (resultList == null) { - return new AuditEventRecordListType(); - } - - AuditEventRecordListType results = new AuditEventRecordListType(); - for (AuditEventRecord auditRecord : resultList) { - results.getObject().add(auditRecord.createAuditEventRecordType(true)); - } - - return results; - } - - - @Override - public ObjectListType processReport(String reportOid, String query, RemoteReportParametersType parameters, SelectorQualifiedGetOptionsType options) { - - Task task = createTaskInstance(OP_PROCESS_REPORT); - auditLogin(task); - OperationResult operationResult = task.getResult(); - - try { - - PrismObject report = authorizeReportProcessing("processReport", reportOid, task, operationResult); - - VariablesMap parametersMap = getParamsMap(parameters); - ObjectQuery q = reportService.parseQuery(report, query, parametersMap, task, operationResult); - Collection> resultList = reportService.searchObjects(q, - MiscSchemaUtil.optionsTypeToOptions(options, prismContext), task, operationResult); - - return createObjectListType(resultList); - } catch (SchemaException | ObjectNotFoundException | SecurityViolationException - | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { - // TODO Auto-generated catch block - throw new Fault(e); - } - - } - - private PrismObject authorizeReportProcessing(String operationName, String reportOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - if (StringUtils.isBlank(reportOid)) { - LOGGER.error("No report OID was specified during access to report service operation {}", operationName); - throw new SchemaException("No report OID specified"); - } - PrismObject report = reportService.getReportDefinition(reportOid, task, result); - if (!reportService.isAuthorizedToRunReport(report, task, result)) { - LOGGER.error("User is not authorized to run report {}, therefore access to report service operation {} was denied", report, operationName); - throw new Fault(new SecurityViolationException("Not authorized")); - } - return report; - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.report.impl; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.logging.Logger; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.SelectorQualifiedGetOptionsType; + +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.interceptor.Fault; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +import com.evolveum.midpoint.model.common.util.AbstractModelWebService; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.report.api.ReportPort; +import com.evolveum.midpoint.report.api.ReportService; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.expression.VariablesMap; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; +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.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.api_types_3.ObjectListType; +import com.evolveum.midpoint.xml.ns._public.common.audit_3.AuditEventRecordListType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportParameterType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportType; +import com.evolveum.midpoint.xml.ns._public.report.report_3.RemoteReportParameterType; +import com.evolveum.midpoint.xml.ns._public.report.report_3.RemoteReportParametersType; +import com.evolveum.midpoint.xml.ns._public.report.report_3.ReportPortType; + +@Service +public class ReportWebService extends AbstractModelWebService implements ReportPortType, ReportPort { + + private static final String OP_EVALUATE_SCRIPT = ReportWebService.class.getName() + ".evaluateScript"; + private static final String OP_EVALUATE_AUDIT_SCRIPT = ReportWebService.class.getName() + ".evaluateAuditScript"; + private static final String OP_PROCESS_REPORT = ReportWebService.class.getName() + ".processReport"; + + private static final Trace LOGGER = TraceManager.getTrace(ReportWebService.class); + + @Autowired private PrismContext prismContext; + @Autowired private ReportService reportService; + + @Override + public ObjectListType evaluateScript(String reportOid, String script, RemoteReportParametersType parameters) { + + Task task = createTaskInstance(OP_EVALUATE_SCRIPT); + auditLogin(task); + OperationResult operationResult = task.getResult(); + + try { + + PrismObject report = authorizeReportProcessing("evaluateScript", reportOid, task, operationResult); + + VariablesMap params = getParamsMap(parameters); + Collection resultList = reportService.evaluateScript(report, script, params, task, operationResult); + return createObjectListType(resultList); + } catch (Throwable e) { + throw new Fault(e); + } + + } + + @Override + public AuditEventRecordListType evaluateAuditScript(String reportOid, String script, RemoteReportParametersType parameters) { + + Task task = createTaskInstance(OP_EVALUATE_AUDIT_SCRIPT); + auditLogin(task); + OperationResult operationResult = task.getResult(); + + try { + PrismObject report = authorizeReportProcessing("evaluateAuditScript", reportOid, task, operationResult); + + VariablesMap params = getParamsMap(parameters); + Collection resultList = reportService.evaluateAuditScript(report, script, params, task, operationResult); + return createAuditEventRecordListType(resultList); + } catch (Throwable e) { + // TODO Auto-generated catch block + throw new Fault(e); + } + + } + + private VariablesMap getParamsMap(RemoteReportParametersType parametersType) throws SchemaException { + + prismContext.adopt(parametersType); + VariablesMap parametersMap = new VariablesMap(); + if (parametersType == null || parametersType.getRemoteParameter() == null + || parametersType.getRemoteParameter().isEmpty()) { + return parametersMap; + } + List items = parametersType.getRemoteParameter(); + for (RemoteReportParameterType item : items) { + String paramName = item.getParameterName(); + ReportParameterType param = item.getParameterValue(); + if (param == null){ + parametersMap.put(paramName, null); + continue; + } + if (param.getAny().size() == 1) { + parametersMap.put(paramName, param.getAny().get(0), param.getAny().get(0).getClass()); + } else { + parametersMap.put(paramName, param.getAny(), List.class); + } + + } + + return parametersMap; + + + } + + private ObjectListType createObjectListType(Collection resultList) { + if (resultList == null) { + return new ObjectListType(); + } + + ObjectListType results = new ObjectListType(); + int skipped = 0; + for (Object object : resultList) { + if (object instanceof PrismObject) { + results.getObject().add(((PrismObject) object).asObjectable()); + } else if (object instanceof ObjectType) { + results.getObject().add((ObjectType) object); + } else { + skipped++; + } + } + if (skipped > 0) { + LOGGER.warn("{} non-PrismObject data objects not returned, as these are not supported by ReportWebService yet", skipped); + } + + return results; + } + + private AuditEventRecordListType createAuditEventRecordListType(Collection resultList) { + if (resultList == null) { + return new AuditEventRecordListType(); + } + + AuditEventRecordListType results = new AuditEventRecordListType(); + for (AuditEventRecord auditRecord : resultList) { + results.getObject().add(auditRecord.createAuditEventRecordType(true)); + } + + return results; + } + + + @Override + public ObjectListType processReport(String reportOid, String query, RemoteReportParametersType parameters, SelectorQualifiedGetOptionsType options) { + + Task task = createTaskInstance(OP_PROCESS_REPORT); + auditLogin(task); + OperationResult operationResult = task.getResult(); + + try { + + PrismObject report = authorizeReportProcessing("processReport", reportOid, task, operationResult); + + VariablesMap parametersMap = getParamsMap(parameters); + ObjectQuery q = reportService.parseQuery(report, query, parametersMap, task, operationResult); + Collection> resultList = (Collection) reportService.searchObjects(q, + MiscSchemaUtil.optionsTypeToOptions(options, prismContext), task, operationResult); + + return createObjectListType(resultList); + } catch (SchemaException | ObjectNotFoundException | SecurityViolationException + | CommunicationException | ConfigurationException | ExpressionEvaluationException e) { + // TODO Auto-generated catch block + throw new Fault(e); + } + + } + + private PrismObject authorizeReportProcessing(String operationName, String reportOid, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + if (StringUtils.isBlank(reportOid)) { + LOGGER.error("No report OID was specified during access to report service operation {}", operationName); + throw new SchemaException("No report OID specified"); + } + PrismObject report = reportService.getReportDefinition(reportOid, task, result); + if (!reportService.isAuthorizedToRunReport(report, task, result)) { + LOGGER.error("User is not authorized to run report {}, therefore access to report service operation {} was denied", report, operationName); + throw new Fault(new SecurityViolationException("Not authorized")); + } + return report; + } + +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/ExpressionEvaluationHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/ExpressionEvaluationHelper.java index bf887db178b..36b60539dfb 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/ExpressionEvaluationHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processes/common/ExpressionEvaluationHelper.java @@ -1,122 +1,122 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.wf.impl.processes.common; - -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.path.ItemName; -import com.evolveum.midpoint.repo.common.expression.*; -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.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.QNameUtil; -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.xml.ns._public.common.common_3.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; - -/** - * @author mederly - */ -@Component -public class ExpressionEvaluationHelper { - - @Autowired private ExpressionFactory expressionFactory; - @Autowired private PrismContext prismContext; - - public List evaluateRefExpressions(List expressions, - ExpressionVariables variables, String contextDescription, - Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - List retval = new ArrayList<>(); - for (ExpressionType expression : expressions) { - retval.addAll(evaluateRefExpression(expression, variables, contextDescription, task, result)); - } - return retval; - } - - public List evaluateRefExpression(ExpressionType expressionType, ExpressionVariables variables, - String contextDescription, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return evaluateExpression(expressionType, variables, contextDescription, ObjectReferenceType.class, - ObjectReferenceType.COMPLEX_TYPE, false, ExpressionUtil.createRefConvertor(UserType.COMPLEX_TYPE), task, result); - } - - @SuppressWarnings("unchecked") - @NotNull - public List evaluateExpression(ExpressionType expressionType, ExpressionVariables variables, - String contextDescription, Class clazz, QName typeName, - boolean multiValued, Function additionalConvertor, Task task, - OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - MutableItemDefinition resultDef; - ItemName resultName = new ItemName(SchemaConstants.NS_C, "result"); - if (QNameUtil.match(typeName, ObjectReferenceType.COMPLEX_TYPE)) { - resultDef = prismContext.definitionFactory().createReferenceDefinition(resultName, typeName); - } else { - resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, typeName); - } - if (multiValued) { - resultDef.setMaxOccurs(-1); - } - Expression expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), contextDescription, task, result); - ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, variables, contextDescription, task); - context.setAdditionalConvertor(additionalConvertor); - PrismValueDeltaSetTriple exprResultTriple = ModelExpressionThreadLocalHolder - .evaluateAnyExpressionInContext(expression, context, task, result); - List list = new ArrayList<>(); - for (PrismValue pv : exprResultTriple.getZeroSet()) { - T realValue; - if (pv instanceof PrismReferenceValue) { - // pv.getRealValue sometimes returns synthesized Referencable, not ObjectReferenceType - // If we would stay with that we would need to make many changes throughout workflow module. - // So it is safer to stay with ObjectReferenceType. - ObjectReferenceType ort = new ObjectReferenceType(); - ort.setupReferenceValue((PrismReferenceValue) pv); - realValue = (T) ort; - } else { - realValue = pv.getRealValue(); - } - list.add(realValue); - } - return list; - } - - public boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, - String contextDescription, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - Collection values = evaluateExpression(expressionType, expressionVariables, contextDescription, - Boolean.class, DOMUtil.XSD_BOOLEAN, false, null, task, result); - return MiscUtil.getSingleValue(values, false, contextDescription); - } - - public String evaluateStringExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, - String contextDescription, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - Collection values = evaluateExpression(expressionType, expressionVariables, contextDescription, - String.class, DOMUtil.XSD_STRING, false, null, task, result); - return MiscUtil.getSingleValue(values, null, contextDescription); - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.wf.impl.processes.common; + +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.path.ItemName; +import com.evolveum.midpoint.repo.common.expression.*; +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.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.QNameUtil; +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.xml.ns._public.common.common_3.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +/** + * @author mederly + */ +@Component +public class ExpressionEvaluationHelper { + + @Autowired private ExpressionFactory expressionFactory; + @Autowired private PrismContext prismContext; + + public List evaluateRefExpressions(List expressions, + ExpressionVariables variables, String contextDescription, + Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + List retval = new ArrayList<>(); + for (ExpressionType expression : expressions) { + retval.addAll(evaluateRefExpression(expression, variables, contextDescription, task, result)); + } + return retval; + } + + public List evaluateRefExpression(ExpressionType expressionType, ExpressionVariables variables, + String contextDescription, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + return evaluateExpression(expressionType, variables, contextDescription, ObjectReferenceType.class, + ObjectReferenceType.COMPLEX_TYPE, false, ExpressionUtil.createRefConvertor(UserType.COMPLEX_TYPE), task, result); + } + + @SuppressWarnings("unchecked") + @NotNull + public List evaluateExpression(ExpressionType expressionType, ExpressionVariables variables, + String contextDescription, Class clazz, QName typeName, + boolean multiValued, Function additionalConvertor, Task task, + OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + MutableItemDefinition resultDef; + ItemName resultName = new ItemName(SchemaConstants.NS_C, "result"); + if (QNameUtil.match(typeName, ObjectReferenceType.COMPLEX_TYPE)) { + resultDef = prismContext.definitionFactory().createReferenceDefinition(resultName, typeName); + } else { + resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, typeName); + } + if (multiValued) { + resultDef.setMaxOccurs(-1); + } + Expression expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), contextDescription, task, result); + ExpressionEvaluationContext context = new ExpressionEvaluationContext(null, variables, contextDescription, task); + context.setAdditionalConvertor(additionalConvertor); + PrismValueDeltaSetTriple exprResultTriple = ModelExpressionThreadLocalHolder + .evaluateAnyExpressionInContext(expression, context, task, result); + List list = new ArrayList<>(); + for (PrismValue pv : exprResultTriple.getZeroSet()) { + T realValue; + if (pv instanceof PrismReferenceValue) { + // pv.getRealValue sometimes returns synthesized Referencable, not ObjectReferenceType + // If we would stay with that we would need to make many changes throughout workflow module. + // So it is safer to stay with ObjectReferenceType. + ObjectReferenceType ort = new ObjectReferenceType(); + ort.setupReferenceValue((PrismReferenceValue) pv); + realValue = (T) ort; + } else { + realValue = pv.getRealValue(); + } + list.add(realValue); + } + return list; + } + + public boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, + String contextDescription, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + Collection values = evaluateExpression(expressionType, expressionVariables, contextDescription, + Boolean.class, DOMUtil.XSD_BOOLEAN, false, null, task, result); + return MiscUtil.getSingleValue(values, false, contextDescription); + } + + public String evaluateStringExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, + String contextDescription, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + Collection values = evaluateExpression(expressionType, expressionVariables, contextDescription, + String.class, DOMUtil.XSD_STRING, false, null, task, result); + return MiscUtil.getSingleValue(values, null, contextDescription); + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GcpExpressionHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GcpExpressionHelper.java index 70a60d469a8..d73420a5a34 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GcpExpressionHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/general/GcpExpressionHelper.java @@ -1,95 +1,95 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.wf.impl.processors.general; - -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -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.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -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.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.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.GeneralChangeProcessorScenarioType; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.namespace.QName; - -import java.util.Collection; - -/** - * @author mederly - */ -@Component -public class GcpExpressionHelper { - - private static final Trace LOGGER = TraceManager.getTrace(GcpExpressionHelper.class); - - @Autowired - private ExpressionFactory expressionFactory; - - boolean evaluateActivationCondition(GeneralChangeProcessorScenarioType scenarioType, ModelContext context, Task taskFromModel, OperationResult result) throws SchemaException { - ExpressionType conditionExpression = scenarioType.getActivationCondition(); - - if (conditionExpression == null) { - return true; - } - - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_MODEL_CONTEXT, context, ModelContext.class); - - boolean start; - try { - start = evaluateBooleanExpression(conditionExpression, variables, "workflow activation condition", taskFromModel, result); - } catch (ObjectNotFoundException|ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { - throw new SystemException("Couldn't evaluate generalChangeProcessor activation condition", e); - } - return start; - } - - private boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String opContext, Task taskFromModel, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - PrismContext prismContext = expressionFactory.getPrismContext(); - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_BOOLEAN); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), opContext, taskFromModel, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, opContext, taskFromModel); - PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, params, taskFromModel, result); - - Collection> exprResult = exprResultTriple.getZeroSet(); - if (exprResult.size() == 0) { - return false; - } else if (exprResult.size() > 1) { - throw new IllegalStateException("Expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); - } - Boolean boolResult = exprResult.iterator().next().getValue(); - return boolResult != null ? boolResult : false; - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.wf.impl.processors.general; + +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +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.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +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.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.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.GeneralChangeProcessorScenarioType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; + +import java.util.Collection; + +/** + * @author mederly + */ +@Component +public class GcpExpressionHelper { + + private static final Trace LOGGER = TraceManager.getTrace(GcpExpressionHelper.class); + + @Autowired + private ExpressionFactory expressionFactory; + + boolean evaluateActivationCondition(GeneralChangeProcessorScenarioType scenarioType, ModelContext context, Task taskFromModel, OperationResult result) throws SchemaException { + ExpressionType conditionExpression = scenarioType.getActivationCondition(); + + if (conditionExpression == null) { + return true; + } + + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_MODEL_CONTEXT, context, ModelContext.class); + + boolean start; + try { + start = evaluateBooleanExpression(conditionExpression, variables, "workflow activation condition", taskFromModel, result); + } catch (ObjectNotFoundException|ExpressionEvaluationException | CommunicationException | ConfigurationException | SecurityViolationException e) { + throw new SystemException("Couldn't evaluate generalChangeProcessor activation condition", e); + } + return start; + } + + private boolean evaluateBooleanExpression(ExpressionType expressionType, ExpressionVariables expressionVariables, String opContext, Task taskFromModel, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + + PrismContext prismContext = expressionFactory.getPrismContext(); + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_BOOLEAN); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), opContext, taskFromModel, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, opContext, taskFromModel); + PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, params, taskFromModel, result); + + Collection> exprResult = exprResultTriple.getZeroSet(); + if (exprResult.size() == 0) { + return false; + } else if (exprResult.size() > 1) { + throw new IllegalStateException("Expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); + } + Boolean boolResult = exprResult.iterator().next().getValue(); + return boolResult != null ? boolResult : false; + } + +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java index b25b1638c6c..d53899398bf 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/BasePrimaryChangeAspect.java @@ -1,206 +1,205 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.wf.impl.processors.primary.aspect; - -import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; -import com.evolveum.midpoint.model.impl.expr.ExpressionEnvironment; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismReferenceValue; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.RelationRegistry; -import com.evolveum.midpoint.schema.SearchResultList; -import com.evolveum.midpoint.schema.expression.ExpressionProfile; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.wf.impl.processes.itemApproval.ApprovalSchemaHelper; -import com.evolveum.midpoint.wf.impl.processes.itemApproval.ReferenceResolver; -import com.evolveum.midpoint.wf.impl.processes.itemApproval.RelationResolver; -import com.evolveum.midpoint.wf.impl.processors.ConfigurationHelper; -import com.evolveum.midpoint.wf.impl.processors.ModelHelper; -import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; -import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -import javax.annotation.PostConstruct; -import javax.xml.namespace.QName; -import java.util.*; -import java.util.stream.Collectors; - -/** - * @author mederly - */ -public abstract class BasePrimaryChangeAspect implements PrimaryChangeAspect, BeanNameAware { - - private static final Trace LOGGER = TraceManager.getTrace(BasePrimaryChangeAspect.class); - - private String beanName; - - @Autowired - @Qualifier("cacheRepositoryService") - protected RepositoryService repositoryService; - - @Autowired protected PrimaryChangeProcessor changeProcessor; - @Autowired protected PrimaryChangeAspectHelper primaryChangeAspectHelper; - @Autowired protected ConfigurationHelper configurationHelper; - @Autowired protected PrismContext prismContext; - @Autowired protected RelationRegistry relationRegistry; - @Autowired protected ModelHelper modelHelper; - @Autowired private SystemObjectCache systemObjectCache; - @Autowired private MappingFactory mappingFactory; - @Autowired protected ApprovalSchemaHelper approvalSchemaHelper; - - @PostConstruct - public void init() { - changeProcessor.registerChangeAspect(this, isFirst()); - } - - protected boolean isFirst() { - return false; - } - - @Override - public String getBeanName() { - return beanName; - } - - public void setBeanName(String name) { - this.beanName = name; - } - - public PrimaryChangeProcessor getChangeProcessor() { - return changeProcessor; - } - - @Override - public boolean isEnabledByDefault() { - return false; // overridden in selected aspects - } - - @Override - public boolean isEnabled(PrimaryChangeProcessorConfigurationType processorConfigurationType) { - return primaryChangeAspectHelper.isEnabled(processorConfigurationType, this); - } - - public RelationResolver createRelationResolver(ObjectType object, OperationResult result) { - return createRelationResolver(object != null ? object.asPrismObject() : null, result); - } - - private List resolveReferenceFromFilter(Class clazz, SearchFilterType filter, String sourceDescription, - LensContext lensContext, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionEnvironment env = new ExpressionEnvironment<>(); - env.setLensContext(lensContext); - env.setCurrentResult(result); - env.setCurrentTask(task); - ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); - try { - - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(getFocusObjectable(lensContext), null, null, systemConfiguration.asObjectable(), prismContext); - - ObjectFilter origFilter = prismContext.getQueryConverter().parseFilter(filter, clazz); - ObjectFilter evaluatedFilter = ExpressionUtil - .evaluateFilterExpressions(origFilter, variables, MiscSchemaUtil.getExpressionProfile(), mappingFactory.getExpressionFactory(), prismContext, " evaluating approverRef filter expression ", task, result); - - if (evaluatedFilter == null) { - throw new SchemaException("Filter could not be evaluated in approverRef in "+sourceDescription+"; original filter = "+origFilter); - } - - SearchResultList> targets = repositoryService.searchObjects(clazz, prismContext.queryFactory().createQuery(evaluatedFilter), null, result); - - return targets.stream() - .map(object -> ObjectTypeUtil.createObjectRef(object, prismContext)) - .collect(Collectors.toList()); - - } finally { - ModelExpressionThreadLocalHolder.popExpressionEnvironment(); - } - } - - private FocusType getFocusObjectable(LensContext lensContext) { - if (lensContext.getFocusContext() == null) { - return null; // shouldn't occur, probably - } - PrismObject focus = lensContext.getFocusContext().getObjectAny(); - return focus != null ? (FocusType) focus.asObjectable() : null; - } - - public ReferenceResolver createReferenceResolver(ModelContext modelContext, Task taskFromModel, OperationResult result) { - return (ref, sourceDescription) -> { - if (ref == null) { - return Collections.emptyList(); - } else if (ref.getOid() != null) { - return Collections.singletonList(ref.clone()); - } else { - Class clazz; - if (ref.getType() != null) { - clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(ref.getType()); - if (clazz == null) { - throw new SchemaException("Cannot determine type from " + ref.getType() + " in approver reference in " + sourceDescription); - } - } else { - throw new SchemaException("Missing type in target reference in " + sourceDescription); - } - return resolveReferenceFromFilter(clazz, ref.getFilter(), sourceDescription, (LensContext) modelContext, taskFromModel, result); - } - }; - } - - public RelationResolver createRelationResolver(PrismObject object, OperationResult result) { - return relations -> { - if (object == null || object.getOid() == null || relations.isEmpty()) { - return Collections.emptyList(); - } - S_AtomicFilterExit q = prismContext.queryFor(FocusType.class).none(); - for (QName approverRelation : relations) { - PrismReferenceValue approverReference = prismContext.itemFactory().createReferenceValue(object.getOid()); - approverReference.setRelation(relationRegistry.normalizeRelation(approverRelation)); - q = q.or().item(FocusType.F_ROLE_MEMBERSHIP_REF).ref(approverReference); - } - ObjectQuery query = q.build(); - LOGGER.trace("Looking for approvers for {} using query:\n{}", object, DebugUtil.debugDumpLazily(query)); - List> objects; - try { - objects = repositoryService.searchObjects(FocusType.class, query, null, result); - } catch (SchemaException e) { - throw new SystemException("Couldn't retrieve approvers for " + object + ": " + e.getMessage(), e); - } - List> distinctObjects = ObjectTypeUtil.keepDistinctObjects(objects); - LOGGER.trace("Query evaluation resulted in {} approver(s): {}", distinctObjects.size(), DebugUtil.toStringLazily(distinctObjects)); - return distinctObjects.stream() - .map(object1 -> ObjectTypeUtil.createObjectRef(object1, prismContext)) - .collect(Collectors.toList()); - }; - } - - protected List findApproversByReference(PrismObject target, ApprovalPolicyActionType action, - OperationResult result) throws SchemaException { - return createRelationResolver(target, result) - .getApprovers(action.getApproverRelation()); - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.wf.impl.processors.primary.aspect; + +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.mapping.MappingFactory; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.RelationRegistry; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ApprovalSchemaHelper; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.ReferenceResolver; +import com.evolveum.midpoint.wf.impl.processes.itemApproval.RelationResolver; +import com.evolveum.midpoint.wf.impl.processors.ConfigurationHelper; +import com.evolveum.midpoint.wf.impl.processors.ModelHelper; +import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +import javax.annotation.PostConstruct; +import javax.xml.namespace.QName; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author mederly + */ +public abstract class BasePrimaryChangeAspect implements PrimaryChangeAspect, BeanNameAware { + + private static final Trace LOGGER = TraceManager.getTrace(BasePrimaryChangeAspect.class); + + private String beanName; + + @Autowired + @Qualifier("cacheRepositoryService") + protected RepositoryService repositoryService; + + @Autowired protected PrimaryChangeProcessor changeProcessor; + @Autowired protected PrimaryChangeAspectHelper primaryChangeAspectHelper; + @Autowired protected ConfigurationHelper configurationHelper; + @Autowired protected PrismContext prismContext; + @Autowired protected RelationRegistry relationRegistry; + @Autowired protected ModelHelper modelHelper; + @Autowired private SystemObjectCache systemObjectCache; + @Autowired private MappingFactory mappingFactory; + @Autowired protected ApprovalSchemaHelper approvalSchemaHelper; + + @PostConstruct + public void init() { + changeProcessor.registerChangeAspect(this, isFirst()); + } + + protected boolean isFirst() { + return false; + } + + @Override + public String getBeanName() { + return beanName; + } + + public void setBeanName(String name) { + this.beanName = name; + } + + public PrimaryChangeProcessor getChangeProcessor() { + return changeProcessor; + } + + @Override + public boolean isEnabledByDefault() { + return false; // overridden in selected aspects + } + + @Override + public boolean isEnabled(PrimaryChangeProcessorConfigurationType processorConfigurationType) { + return primaryChangeAspectHelper.isEnabled(processorConfigurationType, this); + } + + public RelationResolver createRelationResolver(ObjectType object, OperationResult result) { + return createRelationResolver(object != null ? object.asPrismObject() : null, result); + } + + private List resolveReferenceFromFilter(Class clazz, SearchFilterType filter, String sourceDescription, + LensContext lensContext, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + ExpressionEnvironment env = new ExpressionEnvironment<>(); + env.setLensContext(lensContext); + env.setCurrentResult(result); + env.setCurrentTask(task); + ModelExpressionThreadLocalHolder.pushExpressionEnvironment(env); + try { + + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(getFocusObjectable(lensContext), null, null, systemConfiguration.asObjectable(), prismContext); + + ObjectFilter origFilter = prismContext.getQueryConverter().parseFilter(filter, clazz); + ObjectFilter evaluatedFilter = ExpressionUtil + .evaluateFilterExpressions(origFilter, variables, MiscSchemaUtil.getExpressionProfile(), mappingFactory.getExpressionFactory(), prismContext, " evaluating approverRef filter expression ", task, result); + + if (evaluatedFilter == null) { + throw new SchemaException("Filter could not be evaluated in approverRef in "+sourceDescription+"; original filter = "+origFilter); + } + + SearchResultList> targets = repositoryService.searchObjects(clazz, prismContext.queryFactory().createQuery(evaluatedFilter), null, result); + + return targets.stream() + .map(object -> ObjectTypeUtil.createObjectRef(object, prismContext)) + .collect(Collectors.toList()); + + } finally { + ModelExpressionThreadLocalHolder.popExpressionEnvironment(); + } + } + + private FocusType getFocusObjectable(LensContext lensContext) { + if (lensContext.getFocusContext() == null) { + return null; // shouldn't occur, probably + } + PrismObject focus = lensContext.getFocusContext().getObjectAny(); + return focus != null ? (FocusType) focus.asObjectable() : null; + } + + public ReferenceResolver createReferenceResolver(ModelContext modelContext, Task taskFromModel, OperationResult result) { + return (ref, sourceDescription) -> { + if (ref == null) { + return Collections.emptyList(); + } else if (ref.getOid() != null) { + return Collections.singletonList(ref.clone()); + } else { + Class clazz; + if (ref.getType() != null) { + clazz = prismContext.getSchemaRegistry().determineCompileTimeClass(ref.getType()); + if (clazz == null) { + throw new SchemaException("Cannot determine type from " + ref.getType() + " in approver reference in " + sourceDescription); + } + } else { + throw new SchemaException("Missing type in target reference in " + sourceDescription); + } + return resolveReferenceFromFilter(clazz, ref.getFilter(), sourceDescription, (LensContext) modelContext, taskFromModel, result); + } + }; + } + + public RelationResolver createRelationResolver(PrismObject object, OperationResult result) { + return relations -> { + if (object == null || object.getOid() == null || relations.isEmpty()) { + return Collections.emptyList(); + } + S_AtomicFilterExit q = prismContext.queryFor(FocusType.class).none(); + for (QName approverRelation : relations) { + PrismReferenceValue approverReference = prismContext.itemFactory().createReferenceValue(object.getOid()); + approverReference.setRelation(relationRegistry.normalizeRelation(approverRelation)); + q = q.or().item(FocusType.F_ROLE_MEMBERSHIP_REF).ref(approverReference); + } + ObjectQuery query = q.build(); + LOGGER.trace("Looking for approvers for {} using query:\n{}", object, DebugUtil.debugDumpLazily(query)); + List> objects; + try { + objects = repositoryService.searchObjects(FocusType.class, query, null, result); + } catch (SchemaException e) { + throw new SystemException("Couldn't retrieve approvers for " + object + ": " + e.getMessage(), e); + } + List> distinctObjects = ObjectTypeUtil.keepDistinctObjects(objects); + LOGGER.trace("Query evaluation resulted in {} approver(s): {}", distinctObjects.size(), DebugUtil.toStringLazily(distinctObjects)); + return distinctObjects.stream() + .map(object1 -> ObjectTypeUtil.createObjectRef(object1, prismContext)) + .collect(Collectors.toList()); + }; + } + + protected List findApproversByReference(PrismObject target, ApprovalPolicyActionType action, + OperationResult result) throws SchemaException { + return createRelationResolver(target, result) + .getApprovers(action.getApproverRelation()); + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java index e15ca149021..d2460d4b743 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/aspect/PrimaryChangeAspectHelper.java @@ -1,149 +1,149 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.wf.impl.processors.primary.aspect; - -import com.evolveum.midpoint.model.api.context.ModelContext; -import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.prism.PrismContext; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -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.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -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.ExpressionType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PcpAspectConfigurationType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PrimaryChangeProcessorConfigurationType; -import org.apache.velocity.util.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.xml.namespace.QName; -import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; - -/** - * @author mederly - */ -@Component -public class PrimaryChangeAspectHelper { - - private static final Trace LOGGER = TraceManager.getTrace(PrimaryChangeAspectHelper.class); - - @Autowired private PrismContext prismContext; - @Autowired private ExpressionFactory expressionFactory; - - public boolean isEnabled(PrimaryChangeProcessorConfigurationType processorConfigurationType, PrimaryChangeAspect aspect) { - if (processorConfigurationType == null) { - return aspect.isEnabledByDefault(); - } - PcpAspectConfigurationType aspectConfigurationType = getPcpAspectConfigurationType(processorConfigurationType, aspect); // result may be null - return isEnabled(aspectConfigurationType, aspect.isEnabledByDefault()); - } - - public PcpAspectConfigurationType getPcpAspectConfigurationType(PrimaryChangeProcessorConfigurationType processorConfigurationType, PrimaryChangeAspect aspect) { - if (processorConfigurationType == null) { - return null; - } - String aspectName = aspect.getBeanName(); - String getterName = "get" + StringUtils.capitalizeFirstLetter(aspectName); - Object aspectConfigurationObject; - try { - Method getter = processorConfigurationType.getClass().getDeclaredMethod(getterName); - try { - aspectConfigurationObject = getter.invoke(processorConfigurationType); - } catch (IllegalAccessException|InvocationTargetException e) { - throw new SystemException("Couldn't obtain configuration for aspect " + aspectName + " from the workflow configuration.", e); - } - if (aspectConfigurationObject != null) { - return (PcpAspectConfigurationType) aspectConfigurationObject; - } - LOGGER.trace("Specific configuration for {} not found, trying generic configuration", aspectName); - } catch (NoSuchMethodException e) { - // nothing wrong with this, let's try generic configuration - LOGGER.trace("Configuration getter method for {} not found, trying generic configuration", aspectName); - } - -// for (GenericPcpAspectConfigurationType genericConfig : processorConfigurationType.getOtherAspect()) { -// if (aspectName.equals(genericConfig.getName())) { -// return genericConfig; -// } -// } - return null; - } - - private boolean isEnabled(PcpAspectConfigurationType configurationType, boolean enabledByDefault) { - if (configurationType == null) { - return enabledByDefault; - } else { - return !Boolean.FALSE.equals(configurationType.isEnabled()); - } - } - //endregion - - //region ========================================================================== Expression evaluation - - public boolean evaluateApplicabilityCondition(PcpAspectConfigurationType config, ModelContext modelContext, Serializable itemToApprove, - ExpressionVariables additionalVariables, PrimaryChangeAspect aspect, Task task, OperationResult result) { - - if (config == null || config.getApplicabilityCondition() == null) { - return true; - } - - ExpressionType expressionType = config.getApplicabilityCondition(); - - QName resultName = new QName(SchemaConstants.NS_C, "result"); - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_BOOLEAN); - - ExpressionVariables expressionVariables = new ExpressionVariables(); - expressionVariables.put(ExpressionConstants.VAR_MODEL_CONTEXT, modelContext, ModelContext.class); - expressionVariables.put(ExpressionConstants.VAR_ITEM_TO_APPROVE, itemToApprove, itemToApprove.getClass()); - if (additionalVariables != null) { - expressionVariables.addVariableDefinitions(additionalVariables); - } - - PrismValueDeltaSetTriple> exprResultTriple; - try { - Expression,PrismPropertyDefinition> expression = - expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), - "applicability condition expression", task, result); - ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, - "applicability condition expression", task); - - exprResultTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); - } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | RuntimeException | CommunicationException | ConfigurationException | SecurityViolationException e) { - // TODO report as a specific exception? - throw new SystemException("Couldn't evaluate applicability condition in aspect " - + aspect.getClass().getSimpleName() + ": " + e.getMessage(), e); - } - - Collection> exprResult = exprResultTriple.getZeroSet(); - if (exprResult.size() == 0) { - return false; - } else if (exprResult.size() > 1) { - throw new IllegalStateException("Applicability condition expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); - } - Boolean boolResult = exprResult.iterator().next().getValue(); - return boolResult != null ? boolResult : false; - } - - //endregion - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.wf.impl.processors.primary.aspect; + +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +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.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +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.ExpressionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PcpAspectConfigurationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PrimaryChangeProcessorConfigurationType; +import org.apache.velocity.util.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; + +/** + * @author mederly + */ +@Component +public class PrimaryChangeAspectHelper { + + private static final Trace LOGGER = TraceManager.getTrace(PrimaryChangeAspectHelper.class); + + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + + public boolean isEnabled(PrimaryChangeProcessorConfigurationType processorConfigurationType, PrimaryChangeAspect aspect) { + if (processorConfigurationType == null) { + return aspect.isEnabledByDefault(); + } + PcpAspectConfigurationType aspectConfigurationType = getPcpAspectConfigurationType(processorConfigurationType, aspect); // result may be null + return isEnabled(aspectConfigurationType, aspect.isEnabledByDefault()); + } + + public PcpAspectConfigurationType getPcpAspectConfigurationType(PrimaryChangeProcessorConfigurationType processorConfigurationType, PrimaryChangeAspect aspect) { + if (processorConfigurationType == null) { + return null; + } + String aspectName = aspect.getBeanName(); + String getterName = "get" + StringUtils.capitalizeFirstLetter(aspectName); + Object aspectConfigurationObject; + try { + Method getter = processorConfigurationType.getClass().getDeclaredMethod(getterName); + try { + aspectConfigurationObject = getter.invoke(processorConfigurationType); + } catch (IllegalAccessException|InvocationTargetException e) { + throw new SystemException("Couldn't obtain configuration for aspect " + aspectName + " from the workflow configuration.", e); + } + if (aspectConfigurationObject != null) { + return (PcpAspectConfigurationType) aspectConfigurationObject; + } + LOGGER.trace("Specific configuration for {} not found, trying generic configuration", aspectName); + } catch (NoSuchMethodException e) { + // nothing wrong with this, let's try generic configuration + LOGGER.trace("Configuration getter method for {} not found, trying generic configuration", aspectName); + } + +// for (GenericPcpAspectConfigurationType genericConfig : processorConfigurationType.getOtherAspect()) { +// if (aspectName.equals(genericConfig.getName())) { +// return genericConfig; +// } +// } + return null; + } + + private boolean isEnabled(PcpAspectConfigurationType configurationType, boolean enabledByDefault) { + if (configurationType == null) { + return enabledByDefault; + } else { + return !Boolean.FALSE.equals(configurationType.isEnabled()); + } + } + //endregion + + //region ========================================================================== Expression evaluation + + public boolean evaluateApplicabilityCondition(PcpAspectConfigurationType config, ModelContext modelContext, Serializable itemToApprove, + ExpressionVariables additionalVariables, PrimaryChangeAspect aspect, Task task, OperationResult result) { + + if (config == null || config.getApplicabilityCondition() == null) { + return true; + } + + ExpressionType expressionType = config.getApplicabilityCondition(); + + QName resultName = new QName(SchemaConstants.NS_C, "result"); + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition(resultName, DOMUtil.XSD_BOOLEAN); + + ExpressionVariables expressionVariables = new ExpressionVariables(); + expressionVariables.put(ExpressionConstants.VAR_MODEL_CONTEXT, modelContext, ModelContext.class); + expressionVariables.put(ExpressionConstants.VAR_ITEM_TO_APPROVE, itemToApprove, itemToApprove.getClass()); + if (additionalVariables != null) { + expressionVariables.addVariableDefinitions(additionalVariables); + } + + PrismValueDeltaSetTriple> exprResultTriple; + try { + Expression,PrismPropertyDefinition> expression = + expressionFactory.makeExpression(expressionType, resultDef, MiscSchemaUtil.getExpressionProfile(), + "applicability condition expression", task, result); + ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, expressionVariables, + "applicability condition expression", task); + + exprResultTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, params, task, result); + } catch (SchemaException | ExpressionEvaluationException | ObjectNotFoundException | RuntimeException | CommunicationException | ConfigurationException | SecurityViolationException e) { + // TODO report as a specific exception? + throw new SystemException("Couldn't evaluate applicability condition in aspect " + + aspect.getClass().getSimpleName() + ": " + e.getMessage(), e); + } + + Collection> exprResult = exprResultTriple.getZeroSet(); + if (exprResult.size() == 0) { + return false; + } else if (exprResult.size() > 1) { + throw new IllegalStateException("Applicability condition expression should return exactly one boolean value; it returned " + exprResult.size() + " ones"); + } + Boolean boolResult = exprResult.iterator().next().getValue(); + return boolResult != null ? boolResult : false; + } + + //endregion + +}