From a4b0baff877cbec06416a34a4bcba3369d1c4d88 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Mon, 16 Mar 2020 22:41:14 +0100 Subject: [PATCH 1/8] fixing MID-6021 - summary panel organizations. --- .../page/admin/PageAdminObjectDetails.java | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java index e4c458543dd..7f48e36fc3f 100755 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java @@ -16,9 +16,12 @@ import com.evolveum.midpoint.gui.api.util.WebPrismUtil; import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.web.component.AjaxButton; import com.evolveum.midpoint.web.component.prism.ValueStatus; @@ -100,6 +103,7 @@ public abstract class PageAdminObjectDetails extends PageA protected static final String OPERATION_LOAD_ARCHETYPE_REF = DOT_CLASS + "loadArchetypeRef"; protected static final String OPERATION_EXECUTE_ARCHETYPE_CHANGES = DOT_CLASS + "executeArchetypeChanges"; protected static final String OPERATION_LOAD_FILTERED_ARCHETYPES = DOT_CLASS + "loadFilteredArchetypes"; + protected static final String OPERATION_LOAD_PARENT_ORG = DOT_CLASS + "loadParentOrgs"; protected static final String ID_SUMMARY_PANEL = "summaryPanel"; protected static final String ID_MAIN_PANEL = "mainPanel"; @@ -336,7 +340,9 @@ protected O load() { return null; } - return wrapper.getObject().asObjectable(); + PrismObject object = wrapper.getObject(); + loadParentOrgs(object); + return object.asObjectable(); } }; } @@ -680,43 +686,45 @@ protected PrismObjectWrapper loadObjectWrapper(PrismObject objectToEdit, b protected Collection> buildGetOptions() { return getOperationOptionsBuilder() - .item(UserType.F_JPEG_PHOTO).retrieve() + .item(FocusType.F_JPEG_PHOTO).retrieve() .build(); } -// private void loadParentOrgs(PrismObjectWrapper wrapper, Task task, OperationResult result) { -// OperationResult subResult = result.createMinorSubresult(OPERATION_LOAD_PARENT_ORGS); -// PrismObject focus = wrapper.getObject(); -// // Load parent organizations (full objects). There are used in the -// // summary panel and also in the main form. -// // Do it here explicitly instead of using resolve option to have ability -// // to better handle (ignore) errors. -// for (ObjectReferenceType parentOrgRef : focus.asObjectable().getParentOrgRef()) { -// -// PrismObject parentOrg = null; -// try { -// -// parentOrg = getModelService().getObject(OrgType.class, parentOrgRef.getOid(), null, task, -// subResult); -// LOGGER.trace("Loaded parent org with result {}", -// new Object[] { subResult.getLastSubresult() }); -// } catch (AuthorizationException e) { -// // This can happen if the user has permission to read parentOrgRef but it does not have -// // the permission to read target org -// // It is OK to just ignore it. -// subResult.muteLastSubresultError(); -// LOGGER.debug("User {} does not have permission to read parent org unit {} (ignoring error)", task.getOwner().getName(), parentOrgRef.getOid()); -// } catch (Exception ex) { -// subResult.recordWarning(createStringResource("PageAdminObjectDetails.message.loadParentOrgs.warning", parentOrgRef.getOid()).getString(), ex); -// LOGGER.warn("Cannot load parent org {}: {}", parentOrgRef.getOid(), ex.getMessage(), ex); -// } -// -// if (parentOrg != null) { -// wrapper.getParentOrgs().add(parentOrg); -// } -// } -// subResult.computeStatus(); -// } + private void loadParentOrgs(PrismObject object) { + Task task = createSimpleTask(OPERATION_LOAD_PARENT_ORG); + OperationResult subResult = task.getResult(); + // Load parent organizations (full objects). There are used in the + // summary panel and also in the main form. + // Do it here explicitly instead of using resolve option to have ability + // to better handle (ignore) errors. + for (ObjectReferenceType parentOrgRef : object.asObjectable().getParentOrgRef()) { + + PrismObject parentOrg = null; + try { + + parentOrg = getModelService().getObject(OrgType.class, parentOrgRef.getOid(), null, task, + subResult); + LOGGER.trace("Loaded parent org with result {}", + new Object[] { subResult.getLastSubresult() }); + } catch (AuthorizationException e) { + // This can happen if the user has permission to read parentOrgRef but it does not have + // the permission to read target org + // It is OK to just ignore it. + subResult.muteLastSubresultError(); + LOGGER.debug("User {} does not have permission to read parent org unit {} (ignoring error)", task.getOwner().getName(), parentOrgRef.getOid()); + } catch (Exception ex) { + subResult.recordWarning(createStringResource("PageAdminObjectDetails.message.loadParentOrgs.warning", parentOrgRef.getOid()).getString(), ex); + LOGGER.warn("Cannot load parent org {}: {}", parentOrgRef.getOid(), ex.getMessage(), ex); + } + + if (parentOrg != null) { + ObjectReferenceType ref = ObjectTypeUtil.createObjectRef(parentOrg, getPrismContext()); + ref.asReferenceValue().setObject(parentOrg); + object.asObjectable().getParentOrgRef().add(ref); + } + } + subResult.computeStatus(); + } protected abstract Class getRestartResponsePage(); From 739c93db806cdd55af6c177e613ffba9212e5d8e Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Tue, 17 Mar 2020 06:32:01 +0100 Subject: [PATCH 2/8] 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 + +} From dce03b8c19f8d334af83d3ab1f0fc14cbdef1fc8 Mon Sep 17 00:00:00 2001 From: lskublik Date: Tue, 17 Mar 2020 10:40:32 +0100 Subject: [PATCH 3/8] fix for showing of empty readOnly resource attributes (MID-6006) --- .../midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java index 9278e2e1930..2c8f81adf81 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java @@ -66,6 +66,7 @@ public IW createWrapper(PrismContainerValueWrapper parent, ItemDefinition if (childItem == null) { childItem = (I) parent.getNewValue().findOrCreateItem(name); + childItem.setDefinition(def); } return createWrapper(parent, childItem, status, context); From 92f4e7e3af2b5dd3a61e375e00e1034ddd6b5fa9 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Tue, 17 Mar 2020 11:42:01 +0100 Subject: [PATCH 4/8] .editorconfig: end_of_line = lf (not crlf) CRLF was result of "system dependent" value in IDE, but it seems to enforce "system dependent" value of the commit author, which sucks. If we have to choose, we want LF, of course. --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index fe4cd7d9a30..da5fc0cfcf6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,6 @@ [*] charset = utf-8 -end_of_line = crlf +end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = false From 8fb27da0b4371e73ee1729d9751bf71f1751b11e Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Tue, 17 Mar 2020 11:51:15 +0100 Subject: [PATCH 5/8] MidpointTestMixin: displayTestFooter now shows thrown exception This may duplicate report, but sometimes the exception is lost in logs. This should ensure that it appears in both logs and stdout. --- .../midpoint/tools/testng/MidpointTestMixin.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/test-ng/src/main/java/com/evolveum/midpoint/tools/testng/MidpointTestMixin.java b/tools/test-ng/src/main/java/com/evolveum/midpoint/tools/testng/MidpointTestMixin.java index 1730cac578a..23870f33f97 100644 --- a/tools/test-ng/src/main/java/com/evolveum/midpoint/tools/testng/MidpointTestMixin.java +++ b/tools/test-ng/src/main/java/com/evolveum/midpoint/tools/testng/MidpointTestMixin.java @@ -33,7 +33,7 @@ public interface MidpointTestMixin { String TEST_LOG_SECTION_SUFFIX = " --------------------------------------"; String DISPLAY_OUT_PREFIX = "\n*** "; - String DISPLAY_LOG_FORMAT1 = "*** {}"; + String DISPLAY_LOG_FORMAT1 = "*** {}:"; String DISPLAY_LOG_FORMAT2 = "*** {}:\n{}"; /** @@ -129,6 +129,10 @@ default void displayTestTitle(String testTitle) { * Not intended for tests classes, used by lifecycle methods in our test superclasses. */ default void displayTestFooter(String testTitle, ITestResult testResult) { + if (testResult.getThrowable() != null) { + displayException(testTitle + " thrown unexpected exception", testResult.getThrowable()); + } + long testMsDuration = testResult.getEndMillis() - testResult.getStartMillis(); System.out.println(TEST_OUT_FOOTER_PREFIX + testTitle + " FINISHED in " + testMsDuration + " ms" + TEST_OUT_FOOTER_SUFFIX); logger().info(TEST_LOG_PREFIX + testTitle + " FINISHED in " + testMsDuration + " ms" + TEST_LOG_SUFFIX); @@ -140,7 +144,7 @@ default void display(String text) { } /** - * Displays + * Displays value prefixed with provided title. */ default void displayValue(String title, Object value) { System.out.println(DISPLAY_OUT_PREFIX + title + "\n" + value); @@ -152,8 +156,8 @@ default void displayValue(String title, Object value) { * Use for "bad" exceptions, for expected exceptions use {@link #displayExpectedException}. */ default void displayException(String title, Throwable e) { - System.out.println(DISPLAY_OUT_PREFIX + title); - e.printStackTrace(); + System.out.println(DISPLAY_OUT_PREFIX + title + ":"); + e.printStackTrace(System.out); logger().debug(DISPLAY_LOG_FORMAT1, title, e); } From 9a929697d743b59d69fc833b4e164b66d0e19354 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Tue, 17 Mar 2020 13:08:33 +0100 Subject: [PATCH 6/8] fixes after merge --- .../model/impl/lens/projector/mappings/MappingEvaluator.java | 4 ++-- .../midpoint/model/intest/orgstruct/TestOrgStruct.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) 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 b3aec6e62d3..0aaa0afeffc 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 @@ -15,10 +15,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; import com.evolveum.midpoint.model.common.mapping.MappingFactory; 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.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; 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 89fbc097c62..88bd5f0493a 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 @@ -15,6 +15,9 @@ import java.util.List; import java.util.Set; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; + import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; @@ -24,8 +27,6 @@ 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; From 323d52c1e64dc3906a90515cfd35698df7f83035 Mon Sep 17 00:00:00 2001 From: lskublik Date: Tue, 17 Mar 2020 13:14:51 +0100 Subject: [PATCH 7/8] fix for instantiate of ResourceAttribute in LayerRefinedAttributeDefinitionImpl (MID-6006) --- .../midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java | 1 - .../refinery/LayerRefinedAttributeDefinitionImpl.java | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java index 2c8f81adf81..9278e2e1930 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/factory/ItemWrapperFactoryImpl.java @@ -66,7 +66,6 @@ public IW createWrapper(PrismContainerValueWrapper parent, ItemDefinition if (childItem == null) { childItem = (I) parent.getNewValue().findOrCreateItem(name); - childItem.setDefinition(def); } return createWrapper(parent, childItem, status, context); diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinitionImpl.java b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinitionImpl.java index 2222ba1a227..a1a68748ca9 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinitionImpl.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/refinery/LayerRefinedAttributeDefinitionImpl.java @@ -433,13 +433,17 @@ public String getDisplayName() { @NotNull @Override public ResourceAttribute instantiate() { - return refinedAttributeDefinition.instantiate(); + @NotNull ResourceAttribute resourceAttribute = refinedAttributeDefinition.instantiate(); + resourceAttribute.setDefinition(this); + return resourceAttribute; } @NotNull @Override public ResourceAttribute instantiate(QName name) { - return refinedAttributeDefinition.instantiate(name); + @NotNull ResourceAttribute resourceAttribute = refinedAttributeDefinition.instantiate(name); + resourceAttribute.setDefinition(this); + return resourceAttribute; } @Override From 000ba2eefca5eb5244a54f0ae1ae02ab93abeb8e Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Tue, 17 Mar 2020 19:07:24 +0100 Subject: [PATCH 8/8] fixing report tests --- model/report-impl/pom.xml | 624 +++++++++++++++++++------------------- 1 file changed, 315 insertions(+), 309 deletions(-) diff --git a/model/report-impl/pom.xml b/model/report-impl/pom.xml index 21631fb004c..63a1d1ac7ef 100644 --- a/model/report-impl/pom.xml +++ b/model/report-impl/pom.xml @@ -1,309 +1,315 @@ - - - - - 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 - - - - + + + + + 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.model + model-impl + 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 + + + +