diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java index efaea3a857c..b841b11f883 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/InlineMenuButtonColumn.java @@ -97,7 +97,10 @@ private Component getPanel(String componentId, IModel rowModel, List buttonMenuItems = new ArrayList<>(); menuItems.forEach(menuItem -> { if (menuItem instanceof ButtonInlineMenuItem){ - if (isHeaderPanel && !menuItem.isHeaderMenuItem() || !menuItem.getVisible().getObject()){ + if (isHeaderPanel && !menuItem.isHeaderMenuItem() || !menuItem.getVisible().getObject()) { + return; + } + if (menuItem.getVisibilityChecker() != null && !menuItem.getVisibilityChecker().isVisible(rowModel, isHeaderPanel)) { return; } buttonMenuItems.add((ButtonInlineMenuItem) menuItem); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java index 1656d634314..38e2768e512 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageCreatedReports.java @@ -20,7 +20,9 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.page.admin.PageAdminObjectList; @@ -353,6 +355,14 @@ public String getButtonIconCssClass() { } }); + boolean canReadTraces; + try { + canReadTraces = isAuthorized(ModelAuthorizationAction.READ_TRACE.getUrl()); + } catch (Throwable t) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't authorize reading traces", t); + canReadTraces = false; + } + ButtonInlineMenuItem item = new ButtonInlineMenuItem(createStringResource("DownloadButtonPanel.download")) { private static final long serialVersionUID = 1L; @@ -381,11 +391,20 @@ public boolean isHeaderMenuItem(){ return false; } }; + if (!canReadTraces) { + item.setVisibilityChecker((rowModel, isHeader) -> !isTrace(rowModel)); + } menuItems.add(item); return menuItems; } + private boolean isTrace(IModel rowModel) { + //noinspection unchecked + SelectableBean row = (SelectableBean) rowModel.getObject(); + return ObjectTypeUtil.hasArchetype(row.getValue(), SystemObjectsType.ARCHETYPE_TRACE.value()); + } + private IModel createDeleteConfirmString() { return new IModel() { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java index 63bac5bc717..fe16fcd16a2 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java @@ -4,24 +4,6 @@ import java.util.Collection; import java.util.Collections; -import com.evolveum.midpoint.gui.impl.component.AjaxCompositedIconButton; -import com.evolveum.midpoint.gui.impl.component.data.column.CompositedIconPanel; -import com.evolveum.midpoint.gui.impl.component.icon.CompositedIcon; -import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; -import com.evolveum.midpoint.gui.impl.component.icon.IconCssStyle; -import com.evolveum.midpoint.gui.impl.prism.PrismPropertyValueWrapper; -import com.evolveum.midpoint.gui.impl.prism.PrismReferenceValueWrapperImpl; -import com.evolveum.midpoint.gui.impl.prism.PrismReferenceWrapper; -import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; -import com.evolveum.midpoint.model.api.util.ModelContextUtil; -import com.evolveum.midpoint.prism.Referencable; -import com.evolveum.midpoint.schema.ObjectDeltaOperation; - -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.web.security.util.SecurityUtils; - -import org.apache.commons.collections4.CollectionUtils; import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.AttributeAppender; @@ -35,15 +17,27 @@ import com.evolveum.midpoint.gui.api.prism.PrismObjectWrapper; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.gui.impl.component.AjaxCompositedIconButton; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIcon; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; +import com.evolveum.midpoint.gui.impl.component.icon.IconCssStyle; +import com.evolveum.midpoint.gui.impl.prism.PrismPropertyValueWrapper; import com.evolveum.midpoint.gui.impl.prism.PrismPropertyWrapper; +import com.evolveum.midpoint.gui.impl.prism.PrismReferenceValueWrapperImpl; +import com.evolveum.midpoint.gui.impl.prism.PrismReferenceWrapper; +import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.Referencable; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.report.api.ReportConstants; import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.security.api.AuthorizationConstants; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.MiscUtil; @@ -64,6 +58,7 @@ import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; import com.evolveum.midpoint.web.page.admin.PageAdminObjectDetails; import com.evolveum.midpoint.web.page.admin.reports.PageCreatedReports; +import com.evolveum.midpoint.web.security.util.SecurityUtils; import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.web.util.TaskOperationUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -283,7 +278,7 @@ public void onClick(AjaxRequestTarget target) { ajaxDownloadBehavior.initiate(target); } }; - download.add(new VisibleBehaviour(() -> isDownloadReportVisible())); + download.add(new VisibleBehaviour(this::isDownloadReportVisible)); download.add(AttributeAppender.append("class", "btn-primary")); repeatingView.add(download); } @@ -294,20 +289,15 @@ private boolean isDownloadReportVisible() { } private ReportOutputType getReportOutput() { - PrismProperty reportOutput = getReportOutputProperty(); + String reportOutput = getReportOutputProperty(); if (reportOutput == null) { return null; } - String reportOutputOid = reportOutput.getRealValue(); - if (reportOutputOid == null) { - return null; - } - Task opTask = createSimpleTask(OPERATION_LOAD_REPORT_OUTPUT); OperationResult result = opTask.getResult(); - PrismObject report = WebModelServiceUtils.loadObject(ReportOutputType.class, reportOutputOid, this, opTask, result); + PrismObject report = WebModelServiceUtils.loadObject(ReportOutputType.class, reportOutput, this, opTask, result); if (report == null) { return null; } @@ -318,9 +308,14 @@ private ReportOutputType getReportOutput() { } - private PrismProperty getReportOutputProperty() { + private String getReportOutputProperty() { PrismObject task = getTask().asPrismObject(); - return task.findProperty(ItemPath.create(TaskType.F_EXTENSION, ReportConstants.REPORT_OUTPUT_OID_PROPERTY_NAME)); + PrismProperty reportOutput = task.findProperty(ItemPath.create(TaskType.F_EXTENSION, ReportConstants.REPORT_OUTPUT_OID_PROPERTY_NAME)); + if (reportOutput == null) { + return null; + } + + return reportOutput.getRealValue(); } private void createRefreshNowIconButton(RepeatingView repeatingView) { @@ -423,11 +418,10 @@ private void saveTaskChanges(AjaxRequestTarget target, ObjectDelta tas private CompositedIcon getTaskCleanupCompositedIcon(String basicIconClass){ CompositedIconBuilder iconBuilder = new CompositedIconBuilder(); - CompositedIcon icon = iconBuilder + return iconBuilder .setBasicIcon(basicIconClass, IconCssStyle.IN_ROW_STYLE) .appendLayerIcon(WebComponentUtil.createIconType(GuiStyleConstants.CLASS_ICON_TRASH), IconCssStyle.BOTTOM_RIGHT_STYLE) .build(); - return icon; } private void afterOperation(AjaxRequestTarget target, OperationResult result) { @@ -436,11 +430,27 @@ private void afterOperation(AjaxRequestTarget target, OperationResult result) { refresh(target); } + @Override + public void savePerformed(AjaxRequestTarget target) { + savePerformed(target, false); + } + + private boolean saveAndRun = false; + public void saveAndRunPerformed(AjaxRequestTarget target) { + saveAndRun = true; + savePerformed(target, true); + } + + private void savePerformed(AjaxRequestTarget target, boolean run) { PrismObjectWrapper taskWrapper = getObjectWrapper(); try { PrismPropertyWrapper executionStatus = taskWrapper.findProperty(ItemPath.create(TaskType.F_EXECUTION_STATUS)); - executionStatus.getValue().setRealValue(TaskExecutionStatusType.RUNNABLE); + if (run) { + executionStatus.getValue().setRealValue(TaskExecutionStatusType.RUNNABLE); + } else { + executionStatus.getValue().setRealValue(TaskExecutionStatusType.SUSPENDED); + } setupOwner(taskWrapper); setupRecurrence(taskWrapper); @@ -452,7 +462,11 @@ public void saveAndRunPerformed(AjaxRequestTarget target) { } if (!checkScheduleFilledForReccurentTask(taskWrapper)) { - getSession().error("Cannot run recurring task without setting scheduling for it."); + if (run) { + getSession().error("Cannot run recurring task without setting scheduling for it."); + } else { + getSession().warn("Cannot run recurring task without setting scheduling for it."); + } target.add(getFeedbackPanel()); return; } @@ -507,10 +521,11 @@ public void finishProcessing(AjaxRequestTarget target, Collection { private static final transient Trace LOGGER = TraceManager.getTrace(TaskMainPanel.class); private static final String ID_SAVE_AND_RUN = "saveAndRun"; - private static final String ID_FORM = "taskForm"; public TaskMainPanel(String id, LoadableModel> objectModel, PageAdminObjectDetails parentPage) { super(id, objectModel, parentPage); @@ -77,14 +70,29 @@ protected List createTabs(PageAdminObjectDetails parentPage) { PageTask parentTaskPage = (PageTask) parentPage; + createBasicPanel(tabs, parentTaskPage); + createScheduleTab(tabs, parentTaskPage); + createWorkManagementTab(tabs, parentTaskPage); +// createCleanupPoliciesTab(tabs, parentTaskPage); + createSubtasksTab(tabs, parentTaskPage); + createOperationStatisticsPanel(tabs, parentTaskPage); + createEnvironmentalPerformanceTab(tabs, parentTaskPage); + createOperationTab(tabs, parentTaskPage); + createInteranalPerformanceTab(tabs, parentTaskPage); + createResultTab(tabs, parentTaskPage); + createErrorsTab(tabs, parentTaskPage); + return tabs; + } + + private void createBasicPanel(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior basicTabVisibility = new ObjectTabVisibleBehavior - (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_BASIC_URL, parentTaskPage){ + (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_BASIC_URL, parentPage){ private static final long serialVersionUID = 1L; @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isBasicVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isBasicVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.basic.title"), basicTabVisibility) { @@ -98,12 +106,11 @@ protected void updateHandlerPerformed(AjaxRequestTarget target) { parentPage.refresh(target); } }; -// ItemVisibilityHandler visibilityHandler = wrapper -> getBasicTabVisibility(wrapper.getPath()); -// ItemEditabilityHandler editabilityHandler = wrapper -> getBasicTabEditability(wrapper.getPath()); -// return createContainerPanel(panelId, TaskType.COMPLEX_TYPE, getObjectModel(), visibilityHandler, editabilityHandler); } }); + } + private void createScheduleTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior scheduleTabVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_SCHEDULE_URL, parentPage){ @@ -111,7 +118,7 @@ protected void updateHandlerPerformed(AjaxRequestTarget target) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isSchedulingVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isSchedulingVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.schedule.title"), scheduleTabVisibility) { @@ -120,10 +127,12 @@ public boolean isVisible(){ @Override public WebMarkupContainer createPanel(String panelId) { ItemVisibilityHandler visibilityHandler = wrapper -> ItemVisibility.AUTO; - return createContainerPanel(panelId, TaskType.COMPLEX_TYPE, PrismContainerWrapperModel.fromContainerWrapper(getObjectModel(), TaskType.F_SCHEDULE), visibilityHandler, getTaskEditabilityHandler()); + return createContainerPanel(panelId, ScheduleType.COMPLEX_TYPE, PrismContainerWrapperModel.fromContainerWrapper(getObjectModel(), TaskType.F_SCHEDULE), visibilityHandler, getTaskEditabilityHandler()); } }); + } + private void createWorkManagementTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior workManagementTabVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_WORK_MANAGEMENT_URL, parentPage){ @@ -131,7 +140,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isWorkManagementVisible(getTask()); + return super.isVisible() && parentPage.getTaskTabVisibilty().isWorkManagementVisible(getTask()); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.workManagement.title"), workManagementTabVisibility) { @@ -144,15 +153,17 @@ public WebMarkupContainer createPanel(String panelId) { PrismContainerWrapperModel.fromContainerWrapper(getObjectModel(), TaskType.F_WORK_MANAGEMENT), visibilityHandler, getTaskEditabilityHandler()); } }); + } +// private void createCleanupPoliciesTab(List tabs, PageTask parentPage) { // ObjectTabVisibleBehavior cleanupPoliciesTabVisibility = new ObjectTabVisibleBehavior -// (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_CLEANUP_POLICIES_URL, parentPage){ +// (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_CLEANUP_POLICIES_URL, parentPage) { // // private static final long serialVersionUID = 1L; // // @Override -// public boolean isVisible(){ -// return super.isVisible() && taskTabsVisibility.isCleanupPolicyVisible(); +// public boolean isVisible() { +// return super.isVisible() && parentPage.getTaskTabVisibilty().isCleanupPolicyVisible(); // } // }; // tabs.add(new PanelTab(parentPage.createStringResource("pageTask.cleanupPolicies.title"), cleanupPoliciesTabVisibility) { @@ -166,7 +177,10 @@ public WebMarkupContainer createPanel(String panelId) { // visibilityHandler, getTaskEditabilityHandler()); // } // }); +// +// } + private void createSubtasksTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior subtasksTabVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_SUBTASKS_URL, parentPage){ @@ -174,7 +188,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isSubtasksAndThreadsVisible(getTask()); + return super.isVisible() && parentPage.getTaskTabVisibilty().isSubtasksAndThreadsVisible(getTask()); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.subtasks.title"), subtasksTabVisibility) { @@ -186,7 +200,9 @@ public WebMarkupContainer createPanel(String panelId) { } }); + } + private void createOperationStatisticsPanel(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior operationStatsAndInternalPerfTabsVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_OPERATION_STATISTICS_URL, parentPage){ @@ -194,7 +210,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isInternalPerformanceVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isInternalPerformanceVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.operationStats.title"), operationStatsAndInternalPerfTabsVisibility) { @@ -206,7 +222,9 @@ public WebMarkupContainer createPanel(String panelId) { } }); + } + private void createEnvironmentalPerformanceTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior envPerfTabVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_ENVIRONMENTAL_PERFORMANCE_URL, parentPage){ @@ -214,7 +232,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isEnvironmentalPerformanceVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isEnvironmentalPerformanceVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.environmentalPerformance.title"), envPerfTabVisibility) { @@ -226,7 +244,9 @@ public WebMarkupContainer createPanel(String panelId) { } }); + } + private void createOperationTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior operationTabVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_OPERATION_URL, parentPage){ @@ -234,7 +254,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isOperationVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isOperationVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTaskEdit.operation"), operationTabVisibility) { @@ -245,7 +265,9 @@ public WebMarkupContainer createPanel(String panelId) { return new TaskOperationTabPanel(panelId, PrismContainerWrapperModel.fromContainerWrapper(getObjectModel(), TaskType.F_MODEL_OPERATION_CONTEXT)); } }); + } + private void createInteranalPerformanceTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior internalPerfTabsVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_INTERNAL_PERFORMANCE_URL, parentPage){ @@ -253,7 +275,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isInternalPerformanceVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isInternalPerformanceVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.internalPerformance.title"), internalPerfTabsVisibility) { @@ -264,7 +286,9 @@ public WebMarkupContainer createPanel(String panelId) { return new TaskInternalPerformanceTabPanel(panelId, PrismContainerWrapperModel.fromContainerWrapper(getObjectModel(), TaskType.F_OPERATION_STATS)); } }); + } + private void createResultTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior resultTabVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_RESULT_URL, parentPage){ @@ -272,7 +296,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isResultVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isResultVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.result.title"), resultTabVisibility) { @@ -283,8 +307,9 @@ public WebMarkupContainer createPanel(String panelId) { return new TaskResultTabPanel(panelId, getObjectModel()); } }); + } - + private void createErrorsTab(List tabs, PageTask parentPage) { ObjectTabVisibleBehavior errorsTabVisibility = new ObjectTabVisibleBehavior (Model.of(getObjectWrapper().getObject()), ComponentConstants.UI_TASK_TAB_ERRORS_URL, parentPage){ @@ -292,7 +317,7 @@ public WebMarkupContainer createPanel(String panelId) { @Override public boolean isVisible(){ - return super.isVisible() && parentTaskPage.getTaskTabVisibilty().isErrorsVisible(); + return super.isVisible() && parentPage.getTaskTabVisibilty().isErrorsVisible(); } }; tabs.add(new PanelTab(parentPage.createStringResource("pageTask.errors.title"), errorsTabVisibility) { @@ -303,8 +328,6 @@ public WebMarkupContainer createPanel(String panelId) { return new TaskErrorsTabPanel(panelId, getObjectModel()); } }); - - return tabs; } private ItemVisibility getWorkManagementVisibility(ItemPath path) { @@ -375,8 +398,7 @@ private Panel createContainerPanel(String id, QName ty .visibilityHandler(visibilityHandler) .editabilityHandler(editabilityHandler) .showOnTopLevel(true); - Panel panel = getDetailsPage().initItemPanel(id, typeName, model, builder.build()); - return panel; + return getDetailsPage().initItemPanel(id, typeName, model, builder.build()); } catch (SchemaException e) { LOGGER.error("Cannot create panel for {}, {}", typeName, e.getMessage(), e); getSession().error("Cannot create panel for " + typeName); // TODO opertion result? localization? @@ -386,8 +408,7 @@ private Panel createContainerPanel(String id, QName ty } private ItemEditabilityHandler getTaskEditabilityHandler(){ - ItemEditabilityHandler editableHandler = wrapper -> !WebComponentUtil.isRunningTask(getTask()); - return editableHandler; + return wrapper -> !WebComponentUtil.isRunningTask(getTask()); } private TaskType getTask() { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/ExecuteChangeOptionsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/ExecuteChangeOptionsPanel.java index 3269fdb28d3..c45c91e69a5 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/ExecuteChangeOptionsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/ExecuteChangeOptionsPanel.java @@ -9,9 +9,12 @@ import com.evolveum.midpoint.gui.api.component.BasePanel; import com.evolveum.midpoint.gui.api.component.form.CheckBoxPanel; -import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; +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.TracingProfileType; import org.apache.commons.lang3.StringUtils; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -29,6 +32,8 @@ public class ExecuteChangeOptionsPanel extends BasePanel { private static final long serialVersionUID = 1L; + private static final Trace LOGGER = TraceManager.getTrace(ExecuteChangeOptionsPanel.class); + private static final String ID_FORCE = "force"; private static final String ID_FORCE_CONTAINER = "forceContainer"; private static final String ID_RECONCILE = "reconcile"; @@ -123,8 +128,16 @@ private void initLayout() { showKeepDisplayingResults, true); + boolean canRecordTrace; + try { + canRecordTrace = getPageBase().isAuthorized(ModelAuthorizationAction.RECORD_TRACE.getUrl()); + } catch (Throwable t) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't check trace recording authorization", t); + canRecordTrace = false; + } + WebMarkupContainer tracingContainer = new WebMarkupContainer(ID_TRACING_CONTAINER); - tracingContainer.setVisible(WebModelServiceUtils.isEnableExperimentalFeature(getPageBase())); + tracingContainer.setVisible(canRecordTrace && WebModelServiceUtils.isEnableExperimentalFeature(getPageBase())); add(tracingContainer); DropDownChoice tracing = new DropDownChoice<>(ID_TRACING, PropertyModel.of(getModel(), ExecuteChangeOptionsDto.F_TRACING), diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java index 4bfbf6528b1..300a651cf86 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.prism.equivalence.ParameterizedEquivalenceStrategy; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.SchemaException; import org.jetbrains.annotations.NotNull; @@ -299,6 +300,12 @@ static boolean isEmpty(ObjectDelta delta) { boolean isRedundant(PrismObject object, boolean assumeMissingItems) throws SchemaException; + @Experimental + void removeOperationalItems(); + + @Experimental + void removeEstimatedOldValues(); + class FactorOutResultMulti { public final ObjectDelta remainder; public final List> offsprings = new ArrayList<>(); diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java index b5b49c82213..2dfe8b6e163 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java @@ -52,11 +52,11 @@ public static boolean containsSubpath(Collection paths, Item /** * Returns true if the collection contains a superpath of or equivalent path to the given path. * I.e. having collection = { A/B, A/C } - * then the method for this collection and 'path' returns: - * - path = A/B -> true - * - path = A -> true - * - path = A/B/C -> false - * - path = X -> false + * then the method for this collection and 'pathToBeFound' returns: + * - pathToBeFound = A/B -> true + * - pathToBeFound = A -> true + * - pathToBeFound = A/B/C -> false + * - pathToBeFound = X -> false */ public static boolean containsSuperpathOrEquivalent(Collection paths, ItemPath pathToBeFound) { for (ItemPath path : paths) { diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java index 4a809988491..4c1fd05f56b 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java @@ -1672,7 +1672,9 @@ public void keepPaths(List keep) throws SchemaException { for (QName itemName : itemNames) { Item item = findItemByQName(itemName); ItemPath itemPath = item.getPath().removeIds(); - if (!ItemPathCollectionsUtil.containsSuperpathOrEquivalent(keep, itemPath)) { + if (!ItemPathCollectionsUtil.containsSuperpathOrEquivalent(keep, itemPath) + && !ItemPathCollectionsUtil.containsSubpathOrEquivalent(keep, itemPath)) { + System.out.println("Removing " + itemPath + " because not in " + keep); removeItem(ItemName.fromQName(itemName), Item.class); } else { if (item instanceof PrismContainer) { diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java index d477d01f090..d66dcefcd6b 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java @@ -15,6 +15,7 @@ import com.evolveum.midpoint.prism.path.ItemPathCollectionsUtil; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -1405,4 +1406,47 @@ public boolean isRedundant(PrismObject object, boolean assumeMissingItems) th throw new AssertionError("Unknown change type: " + changeType); } } + + @Experimental // todo review and write some tests + @Override + public void removeOperationalItems() { + switch (changeType) { + case ADD: + objectToAdd.getValue().removeOperationalItems(); + return; + case MODIFY: + Iterator> iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta itemDelta = iterator.next(); + ItemDefinition definition = itemDelta.getDefinition(); + if (definition != null && definition.isOperational()) { + iterator.remove(); + } else { + emptyIfNull(itemDelta.getValuesToAdd()).forEach(this::removeOperationalItems); + emptyIfNull(itemDelta.getValuesToDelete()).forEach(this::removeOperationalItems); + emptyIfNull(itemDelta.getValuesToReplace()).forEach(this::removeOperationalItems); + emptyIfNull(itemDelta.getEstimatedOldValues()).forEach(this::removeOperationalItems); + } + } + return; + case DELETE: + // nothing to do here + } + } + + private void removeOperationalItems(PrismValue value) { + if (value instanceof PrismContainerValue) { + ((PrismContainerValue) value).removeOperationalItems(); + } + } + + @Experimental // todo review and write some tests + @Override + public void removeEstimatedOldValues() { + if (changeType == ChangeType.MODIFY) { + for (ItemDelta modification : modifications) { + modification.setEstimatedOldValues(null); + } + } + } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java index 56838d46706..d0f11414735 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java @@ -212,7 +212,7 @@ private XNodeImpl marshalItemValue(@NotNull PrismValue itemValue, @Nullable Item xnode = serializeReferenceValue((PrismReferenceValue)itemValue, (PrismReferenceDefinition) definition, ctx); } else if (itemValue instanceof PrismPropertyValue) { warnIfItemsToSkip(itemValue, itemsToSkip); - xnode = serializePropertyValue((PrismPropertyValue)itemValue, (PrismPropertyDefinition) definition, typeName); + xnode = serializePropertyValue((PrismPropertyValue)itemValue, (PrismPropertyDefinition) definition, typeName, ctx); } else if (itemValue instanceof PrismContainerValue) { xnode = marshalContainerValue((PrismContainerValue)itemValue, (PrismContainerDefinition) definition, ctx, itemsToSkip); } else { @@ -453,7 +453,8 @@ private QName createReferenceQName(QName qname, String namespace) { //region Serializing properties - specific functionality @NotNull - private XNodeImpl serializePropertyValue(@NotNull PrismPropertyValue value, PrismPropertyDefinition definition, QName typeNameIfNoDefinition) throws SchemaException { + private XNodeImpl serializePropertyValue(@NotNull PrismPropertyValue value, PrismPropertyDefinition definition, + QName typeNameIfNoDefinition, SerializationContext ctx) throws SchemaException { @Nullable QName typeName = definition != null ? definition.getTypeName() : typeNameIfNoDefinition; ExpressionWrapper expression = value.getExpression(); if (expression != null) { @@ -467,7 +468,7 @@ private XNodeImpl serializePropertyValue(@NotNull PrismPropertyValue valu } else if (realValue instanceof PolyStringType) { // should not occur ... return beanMarshaller.marshalPolyString(((PolyStringType) realValue).toPolyString()); } else if (beanMarshaller.canProcess(typeName)) { - XNodeImpl xnode = beanMarshaller.marshall(realValue); + XNodeImpl xnode = beanMarshaller.marshall(realValue, ctx); if (xnode == null) { // Marshaling attempt may process the expression in raw element expression = value.getExpression(); diff --git a/infra/schema-pure-jaxb/pom.xml b/infra/schema-pure-jaxb/pom.xml index 9d6ceeb88f1..9578f3c14e8 100644 --- a/infra/schema-pure-jaxb/pom.xml +++ b/infra/schema-pure-jaxb/pom.xml @@ -111,22 +111,7 @@ ${basedir}/src/compile/resources/catalog.xml true false - - - - - - - annotation-3\.xsd - query-3\.xsd - \\types-3\.xsd - /types-3\.xsd - common-.*\.xsd - scripting-3\.xsd - resource-schema-3\.xsd - - - + @@ -177,17 +162,17 @@ true false - - + - annotation-3\.xsd + public.annotation-3\.xsd query-3\.xsd \\types-3\.xsd /types-3\.xsd common-.*\.xsd - scripting-3\.xsd + /scripting/.* + \\scripting\\.* resource-schema-3\.xsd diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd index 102d8c40392..aa7f756b13d 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd @@ -944,6 +944,14 @@ + + + + 4.1 + true + + + @@ -964,6 +972,21 @@ + + + + 4.1 + true + + + + + + + 4.1 + + + @@ -1403,6 +1426,8 @@ + + @@ -2571,6 +2596,7 @@ Kind of operation (captured by OperationResult). + HIGHLY EXPERIMENTAL. Maybe it will be scrapped in the near future. 4.1 @@ -2648,6 +2674,88 @@ + + + + Check whether focus should be loaded. + TODO really? + + + + + + + + + + Focus load check that resulted in focus being loaded. + TODO really? + + + + + + + + + + Projection load. + + + + + + + + + + Full projection load. + + + + + + + + + + Execution of focus change. + + + + + + + + + + Execution of projection change. + + + + + + + + + + Provisioning API operation. + + + + + + + + + + Connector operation. + + + + + + @@ -2674,6 +2782,13 @@ + + + + Specification of the data flow(s) to be visualized. + + + @@ -2753,10 +2868,10 @@ - + - Should operational items be displayed? + What kind of data are to be shown? The interpretation is left to the particular trace visualizer. @@ -2807,6 +2922,16 @@ + + + + The trace is visualized in brief form - potentially multiline but less detailed. + + + + + + @@ -2817,6 +2942,97 @@ + + + + The trace is visualized in full details. + + + + + + + + + + + + + Generic data selection rule. + + + 4.1 + true + + + + + + + No "instance" data shown. + + + + + + + + + + Non-operational data shown. + + + + + + + + + + All data is shown. + + + + + + + + + + + Specification of the data flow(s) to be visualized. + + + true + true + 4.1 + + + + + + + Initial set of items to be shown. + + + + + + + Whether to look backward i.e. to the source(s) for specified item(s). + + + + + + + Whether to look forward i.e. to the target(s) derived from specified item(s). + + + + + + diff --git a/infra/schema/src/main/resources/xml/ns/public/model/scripting/extension-3.xsd b/infra/schema/src/main/resources/xml/ns/public/model/scripting/extension-3.xsd index dfacdef7c45..0f70999d0de 100644 --- a/infra/schema/src/main/resources/xml/ns/public/model/scripting/extension-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/model/scripting/extension-3.xsd @@ -1,5 +1,4 @@ - + xmlns:a="http://prism.evolveum.com/xml/ns/public/annotation-3" + xmlns:s="http://midpoint.evolveum.com/xml/ns/public/model/scripting-3" + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + elementFormDefault="qualified"> @@ -22,9 +18,6 @@ - - - @@ -38,7 +31,4 @@ - - - diff --git a/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestParseTaskBulkAction.java b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestParseTaskBulkAction.java index bae6d9805c7..b23f9112548 100644 --- a/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestParseTaskBulkAction.java +++ b/infra/schema/src/test/java/com/evolveum/midpoint/schema/TestParseTaskBulkAction.java @@ -125,7 +125,7 @@ private void assertTask(PrismObject task) { assertPropertyValue(task, "taskIdentifier", "44444444-4444-4444-4444-000000001111"); assertPropertyDefinition(task, "taskIdentifier", DOMUtil.XSD_STRING, 0, 1); - assertPropertyDefinition(task, "executionStatus", JAXBUtil.getTypeQName(TaskExecutionStatusType.class), 1, 1); + assertPropertyDefinition(task, "executionStatus", JAXBUtil.getTypeQName(TaskExecutionStatusType.class), 0, 1); PrismProperty executionStatusProperty = task.findProperty(TaskType.F_EXECUTION_STATUS); PrismPropertyValue executionStatusValue = executionStatusProperty.getValue(); TaskExecutionStatusType executionStatus = executionStatusValue.getValue(); diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelAuthorizationAction.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelAuthorizationAction.java index 5f5ba1a7b0c..e8402428dc4 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelAuthorizationAction.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelAuthorizationAction.java @@ -10,6 +10,7 @@ import com.evolveum.midpoint.util.DisplayableValue; import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.annotation.Experimental; public enum ModelAuthorizationAction implements DisplayableValue { @@ -83,7 +84,13 @@ public enum ModelAuthorizationAction implements DisplayableValue { PARTIAL_EXECUTION("partialExecution", "Partial execution", "PARTIAL_EXECUTION_HELP"), GET_EXTENSION_SCHEMA("getExtensionSchema", "Get extension schema", "GET_EXTENSION_SCHEMA_HELP"), - RUN_REPORT("runReport", "Run report", "RUN_REPORT_HELP"); + RUN_REPORT("runReport", "Run report", "RUN_REPORT_HELP"), + + @Experimental + RECORD_TRACE("recordTrace", "Record trace", "RECORD_TRACE_HELP"), + + @Experimental + READ_TRACE("readTrace", "Read trace", "READ_TRACE_HELP"); public static final String[] AUTZ_ACTIONS_URLS_SEARCH = new String[] { READ.getUrl(), SEARCH.getUrl() }; public static final String[] AUTZ_ACTIONS_URLS_GET = new String[] { READ.getUrl(), GET.getUrl() }; diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java index 6430401d940..5dfab902ec0 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java @@ -64,6 +64,8 @@ public class MappingImpl // configuration properties (unmodifiable) private final MappingType mappingType; private final MappingKindType mappingKind; + private final ItemPath implicitSourcePath; + private final ItemPath implicitTargetPath; private final ExpressionFactory expressionFactory; private final ExpressionVariables variables; private final PrismContext prismContext; @@ -138,6 +140,8 @@ private MappingImpl(Builder builder) { variables = builder.variables; mappingType = builder.mappingType; mappingKind = builder.mappingKind; + implicitSourcePath = builder.implicitSourcePath; + implicitTargetPath = builder.implicitTargetPath; objectResolver = builder.objectResolver; securityContextManager = builder.securityContextManager; defaultSource = builder.defaultSource; @@ -407,6 +411,8 @@ public void evaluate(Task task, OperationResult parentResult) throws ExpressionE trace = new MappingEvaluationTraceType(prismContext) .mapping(mappingType.clone()) .mappingKind(mappingKind) + .implicitSourcePath(implicitSourcePath != null ? new ItemPathType(implicitSourcePath) : null) + .implicitTargetPath(implicitTargetPath != null ? new ItemPathType(implicitTargetPath) : null) .containingObjectRef(ObjectTypeUtil.createObjectRef(originObject, prismContext)); trace.setMapping(mappingType.clone()); result.addTrace(trace); @@ -1278,6 +1284,8 @@ public PrismValueDeltaSetTripleProducer clone() { MappingImpl clone = new Builder() .mappingType(mappingType) .mappingKind(mappingKind) + .implicitSourcePath(implicitSourcePath) + .implicitTargetPath(implicitTargetPath) .contextDescription(contextDescription) .expressionFactory(expressionFactory) .securityContextManager(securityContextManager) @@ -1466,6 +1474,8 @@ public static final class Builder defaultSource; @@ -1511,6 +1521,16 @@ public Builder mappingKind(MappingKindType val) { return this; } + public Builder implicitSourcePath(ItemPath val) { + implicitSourcePath = val; + return this; + } + + public Builder implicitTargetPath(ItemPath val) { + implicitTargetPath = val; + return this; + } + public Builder objectResolver(ObjectResolver val) { objectResolver = val; return this; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java index 6f869b3c310..2d606df39a9 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java @@ -26,12 +26,15 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; import com.evolveum.midpoint.model.impl.util.AuditHelper; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; import com.evolveum.midpoint.schema.cache.CacheType; import com.evolveum.midpoint.schema.result.OperationResultBuilder; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; import com.evolveum.midpoint.task.api.*; import com.evolveum.midpoint.util.logging.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -143,6 +146,7 @@ public class Clockwork { @Autowired private PolicyRuleSuspendTaskExecutor policyRuleSuspendTaskExecutor; @Autowired private ClockworkAuthorizationHelper clockworkAuthorizationHelper; @Autowired private CacheConfigurationManager cacheConfigurationManager; + @Autowired private SecurityEnforcer securityEnforcer; @Autowired(required = false) private HookRegistry hookRegistry; @@ -246,12 +250,15 @@ public HookOperationMode run(LensContext context, Task // todo check authorization in this method private boolean startTracingIfRequested(LensContext context, Task task, - OperationResultBuilder builder, OperationResult parentResult) throws SchemaException { + OperationResultBuilder builder, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, + SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { // If the result is already traced, we could abstain from recording the final trace ourselves. // But I think it's more reasonable to do that, because e.g. if there is clockwork-inside-clockwork processing, // we would like to have two traces, even if the second one is contained also within the first one. TracingProfileType tracingProfile = ModelExecuteOptions.getTracingProfile(context.getOptions()); if (tracingProfile != null) { + securityEnforcer.authorize(ModelAuthorizationAction.RECORD_TRACE.getUrl(), null, + AuthorizationParameters.EMPTY, null, task, parentResult); builder.tracingProfile(tracer.compileProfile(tracingProfile, parentResult)); return true; } else if (task.getTracingRequestedFor().contains(TracingRootType.CLOCKWORK_RUN)) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java index 048a7f55412..227f0a4c65e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java @@ -13,6 +13,7 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition; @@ -467,7 +468,8 @@ private MappingImpl, ResourceAttributeDefinition> e //noinspection CaughtExceptionImmediatelyRethrown try { - evaluatedMapping = evaluateMapping(builder, attrName, outputDefinition, null, task, result); + evaluatedMapping = evaluateMapping(builder, ShadowType.F_ATTRIBUTES.append(attrName), + attrName, outputDefinition, null, task, result); } catch (SchemaException e) { throw new SchemaException(getAttributeEvaluationErrorMessage(attrName, e), e); @@ -582,16 +584,17 @@ private MappingImpl, PrismContainerDe .originType(OriginType.ASSIGNMENTS) .originObject(getSource()); + ItemPath implicitTargetPath = ShadowType.F_ASSOCIATION.append(assocName); // not quite correct MappingImpl, PrismContainerDefinition> evaluatedMapping = evaluateMapping( - mappingBuilder, assocName, outputDefinition, rAssocDef.getAssociationTarget(), task, result); + mappingBuilder, implicitTargetPath, assocName, outputDefinition, rAssocDef.getAssociationTarget(), task, result); - LOGGER.trace("Evaluated mapping for association " + assocName + ": " + evaluatedMapping); + LOGGER.trace("Evaluated mapping for association {}: {}", assocName, evaluatedMapping); return evaluatedMapping; } @SuppressWarnings("ConstantConditions") private > MappingImpl evaluateMapping( - MappingImpl.Builder builder, QName mappingQName, D outputDefinition, + MappingImpl.Builder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, RefinedObjectClassDefinition assocTargetObjectClassDefinition, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { @@ -601,6 +604,7 @@ private > MappingImpl ev builder = builder.mappingQName(mappingQName) .mappingKind(MappingKindType.CONSTRUCTION) + .implicitTargetPath(implicitTargetPath) .sourceContext(getFocusOdo()) .defaultTargetDefinition(outputDefinition) .originType(getOriginType()) 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 1756f97919d..06af7be080a 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 @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.impl.lens.projector; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; import com.evolveum.midpoint.repo.common.expression.Source; @@ -71,10 +72,10 @@ 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"); + private static final ItemName SHADOW_EXISTS_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "shadowExists"); + private static final ItemName LEGAL_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "legal"); + private static final ItemName ASSIGNED_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "assigned"); + private static final ItemName FOCUS_EXISTS_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "focusExists"); @Autowired private ContextLoader contextLoader; @Autowired private PrismContext prismContext; @@ -482,7 +483,9 @@ private boolean evaluateExistenceMapping(final LensContext params.setContext(context); params.setInitializer(builder -> { - builder.mappingKind(MappingKindType.OUTBOUND); + builder.mappingKind(MappingKindType.OUTBOUND) + .implicitSourcePath(LEGAL_PROPERTY_NAME) + .implicitTargetPath(SHADOW_EXISTS_PROPERTY_NAME); // Source: legal ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(projCtx); @@ -575,6 +578,7 @@ private void evaluateActivationMapping(final LensContex MappingInitializer,PrismPropertyDefinition> initializer = builder -> { builder.mappingKind(MappingKindType.OUTBOUND); + builder.implicitTargetPath(projectionPropertyPath); // Source: administrativeStatus, validFrom or validTo ItemDeltaItem,PrismPropertyDefinition> sourceIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); @@ -584,19 +588,19 @@ private void evaluateActivationMapping(final LensContex ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); // Source: computedShadowStatus - ItemDeltaItem,PrismPropertyDefinition> computedIdi; + ItemPath sourcePath; if (capValidFrom != null && capValidTo != null) { // "Native" validFrom and validTo, directly use administrativeStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); - + sourcePath = focusPropertyPath; } else { // Simulate validFrom and validTo using effectiveStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS); - + sourcePath = SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS; } + ItemDeltaItem, PrismPropertyDefinition> computedIdi = + context.getFocusContext().getObjectDeltaObject().findIdi(sourcePath); + builder.implicitSourcePath(sourcePath); Source,PrismPropertyDefinition> computedSource = new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME); - builder.defaultSource(computedSource); Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME); @@ -605,6 +609,7 @@ private void evaluateActivationMapping(final LensContex } else { Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME); builder.defaultSource(source); + builder.implicitSourcePath(focusPropertyPath); } // Source: legal @@ -658,6 +663,7 @@ private void evaluateOutboundMapping(final LensContext< builder.originType(OriginType.OUTBOUND); builder.mappingKind(MappingKindType.OUTBOUND); builder.originObject(projCtx.getResource()); + builder.implicitTargetPath(projectionPropertyPath); initializer.initialize(builder); 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 5a094acdc26..74b04ed4b4e 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 @@ -1,1656 +1,1673 @@ -/* - * 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.projector; - -import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; -import static com.evolveum.midpoint.schema.result.OperationResult.DEFAULT; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.schema.*; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.internals.InternalsConfig; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -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 com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; -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.LensObjectDeltaOperation; -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.security.SecurityHelper; -import com.evolveum.midpoint.provisioning.api.ProvisioningService; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ExceptionUtil; -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.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.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; - -/** - * Context loader loads the missing parts of the context. The context enters the projector with just the minimum information. - * Context loader gets missing data such as accounts. It gets them from the repository or provisioning as necessary. It follows - * the account links in focus (linkRef) and focus deltas. - * - * @author Radovan Semancik - * - */ -@Component -public class ContextLoader { - - @Autowired - @Qualifier("cacheRepositoryService") - private transient RepositoryService cacheRepositoryService; - - @Autowired private SystemObjectCache systemObjectCache; - @Autowired private ArchetypeManager archetypeManager; - @Autowired private ProvisioningService provisioningService; - @Autowired private PrismContext prismContext; - @Autowired private SecurityHelper securityHelper; - @Autowired private ClockworkMedic medic; - - private static final Trace LOGGER = TraceManager.getTrace(ContextLoader.class); - - public static final String CLASS_DOT = ContextLoader.class.getName() + "."; - private static final String OPERATION_LOAD = CLASS_DOT + "load"; - private static final String OPERATION_LOAD_PROJECTION = CLASS_DOT + "loadProjection"; - - public void load(LensContext context, String activityDescription, - Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - - context.checkAbortRequested(); - - context.recompute(); - - OperationResult result = parentResult.createMinorSubresult(OPERATION_LOAD); - ProjectorComponentTraceType trace; - if (result.isTraced()) { - trace = new ProjectorComponentTraceType(prismContext); - if (result.isTracingNormal(ProjectorComponentTraceType.class)) { - trace.setInputLensContextText(context.debugDump()); - } - trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); - result.addTrace(trace); - } else { - trace = null; - } - - try { - - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - preprocessProjectionContext(context, projectionContext, task, result); - } - - if (consistencyChecks) context.checkConsistence(); - - determineFocusContext(context, task, result); - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - - context.recomputeFocus(); - - loadFromSystemConfig(context, task, result); - - if (FocusType.class.isAssignableFrom(context.getFocusClass())) { - // this also removes the accountRef deltas - //noinspection unchecked - loadLinkRefs((LensContext)context, task, result); - LOGGER.trace("loadLinkRefs done"); - } - - // Some cleanup - if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().isModify() && focusContext.getPrimaryDelta().isEmpty()) { - focusContext.setPrimaryDelta(null); - } - - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - if (projectionContext.getSynchronizationIntent() != null) { - // Accounts with explicitly set intent are never rotten. These are explicitly requested actions - // if they fail then they really should fail. - projectionContext.setFresh(true); - } - } - - setPrimaryDeltaOldValue(focusContext); - - } else { - // Projection contexts are not rotten in this case. There is no focus so there is no way to refresh them. - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - projectionContext.setFresh(true); - } - } - - removeRottenContexts(context); - - if (consistencyChecks) context.checkConsistence(); - - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - context.checkAbortRequested(); - // TODO: not perfect. Practically, we want loadProjection operation to contain all the projection - // results. But for that we would need code restructure. - OperationResult projectionResult = result.createMinorSubresult(OPERATION_LOAD_PROJECTION); - try { - finishLoadOfProjectionContext(context, projectionContext, task, projectionResult); - } catch (Throwable e) { - projectionResult.recordFatalError(e); - throw e; - } - projectionResult.computeStatus(); - } - - if (consistencyChecks) context.checkConsistence(); - - context.recompute(); - - if (consistencyChecks) { - fullCheckConsistence(context); - } - - medic.traceContext(LOGGER, activityDescription, "after load", false, context, false); - - result.computeStatusComposite(); - - } catch (Throwable e) { - result.recordFatalError(e); - throw e; - } finally { - if (trace != null) { - if (result.isTracingNormal(ProjectorComponentTraceType.class)) { - trace.setOutputLensContextText(context.debugDump()); - } - trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); - } - } - } - - - /** - * Removes projection contexts that are not fresh. - * These are usually artifacts left after the context reload. E.g. an account that used to be linked to a user before - * but was removed in the meantime. - */ - private void removeRottenContexts(LensContext context) { - Iterator projectionIterator = context.getProjectionContextsIterator(); - while (projectionIterator.hasNext()) { - LensProjectionContext projectionContext = projectionIterator.next(); - if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { - // We must never remove contexts with primary delta. Even though it fails later on. - // What the user wishes should be done (or at least attempted) regardless of the consequences. - // Vox populi vox dei - continue; - } - if (projectionContext.getWave() >= context.getExecutionWave()) { - // We must not remove context from this and later execution waves. These haven't had the - // chance to be executed yet - continue; - } - ResourceShadowDiscriminator discr = projectionContext.getResourceShadowDiscriminator(); - if (discr != null && discr.getOrder() > 0) { - // HACK never rot higher-order context. TODO: check if lower-order context is rotten, the also rot this one - continue; - } - if (!projectionContext.isFresh()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Removing rotten context {}", projectionContext.getHumanReadableName()); - } - - if (projectionContext.isToBeArchived()) { - context.getHistoricResourceObjects().add(projectionContext.getResourceShadowDiscriminator()); - } - - List> executedDeltas = projectionContext.getExecutedDeltas(); - context.getRottenExecutedDeltas().addAll(executedDeltas); - projectionIterator.remove(); - } - } - } - - - /** - * Make sure that the projection context is loaded as appropriate. - */ - public void makeSureProjectionIsLoaded(LensContext context, - LensProjectionContext projectionContext, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - preprocessProjectionContext(context, projectionContext, task, result); - finishLoadOfProjectionContext(context, projectionContext, task, result); - } - - /** - * Make sure that the context is OK and consistent. It means that is has a resource, it has correctly processed - * discriminator, etc. - */ - private void preprocessProjectionContext(LensContext context, - LensProjectionContext projectionContext, Task task, OperationResult result) - throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (!ShadowType.class.isAssignableFrom(projectionContext.getObjectTypeClass())) { - return; - } - String resourceOid = null; - boolean isThombstone = false; - ShadowKindType kind = ShadowKindType.ACCOUNT; - String intent = null; - String tag = null; - int order = 0; - ResourceShadowDiscriminator rsd = projectionContext.getResourceShadowDiscriminator(); - if (rsd != null) { - resourceOid = rsd.getResourceOid(); - isThombstone = rsd.isTombstone(); - kind = rsd.getKind(); - intent = rsd.getIntent(); - tag = rsd.getTag(); - order = rsd.getOrder(); - } - if (resourceOid == null && projectionContext.getObjectCurrent() != null) { - resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectCurrent().asObjectable()); - } - if (resourceOid == null && projectionContext.getObjectNew() != null) { - resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectNew().asObjectable()); - } - // We still may not have resource OID here. E.g. in case of the delete when the account is not loaded yet. It is - // perhaps safe to skip this. It will be sorted out later. - - if (resourceOid != null) { - if (intent == null && projectionContext.getObjectNew() != null) { - ShadowType shadowNewType = projectionContext.getObjectNew().asObjectable(); - kind = ShadowUtil.getKind(shadowNewType); - intent = ShadowUtil.getIntent(shadowNewType); - tag = shadowNewType.getTag(); - } - ResourceType resource = projectionContext.getResource(); - if (resource == null) { - resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - projectionContext.setResource(resource); - } - String refinedIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); - rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, isThombstone); - rsd.setOrder(order); - projectionContext.setResourceShadowDiscriminator(rsd); - } - if (projectionContext.getOid() == null && rsd != null && rsd.getOrder() != 0) { - // Try to determine OID from lower-order contexts - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (rsd.equivalent(aDiscr) && aProjCtx.getOid() != null) { - projectionContext.setOid(aProjCtx.getOid()); - break; - } - } - } - } - - /** - * try to load focus context from oid, delta, projections (e.g. by determining account owners) - */ - public void determineFocusContext(LensContext context, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - - OperationResult result = parentResult.subresult(CLASS_DOT + "determineFocusContext") - .setMinor() - .build(); - FocusLoadedTraceType trace; - if (result.isTraced()) { - trace = new FocusLoadedTraceType(prismContext); - if (result.isTracingNormal(FocusLoadedTraceType.class)) { - trace.setInputLensContextText(context.debugDump()); - } - trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); - result.addTrace(trace); - } else { - trace = null; - } - LensFocusContext focusContext = context.getFocusContext(); - try { - if (focusContext == null) { - focusContext = determineFocusContextFromProjections(context, result); - } - - if (focusContext == null) { - result.addReturnComment("Nothing to load"); - return; - } - - // Make sure that we RELOAD the focus object if the context is not fresh - // the focus may have changed in the meantime - if (focusContext.getObjectCurrent() != null && focusContext.isFresh()) { - result.addReturnComment("Already loaded"); - return; - } - ObjectDelta objectDelta = focusContext.getDelta(); - if (objectDelta != null && objectDelta.isAdd() && focusContext.getExecutedDeltas().isEmpty()) { - //we're adding the focal object. No need to load it, it is in the delta - focusContext.setFresh(true); - result.addReturnComment("Obtained from delta"); - return; - } - if (focusContext.getObjectCurrent() != null && objectDelta != null && objectDelta.isDelete()) { - // do not reload if the delta is delete. the reload will most likely fail anyway - // but DO NOT set the fresh flag in this case, it may be misleading - result.addReturnComment("Not loading as delta is DELETE"); - return; - } - - String focusOid = focusContext.getOid(); - if (StringUtils.isBlank(focusOid)) { - throw new IllegalArgumentException("No OID in primary focus delta"); - } - - PrismObject object; - if (ObjectTypes.isClassManagedByProvisioning(focusContext.getObjectTypeClass())) { - object = provisioningService.getObject(focusContext.getObjectTypeClass(), focusOid, - SelectorOptions.createCollection(GetOperationOptions.createNoFetch()), task, result); - result.addReturnComment("Loaded via provisioning"); - } else { - - // Always load a complete object here, including the not-returned-by-default properties. - // This is temporary measure to make sure that the mappings will have all they need. - // See MID-2635 - Collection> options = - SelectorOptions.createCollection(GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE)); - object = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusOid, options, result); - result.addReturnComment("Loaded from repository"); - } - - focusContext.setLoadedObject(object); - focusContext.setFresh(true); - LOGGER.trace("Focal object loaded: {}", object); - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - if (trace != null) { - if (result.isTracingNormal(FocusLoadedTraceType.class)) { - trace.setOutputLensContextText(context.debugDump()); - } - trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); - } - result.computeStatusIfUnknown(); - } - } - - private LensFocusContext determineFocusContextFromProjections(LensContext context, OperationResult result) { - String focusOid = null; - LensProjectionContext projectionContextThatYeildedFocusOid = null; - PrismObject focusOwner = null; - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - String projectionOid = projectionContext.getOid(); - if (projectionOid != null) { - PrismObject shadowOwner = cacheRepositoryService.searchShadowOwner(projectionOid, - SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), - result); - if (shadowOwner != null) { - if (focusOid == null || focusOid.equals(shadowOwner.getOid())) { - focusOid = shadowOwner.getOid(); - //noinspection unchecked - focusOwner = (PrismObject) shadowOwner; - projectionContextThatYeildedFocusOid = projectionContext; - } else { - throw new IllegalArgumentException("The context does not have explicit focus. Attempt to determine focus failed because two " + - "projections points to different foci: "+projectionContextThatYeildedFocusOid+"->"+focusOid+"; "+ - projectionContext+"->"+shadowOwner); - } - } - } - } - - if (focusOid != null) { - LensFocusContext focusCtx = context.getOrCreateFocusContext(focusOwner.getCompileTimeClass()); - focusCtx.setOid(focusOid); - return focusCtx; - } - - return null; - } - - private void setPrimaryDeltaOldValue(LensElementContext ctx) { - if (ctx.getPrimaryDelta() != null && ctx.getObjectOld() != null && ctx.isModify()) { - boolean freezeAfterChange; - if (ctx.getPrimaryDelta().isImmutable()) { - ctx.setPrimaryDelta(ctx.getPrimaryDelta().clone()); - freezeAfterChange = true; - } else { - freezeAfterChange = false; - } - for (ItemDelta itemDelta: ctx.getPrimaryDelta().getModifications()) { - LensUtil.setDeltaOldValue(ctx, itemDelta); - } - if (freezeAfterChange) { - ctx.getPrimaryDelta().freeze(); - } - } - } - - private void loadFromSystemConfig(LensContext context, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException { - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - if (systemConfiguration == null) { - // This happens in some tests. And also during first startup. - return; - } - context.setSystemConfiguration(systemConfiguration); - SystemConfigurationType systemConfigurationType = systemConfiguration.asObjectable(); - - if (context.getFocusContext() != null) { - if (context.getFocusContext().getArchetypePolicyType() == null) { - ArchetypePolicyType archetypePolicy = determineArchetypePolicy(context, task, result); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Selected archetype policy:\n{}", - archetypePolicy==null?null:archetypePolicy.asPrismContainerValue().debugDump(1)); - } - context.getFocusContext().setArchetypePolicyType(archetypePolicy); - } - } - - if (context.getFocusTemplate() == null) { - // TODO is the nullity check needed here? - setFocusTemplate(context, result); - } - - if (context.getAccountSynchronizationSettings() == null) { - ProjectionPolicyType globalAccountSynchronizationSettings = systemConfigurationType.getGlobalAccountSynchronizationSettings(); - LOGGER.trace("Applying globalAccountSynchronizationSettings to context: {}", globalAccountSynchronizationSettings); - context.setAccountSynchronizationSettings(globalAccountSynchronizationSettings); - } - - loadSecurityPolicy(context, task, result); - } - - private ArchetypePolicyType determineArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { - PrismObject systemConfiguration = context.getSystemConfiguration(); - if (systemConfiguration == null) { - return null; - } - if (context.getFocusContext() == null) { - return null; - } - PrismObject object = context.getFocusContext().getObjectAny(); - String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); - return archetypeManager.determineArchetypePolicy(object, explicitArchetypeOid, result); - } - - public ArchetypeType updateArchetype(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { - PrismObject systemConfiguration = context.getSystemConfiguration(); - if (systemConfiguration == null) { - return null; - } - if (context.getFocusContext() == null) { - return null; - } - - PrismObject object = context.getFocusContext().getObjectAny(); - - String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); - PrismObject archetype = archetypeManager.determineArchetype(object, explicitArchetypeOid, result); - ArchetypeType archetypeType = null; - if (archetype != null) { - archetypeType = archetype.asObjectable(); - } - - context.getFocusContext().setArchetype(archetypeType); - - return archetypeType; - } - - public void updateArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { - if (context.getFocusContext() == null) { - return; - } - ArchetypePolicyType newArchetypePolicy = determineArchetypePolicy(context, task, result); - if (newArchetypePolicy != context.getFocusContext().getArchetypePolicyType()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Changed policy configuration because of changed subtypes:\n{}", - newArchetypePolicy==null?null:newArchetypePolicy.asPrismContainerValue().debugDump(1)); - } - context.getFocusContext().setArchetypePolicyType(newArchetypePolicy); - } - } - - // expects that object policy configuration is already set in focusContext - public void setFocusTemplate(LensContext context, OperationResult result) - throws ObjectNotFoundException, SchemaException { - - // 1. When this method is called after inbound processing, we might want to change the existing template - // (because e.g. subtype or archetype was determined and we want to move from generic to more specific template). - // 2. On the other hand, if focus template is set up explicitly from the outside (e.g. in synchronization section) - // we probably do not want to change it here. - if (context.getFocusTemplate() != null && context.isFocusTemplateExternallySet()) { - return; - } - - String currentOid = context.getFocusTemplate() != null ? context.getFocusTemplate().getOid() : null; - String newOid; - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - newOid = null; - } else { - ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); - if (archetypePolicy == null) { - LOGGER.trace("No default object template (no policy)"); - newOid = null; - } else { - ObjectReferenceType templateRef = archetypePolicy.getObjectTemplateRef(); - if (templateRef == null) { - LOGGER.trace("No default object template (no templateRef)"); - newOid = null; - } else { - newOid = templateRef.getOid(); - } - } - } - - LOGGER.trace("current focus template OID = {}, new = {}", currentOid, newOid); - if (!java.util.Objects.equals(currentOid, newOid)) { - ObjectTemplateType template; - if (newOid != null) { - template = cacheRepositoryService.getObject(ObjectTemplateType.class, newOid, null, result).asObjectable(); - } else { - template = null; - } - context.setFocusTemplate(template); - } - } - - private void loadLinkRefs(LensContext context, Task task, OperationResult result) throws ObjectNotFoundException, - SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - // Nothing to load - return; - } - - LOGGER.trace("loadLinkRefs starting"); - - PrismObject focusCurrent = focusContext.getObjectCurrent(); - if (focusCurrent != null) { - loadLinkRefsFromFocus(context, focusCurrent, task, result); - LOGGER.trace("loadLinkRefsFromFocus done"); - } - - if (consistencyChecks) context.checkConsistence(); - - loadLinkRefsFromDelta(context, focusCurrent, focusContext, task, result); - LOGGER.trace("loadLinkRefsFromDelta done"); - - if (consistencyChecks) context.checkConsistence(); - - loadProjectionContextsSync(context, task, result); - LOGGER.trace("loadProjectionContextsSync done"); - - if (consistencyChecks) context.checkConsistence(); - } - - /** - * Does not overwrite existing account contexts, just adds new ones. - */ - private void loadLinkRefsFromFocus(LensContext context, PrismObject focus, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); - if (linkRef == null) { - return; - } - for (PrismReferenceValue linkRefVal : linkRef.getValues()) { - String oid = linkRefVal.getOid(); - if (StringUtils.isBlank(oid)) { - LOGGER.trace("Null or empty OID in link reference {} in:\n{}", linkRef, - focus.debugDump(1)); - throw new SchemaException("Null or empty OID in link reference in " + focus); - } - LensProjectionContext existingAccountContext = findAccountContext(oid, context); - -// if (!canBeLoaded(context, existingAccountContext)) { -// continue; -// } - - if (existingAccountContext != null) { - // TODO: do we need to reload the account inside here? yes we need - - existingAccountContext.setFresh(true); - continue; - } - PrismObject shadow; - //noinspection unchecked - PrismObject shadowFromLink = linkRefVal.getObject(); - if (shadowFromLink == null) { - GetOperationOptions rootOpts; - if (context.isDoReconciliationForAllProjections()) { - rootOpts = GetOperationOptions.createForceRetry(); - } else { - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - } - - Collection> options = SelectorOptions.createCollection(rootOpts); - LOGGER.trace("Loading shadow {} from linkRef, options={}", oid, options); - try { - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - } catch (ObjectNotFoundException e) { - // Broken accountRef. We need to mark it for deletion - LensProjectionContext projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); - projectionContext.setFresh(true); - projectionContext.setExists(false); - projectionContext.setShadowExistsInRepo(false); - OperationResult getObjectSubresult = result.getLastSubresult(); - getObjectSubresult.setErrorsHandled(); - continue; - } - } else { - shadow = shadowFromLink; - // Make sure it has a proper definition. This may come from outside of the model. - provisioningService.applyDefinition(shadow, task, result); - } - LensProjectionContext projectionContext = getOrCreateAccountContext(context, shadow, task, result); - projectionContext.setFresh(true); - projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); - if (ShadowUtil.isDead(shadow.asObjectable())) { - projectionContext.markTombstone(); - LOGGER.trace("Loading dead shadow {} for projection {}.", shadow, projectionContext.getHumanReadableName()); - continue; - } - if (context.isDoReconciliationForAllProjections()) { - projectionContext.setDoReconciliation(true); - } - if (projectionContext.isDoReconciliation()) { - // Do not load old account now. It will get loaded later in the - // reconciliation step. - continue; - } - projectionContext.setLoadedObject(shadow); - } - } - - private void loadLinkRefsFromDelta(LensContext context, PrismObject focus, - LensFocusContext focusContext, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - - ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); - if (focusPrimaryDelta == null) { - return; - } - - ReferenceDelta linkRefDelta; - if (focusPrimaryDelta.getChangeType() == ChangeType.ADD) { - PrismReference linkRef = focusPrimaryDelta.getObjectToAdd().findReference( - FocusType.F_LINK_REF); - if (linkRef == null) { - // Adding new focus with no linkRef -> nothing to do - return; - } - linkRefDelta = linkRef.createDelta(FocusType.F_LINK_REF); - linkRefDelta.addValuesToAdd(PrismValueCollectionsUtil.cloneValues(linkRef.getValues())); - } else if (focusPrimaryDelta.getChangeType() == ChangeType.MODIFY) { - linkRefDelta = focusPrimaryDelta.findReferenceModification(FocusType.F_LINK_REF); - if (linkRefDelta == null) { - return; - } - } else { - // delete, all existing account are already marked for delete - return; - } - - if (linkRefDelta.isReplace()) { - // process "replace" by distributing values to delete and add - linkRefDelta = (ReferenceDelta) linkRefDelta.clone(); - PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); - linkRefDelta.distributeReplace(linkRef == null ? null : linkRef.getValues()); - } - - if (linkRefDelta.getValuesToAdd() != null) { - for (PrismReferenceValue refVal : linkRefDelta.getValuesToAdd()) { - String oid = refVal.getOid(); - LensProjectionContext projectionContext = null; - PrismObject shadow = null; - boolean isCombinedAdd = false; - if (oid == null) { - // Adding new account - shadow = refVal.getObject(); - if (shadow == null) { - throw new SchemaException("Null or empty OID in account reference " + refVal + " in " - + focus); - } - provisioningService.applyDefinition(shadow, task, result); - if (consistencyChecks) ShadowUtil.checkConsistence(shadow, "account from "+linkRefDelta); - // Check for conflicting change - projectionContext = LensUtil.getProjectionContext(context, shadow, provisioningService, prismContext, task, result); - if (projectionContext != null) { - // There is already existing context for the same discriminator. Tolerate this only if - // the deltas match. It is an error otherwise. - ObjectDelta primaryDelta = projectionContext.getPrimaryDelta(); - if (primaryDelta == null) { - throw new SchemaException("Attempt to add "+shadow+" to a focus that already contains "+ - projectionContext.getHumanReadableKind()+" of type '"+ - projectionContext.getResourceShadowDiscriminator().getIntent()+"' on "+projectionContext.getResource()); - } - if (!primaryDelta.isAdd()) { - throw new SchemaException("Conflicting changes in the context. " + - "Add of linkRef in the focus delta with embedded object conflicts with explicit delta "+primaryDelta); - } - if (!shadow.equals(primaryDelta.getObjectToAdd())) { - throw new SchemaException("Conflicting changes in the context. " + - "Add of linkRef in the focus delta with embedded object is not adding the same object as explicit delta "+primaryDelta); - } - } else { - // Create account context from embedded object - projectionContext = createProjectionContext(context, shadow, task, result); - } - // This is a new account that is to be added. So it should - // go to account primary delta - ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); - projectionContext.setPrimaryDelta(accountPrimaryDelta); - projectionContext.setFullShadow(true); - projectionContext.setExists(false); - isCombinedAdd = true; - } else { - // We have OID. This is either linking of existing account or - // add of new account - // therefore check for account existence to decide - try { - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - Collection> options = SelectorOptions.createCollection(rootOpts); - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - // Create account context from retrieved object - projectionContext = getOrCreateAccountContext(context, shadow, task, result); - projectionContext.setLoadedObject(shadow); - projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); - } catch (ObjectNotFoundException e) { - if (refVal.getObject() == null) { - // account does not exist, no composite account in - // ref -> this is really an error - throw e; - } else { - // New account (with OID) - result.muteLastSubresultError(); - shadow = refVal.getObject(); - if (!shadow.hasCompleteDefinition()) { - provisioningService.applyDefinition(shadow, task, result); - } - // Create account context from embedded object - projectionContext = createProjectionContext(context, shadow, task, result); - ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); - projectionContext.setPrimaryDelta(accountPrimaryDelta); - projectionContext.setFullShadow(true); - projectionContext.setExists(false); - projectionContext.setShadowExistsInRepo(false); - isCombinedAdd = true; - } - } - } - if (context.isDoReconciliationForAllProjections() && !isCombinedAdd) { - projectionContext.setDoReconciliation(true); - } - projectionContext.setFresh(true); - } - } - - if (linkRefDelta.getValuesToDelete() != null) { - for (PrismReferenceValue refVal : linkRefDelta.getValuesToDelete()) { - String oid = refVal.getOid(); - LensProjectionContext projectionContext = null; - PrismObject shadow = null; - if (oid == null) { - throw new SchemaException("Cannot delete account ref without an oid in " + focus); - } else { - try { - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - // Create account context from retrieved object - projectionContext = getOrCreateAccountContext(context, shadow, task, result); - projectionContext.setLoadedObject(shadow); - projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); - } catch (ObjectNotFoundException e) { - try{ - // Broken accountRef. We need to try again with raw options, because the error should be thrown because of non-existent resource - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); - projectionContext.setFresh(true); - projectionContext.setExists(false); - projectionContext.setShadowExistsInRepo(false); - OperationResult getObjectSubresult = result.getLastSubresult(); - getObjectSubresult.setErrorsHandled(); - } catch (ObjectNotFoundException ex){ - // This is still OK. It means deleting an accountRef - // that points to non-existing object - // just log a warning - LOGGER.warn("Deleting accountRef of " + focus + " that points to non-existing OID " - + oid); - } - - } - } - if (projectionContext != null) { - if (refVal.getObject() == null) { - projectionContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); - } else { - projectionContext.setSynchronizationIntent(SynchronizationIntent.DELETE); - ObjectDelta accountPrimaryDelta = shadow.createDeleteDelta(); - projectionContext.setPrimaryDelta(accountPrimaryDelta); - } - projectionContext.setFresh(true); - } - - } - } - - // remove the accountRefs without oid. These will get into the way now. - // The accounts - // are in the context now and will be linked at the end of the process - // (it they survive the policy) - // We need to make sure this happens on the real primary focus delta - - ObjectDelta primaryDeltaToUpdate; - if (focusPrimaryDelta.isImmutable()) { - primaryDeltaToUpdate = focusPrimaryDelta.clone(); - focusContext.setPrimaryDelta(primaryDeltaToUpdate); - } else { - primaryDeltaToUpdate = focusPrimaryDelta; - } - - if (primaryDeltaToUpdate.getChangeType() == ChangeType.ADD) { - primaryDeltaToUpdate.getObjectToAdd().removeReference(FocusType.F_LINK_REF); - } else if (primaryDeltaToUpdate.getChangeType() == ChangeType.MODIFY) { - primaryDeltaToUpdate.removeReferenceModification(FocusType.F_LINK_REF); - } - // It is little bit questionable whether we need to make primary delta immutable. It makes some sense, but I am not sure. - // Note that (as a side effect) this can make "focus new" immutable as well, in the case of ADD delta. - primaryDeltaToUpdate.freeze(); - } - - private void loadProjectionContextsSync(LensContext context, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - for (LensProjectionContext projCtx : context.getProjectionContexts()) { - if (projCtx.isFresh() && projCtx.getObjectCurrent() != null) { - // already loaded - continue; - } - ObjectDelta syncDelta = projCtx.getSyncDelta(); - if (syncDelta != null) { - if (projCtx.isDoReconciliation()) { - // Do not load old account now. It will get loaded later in the - // reconciliation step. Just mark it as fresh. - projCtx.setFresh(true); - continue; - } - String oid = syncDelta.getOid(); - PrismObject shadow = null; - - if (syncDelta.getChangeType() == ChangeType.ADD) { - shadow = syncDelta.getObjectToAdd().clone(); - projCtx.setLoadedObject(shadow); - projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); - - } else { - - if (oid == null) { - throw new IllegalArgumentException("No OID in sync delta in " + projCtx); - } - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - GetOperationOptions option = GetOperationOptions.createNoFetch(); - option.setDoNotDiscovery(true); - option.setPointInTimeType(PointInTimeType.FUTURE); - Collection> options = SelectorOptions.createCollection(option); - - try { - - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - - } catch (ObjectNotFoundException e) { - LOGGER.trace("Loading shadow {} from sync delta failed: not found", oid); - projCtx.setExists(false); - projCtx.setObjectCurrent(null); - projCtx.setShadowExistsInRepo(false); - } - - // We will not set old account if the delta is delete. The - // account does not really exists now. - // (but the OID and resource will be set from the repo - // shadow) - if (syncDelta.getChangeType() == ChangeType.DELETE) { - projCtx.markTombstone(); - } else if (shadow != null) { - syncDelta.applyTo(shadow); - projCtx.setLoadedObject(shadow); - projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); - } - } - - // Make sure OID is set correctly - projCtx.setOid(oid); - // Make sure that resource is also resolved - if (projCtx.getResource() == null && shadow != null) { - String resourceOid = ShadowUtil.getResourceOid(shadow.asObjectable()); - if (resourceOid == null) { - throw new IllegalArgumentException("No resource OID in " + shadow); - } - ResourceType resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - projCtx.setResource(resourceType); - } - projCtx.setFresh(true); - } - } - } - -// private boolean canBeLoaded(LensContext context, LensProjectionContext projCtx){ -// if (QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_DISCOVERY).equals(context.getChannel()) && projCtx == null && ModelExecuteOptions.isLimitPropagation(context.getOptions())) { -// // avoid to create projection context if the channel which -// // triggered this operation is discovery..we need only -// // projection context of discovered shadow -// return false; -// } -// return true; -// } - - private LensProjectionContext getOrCreateAccountContext(LensContext context, - PrismObject projection, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - ShadowType shadowType = projection.asObjectable(); - String resourceOid = ShadowUtil.getResourceOid(shadowType); - if (resourceOid == null) { - throw new SchemaException("The " + projection + " has null resource reference OID"); - } - - LensProjectionContext projectionContext = context.findProjectionContextByOid(shadowType.getOid()); - - if (projectionContext == null) { - String intent = ShadowUtil.getIntent(shadowType); - ShadowKindType kind = ShadowUtil.getKind(shadowType); - ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - intent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); - boolean thombstone = false; - if (ShadowUtil.isDead(shadowType)) { - thombstone = true; - } - ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, intent, shadowType.getTag(), thombstone); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - - if (projectionContext.getOid() == null) { - projectionContext.setOid(projection.getOid()); - } else if (projection.getOid() != null && !projectionContext.getOid().equals(projection.getOid())) { - // Conflict. We have existing projection and another project that is added (with the same discriminator). - // Chances are that the old object is already deleted (e.g. during rename). So let's be - // slightly inefficient here and check for existing shadow existence - try { - GetOperationOptions rootOpt = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); - rootOpt.setDoNotDiscovery(true); - Collection> opts = SelectorOptions.createCollection(rootOpt); - LOGGER.trace("Projection conflict detected, existing: {}, new {}", projectionContext.getOid(), projection.getOid()); - PrismObject existingShadow = provisioningService.getObject(ShadowType.class, projectionContext.getOid(), opts, task, result); - // Maybe it is the other way around - try { - PrismObject newShadow = provisioningService.getObject(ShadowType.class, projection.getOid(), opts, task, result); - // Obviously, two projections with the same discriminator exists - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Projection {} already exists in context\nExisting:\n{}\nNew:\n{}", rsd, - existingShadow.debugDump(1), newShadow.debugDump(1)); - } - if (!ShadowUtil.isDead(newShadow.asObjectable())) { - throw new PolicyViolationException("Projection "+rsd+" already exists in context (existing "+existingShadow+", new "+projection); - } - // Dead shadow. This is somehow expected, fix it and we can go on - rsd.setTombstone(true); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - projectionContext.setExists(ShadowUtil.isExists(newShadow.asObjectable())); - projectionContext.setFullShadow(false); - } catch (ObjectNotFoundException e) { - // This is somehow expected, fix it and we can go on - result.muteLastSubresultError(); - // We have to create new context in this case, but it has to have thumbstone set - rsd.setTombstone(true); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - // We have to mark it as dead right now, otherwise the uniqueness check may fail - markShadowDead(projection.getOid(), result); - projectionContext.setShadowExistsInRepo(false); - } - } catch (ObjectNotFoundException e) { - // This is somehow expected, fix it and we can go on - result.muteLastSubresultError(); - String shadowOid = projectionContext.getOid(); - projectionContext.getResourceShadowDiscriminator().setTombstone(true); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - projectionContext.setShadowExistsInRepo(false); - // We have to mark it as dead right now, otherwise the uniqueness check may fail - markShadowDead(shadowOid, result); - } - } - } - return projectionContext; - } - - private void markShadowDead(String oid, OperationResult result) { - if (oid == null) { - // nothing to mark - return; - } - Collection> modifications = MiscSchemaUtil.createCollection( - prismContext.deltaFactory().property().createReplaceDelta(prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class), - ShadowType.F_DEAD, true)); - try { - cacheRepositoryService.modifyObject(ShadowType.class, oid, modifications, result); - // TODO report to task? - } catch (ObjectNotFoundException e) { - // Done already - result.muteLastSubresultError(); - } catch (ObjectAlreadyExistsException | SchemaException e) { - // Should not happen - throw new SystemException(e.getMessage(), e); - } - } - - - private LensProjectionContext createProjectionContext(LensContext context, - PrismObject account, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ShadowType shadowType = account.asObjectable(); - String resourceOid = ShadowUtil.getResourceOid(shadowType); - if (resourceOid == null) { - throw new SchemaException("The " + account + " has null resource reference OID"); - } - String intent = ShadowUtil.getIntent(shadowType); - ShadowKindType kind = ShadowUtil.getKind(shadowType); - ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - String accountIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); - ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, accountIntent, shadowType.getTag(), false); - LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); - if (accountSyncContext != null) { - throw new SchemaException("Attempt to add "+account+" to a focus that already contains projection of type '"+accountIntent+"' on "+resource); - } - accountSyncContext = context.createProjectionContext(rsd); - accountSyncContext.setResource(resource); - accountSyncContext.setOid(account.getOid()); - return accountSyncContext; - } - - private LensProjectionContext findAccountContext(String accountOid, LensContext context) { - for (LensProjectionContext accContext : context.getProjectionContexts()) { - if (accountOid.equals(accContext.getOid())) { - return accContext; - } - } - - return null; - } - - private LensProjectionContext getOrCreateEmptyThombstoneProjectionContext(LensContext context, - String missingShadowOid) { - LensProjectionContext projContext = context.findProjectionContextByOid(missingShadowOid); - if (projContext == null) { - projContext = context.createProjectionContext(null); - projContext.setOid(missingShadowOid); - } - - if (projContext.getResourceShadowDiscriminator() == null) { - projContext.setResourceShadowDiscriminator(new ResourceShadowDiscriminator(null, null, null, null, true)); - } else { - projContext.markTombstone(); - } - - projContext.setFullShadow(false); - projContext.setObjectCurrent(null); - - return projContext; - } - - /** - * Check reconcile flag in account sync context and set accountOld - * variable if it's not set (from provisioning), load resource (if not set already), etc. - */ - private void finishLoadOfProjectionContext(LensContext context, - LensProjectionContext projContext, Task task, OperationResult result) - throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - - if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { - return; - } - - // MID-2436 (volatile objects) - as a quick but effective hack, we set reconciliation:=TRUE for volatile accounts - ResourceObjectTypeDefinitionType objectDefinition = projContext.getResourceObjectTypeDefinitionType(); - if (objectDefinition != null && objectDefinition.getVolatility() == ResourceObjectVolatilityType.UNPREDICTABLE && !projContext.isDoReconciliation()) { - LOGGER.trace("Resource object volatility is UNPREDICTABLE => setting doReconciliation to TRUE for {}", projContext.getResourceShadowDiscriminator()); - projContext.setDoReconciliation(true); - } - - // Remember OID before the object could be wiped - String projectionObjectOid = projContext.getOid(); - if (projContext.isDoReconciliation() && !projContext.isFullShadow()) { - // The current object is useless here. So lets just wipe it so it will get loaded - projContext.setObjectCurrent(null); - } - - // Load current object - boolean tombstone = false; - PrismObject projectionObject = projContext.getObjectCurrent(); - if (projContext.getObjectCurrent() == null || needToReload(context, projContext)) { - if (projContext.isAdd()) { - // No need to load old object, there is none - projContext.setExists(false); - projContext.recompute(); - projectionObject = projContext.getObjectNew(); - } else { - if (projectionObjectOid == null) { - projContext.setExists(false); - if (projContext.getResourceShadowDiscriminator() == null || projContext.getResourceShadowDiscriminator().getResourceOid() == null) { - throw new SystemException( - "Projection "+projContext.getHumanReadableName()+" with null OID, no representation and no resource OID in account sync context "+projContext); - } - } else { - GetOperationOptions rootOptions = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); - if (projContext.isDoReconciliation()) { - rootOptions.setForceRefresh(true); - if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { - // Avoid discovery loops - rootOptions.setDoNotDiscovery(true); - } - } else { - rootOptions.setNoFetch(true); - } - rootOptions.setAllowNotFound(true); - Collection> options = SelectorOptions.createCollection(rootOptions); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loading shadow {} for projection {}, options={}", projectionObjectOid, projContext.getHumanReadableName(), options); - } - - try { - PrismObject objectOld = provisioningService.getObject( - projContext.getObjectTypeClass(), projectionObjectOid, options, task, result); - if (LOGGER.isTraceEnabled()) { - if (!GetOperationOptions.isNoFetch(rootOptions) && !GetOperationOptions.isRaw(rootOptions)) { - LOGGER.trace("Full shadow loaded for {}:\n{}", projContext.getHumanReadableName(), objectOld.debugDump(1)); - } - } - Validate.notNull(objectOld.getOid()); - if (InternalsConfig.consistencyChecks) { - String resourceOid = projContext.getResourceOid(); - if (resourceOid != null && !resourceOid.equals(objectOld.asObjectable().getResourceRef().getOid())) { - throw new IllegalStateException("Loaded shadow with wrong resourceRef. Loading shadow "+projectionObjectOid+", got "+ - objectOld.getOid()+", expected resourceRef "+resourceOid+", but was "+objectOld.asObjectable().getResourceRef().getOid()+ - " for context "+projContext.getHumanReadableName()); - } - } - projContext.setLoadedObject(objectOld); - if (projContext.isDoReconciliation()) { - projContext.determineFullShadowFlag(objectOld); - } else { - projContext.setFullShadow(false); - } - projectionObject = objectOld; - if (ShadowUtil.isExists(objectOld.asObjectable())) { - projContext.setExists(true); - } else { - projContext.setExists(false); - if (ShadowUtil.isDead(objectOld.asObjectable())) { - projContext.markTombstone(); - } - LOGGER.debug("Foud only dead {} for projection context {}.", objectOld, projContext.getHumanReadableName()); - tombstone = true; - } - - } catch (ObjectNotFoundException ex) { - LOGGER.debug("Could not find object with oid {} for projection context {}.", projectionObjectOid, projContext.getHumanReadableName()); - // This does not mean BROKEN. The projection was there, but it gone now. - // Consistency mechanism might have kicked in and fixed the shadow. - // What we really want here is a thombstone projection or a refreshed projection. - result.muteLastSubresultError(); - projContext.setShadowExistsInRepo(false); - refreshContextAfterShadowNotFound(context, projContext, options, task, result); - - } catch (CommunicationException | SchemaException | ConfigurationException | SecurityViolationException - | RuntimeException | Error e) { - - LOGGER.warn("Problem while getting object with oid {}. Projection context {} is marked as broken: {}: {}", - projectionObjectOid, projContext.getHumanReadableName(), e.getClass().getSimpleName(), e.getMessage()); - projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - - ResourceType resourceType = projContext.getResource(); - if (resourceType == null) { - throw e; - } else { - ErrorSelectorType errorSelector = null; - if (resourceType.getConsistency() != null) { - errorSelector = resourceType.getConsistency().getConnectorErrorCriticality(); - } - if (errorSelector == null) { - if (e instanceof SchemaException) { - // 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. - return; - } else { - throw e; - } - } else { - if (CriticalityType.FATAL.equals(ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.FATAL))) { - throw e; - } else { - return; - } - } - } - } - - } - projContext.setFresh(true); - } - } else { - projectionObject = projContext.getObjectCurrent(); - if (projectionObjectOid != null) { - projContext.setExists(ShadowUtil.isExists(projectionObject.asObjectable())); - } - } - - - // Determine Resource - ResourceType resourceType = projContext.getResource(); - String resourceOid = null; - if (resourceType == null) { - if (projectionObject != null) { - ShadowType shadowType = projectionObject.asObjectable(); - resourceOid = ShadowUtil.getResourceOid(shadowType); - } else if (projContext.getResourceShadowDiscriminator() != null) { - resourceOid = projContext.getResourceShadowDiscriminator().getResourceOid(); - } else if (!tombstone) { - throw new IllegalStateException("No shadow, no discriminator and not tombstone? That won't do. Projection "+projContext.getHumanReadableName()); - } - } else { - resourceOid = resourceType.getOid(); - } - - // Determine discriminator - ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); - if (discr == null) { - if (projectionObject != null) { - ShadowType accountShadowType = projectionObject.asObjectable(); - String intent = ShadowUtil.getIntent(accountShadowType); - ShadowKindType kind = ShadowUtil.getKind(accountShadowType); - discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, accountShadowType.getTag(), tombstone); - } else { - discr = new ResourceShadowDiscriminator(null, null, null, null, tombstone); - } - projContext.setResourceShadowDiscriminator(discr); - } else { - if (tombstone) { - // We do not want to reset tombstone flag if it was set before - projContext.markTombstone(); - } - } - - // Load resource - if (resourceType == null && resourceOid != null) { - resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - projContext.setResource(resourceType); - } - - //Determine refined schema and password policies for account type - RefinedObjectClassDefinition structuralObjectClassDef = projContext.getStructuralObjectClassDefinition(); - if (structuralObjectClassDef != null) { - LOGGER.trace("Finishing loading of projection context: security policy"); - SecurityPolicyType projectionSecurityPolicy = securityHelper.locateProjectionSecurityPolicy(projContext.getStructuralObjectClassDefinition(), task, result); - LOGGER.trace("Located security policy for: {},\n {}", projContext, projectionSecurityPolicy); - projContext.setProjectionSecurityPolicy(projectionSecurityPolicy); - } else { - LOGGER.trace("No structural object class definition, skipping determining security policy"); - } - - //set limitation, e.g. if this projection context should be recomputed and processed by projector - if (ModelExecuteOptions.isLimitPropagation(context.getOptions())){ - if (context.getTriggeredResourceOid() != null){ - if (!context.getTriggeredResourceOid().equals(resourceOid)){ - projContext.setCanProject(false); - } - } - } - - setPrimaryDeltaOldValue(projContext); - } - - private boolean needToReload(LensContext context, - LensProjectionContext projContext) { - ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); - if (discr == null) { - return false; - } - // This is kind of brutal. But effective. We are reloading all higher-order dependencies - // before they are processed. This makes sure we have fresh state when they are re-computed. - // Because higher-order dependencies may have more than one projection context and the - // changes applied to one of them are not automatically reflected on on other. therefore we need to reload. - if (discr.getOrder() == 0) { - return false; - } - int executionWave = context.getExecutionWave(); - int projCtxWave = projContext.getWave(); - if (executionWave == projCtxWave - 1) { - // Reload right before its execution wave - return true; - } - return false; - } - - private void fullCheckConsistence(LensContext context) { - context.checkConsistence(); - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { - continue; - } - if (projectionContext.getResourceShadowDiscriminator() == null) { - throw new IllegalStateException("No discriminator in "+projectionContext); - } - } - } - - public void loadFullShadow(LensContext context, LensProjectionContext projCtx, String reason, Task task, OperationResult parentResult) - throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (projCtx.isFullShadow()) { - // already loaded - return; - } - if (projCtx.isAdd() && projCtx.getOid() == null) { - // nothing to load yet - return; - } - if (projCtx.isTombstone()) { - // loading is futile - return; - } - OperationResult result = parentResult.subresult(CLASS_DOT + "loadFullShadow") - .setMinor() - .build(); - FullShadowLoadedTraceType trace; - if (result.isTraced()) { - trace = new FullShadowLoadedTraceType(prismContext); - if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { - trace.setInputLensContextText(context.debugDump()); - } - trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); - result.addTrace(trace); - } else { - trace = null; - } - try { - ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator(); - if (discr != null && discr.getOrder() > 0) { - // It may be just too early to load the projection - if (LensUtil.hasLowerOrderContext(context, projCtx) && (context.getExecutionWave() < projCtx.getWave())) { - // We cannot reliably load the context now - result.addReturn(DEFAULT, "too early"); - return; - } - } - - GetOperationOptions getOptions = GetOperationOptions.createAllowNotFound(); - getOptions.setPointInTimeType(PointInTimeType.FUTURE); - if (projCtx.isDoReconciliation()) { - getOptions.setForceRefresh(true); - } - if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { - LOGGER.trace("Loading full resource object {} from provisioning - with doNotDiscover to avoid loops; reason: {}", - projCtx, reason); - // Avoid discovery loops - getOptions.setDoNotDiscovery(true); - } else { - LOGGER.trace("Loading full resource object {} from provisioning (discovery enabled), reason: {}, channel: {}", - projCtx, reason, context.getChannel()); - } - Collection> options = SelectorOptions.createCollection(getOptions); - applyAttributesToGet(projCtx, options); - try { - PrismObject objectCurrent = provisioningService - .getObject(ShadowType.class, projCtx.getOid(), options, task, result); - Validate.notNull(objectCurrent.getOid()); - // TODO: use setLoadedObject() instead? - projCtx.setObjectCurrent(objectCurrent); - projCtx.determineFullShadowFlag(objectCurrent); - if (ShadowUtil.isExists(objectCurrent.asObjectable())) { - result.addReturn(DEFAULT, "found"); - } else { - LOGGER.debug("Load of full resource object {} ended with non-existent shadow (options={})", projCtx, - getOptions); - projCtx.setExists(false); - refreshContextAfterShadowNotFound(context, projCtx, options, task, result); - result.addReturn(DEFAULT, "not found"); - } - - } catch (ObjectNotFoundException ex) { - LOGGER.debug("Load of full resource object {} ended with ObjectNotFoundException (options={})", projCtx, - getOptions); - result.muteLastSubresultError(); - projCtx.setShadowExistsInRepo(false); - refreshContextAfterShadowNotFound(context, projCtx, options, task, result); - result.addReturn(DEFAULT, "not found"); - } - - projCtx.recompute(); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loaded full resource object:\n{}", projCtx.debugDump(1)); - } - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - if (trace != null) { - if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { - trace.setOutputLensContextText(context.debugDump()); - } - trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); - } - result.computeStatusIfUnknown(); - } - } - - public void refreshContextAfterShadowNotFound(LensContext context, LensProjectionContext projCtx, Collection> options, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - if (projCtx.isDelete()){ - //this is OK, shadow was deleted, but we will continue in processing with old shadow..and set it as full so prevent from other full loading - projCtx.setFullShadow(true); - return; - } - - boolean compensated = false; - if (!GetOperationOptions.isDoNotDiscovery(SelectorOptions.findRootOptions(options))) { - // The account might have been re-created by the discovery. - // Reload focus, try to find out if there is a new matching link (and the old is gone) - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - Class focusClass = focusContext.getObjectTypeClass(); - if (FocusType.class.isAssignableFrom(focusClass)) { - LOGGER.trace("Reloading focus to check for new links"); - PrismObject focusCurrent; - try { - focusCurrent = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusContext.getOid(), null, result); - } catch (ObjectNotFoundException e) { - if (focusContext.isDelete()) { - // This may be OK. This may be later wave and the focus may be already deleted. - // Therefore let's just keep what we have. We have no way how to refresh context - // in that situation. - result.muteLastSubresultError(); - LOGGER.trace("ObjectNotFound error is not compensated (focus already deleted), setting context to tombstone"); - projCtx.markTombstone(); - return; - } else { - throw e; - } - } - FocusType focusType = (FocusType) focusCurrent.asObjectable(); - for (ObjectReferenceType linkRef: focusType.getLinkRef()) { - if (linkRef.getOid().equals(projCtx.getOid())) { - // The deleted shadow is still in the linkRef. This should not happen, but it obviously happens sometimes. - // Maybe some strange race condition? Anyway, we want a robust behavior and this linkRef should NOT be there. - // So simple remove it. - LOGGER.warn("The OID "+projCtx.getOid()+" of deleted shadow still exists in the linkRef after discovery ("+focusCurrent+"), removing it"); - ReferenceDelta unlinkDelta = prismContext.deltaFactory().reference().createModificationDelete( - FocusType.F_LINK_REF, focusContext.getObjectDefinition(), linkRef.asReferenceValue().clone()); - focusContext.swallowToSecondaryDelta(unlinkDelta); - continue; - } - boolean found = false; - for (LensProjectionContext pCtx: context.getProjectionContexts()) { - if (linkRef.getOid().equals(pCtx.getOid())) { - found = true; - break; - } - } - if (!found) { - // This link is new, it is not in the existing lens context - PrismObject newLinkRepoShadow = cacheRepositoryService.getObject(ShadowType.class, linkRef.getOid(), null, result); - if (ShadowUtil.matches(newLinkRepoShadow, projCtx.getResourceShadowDiscriminator())) { - LOGGER.trace("Found new matching link: {}, updating projection context", newLinkRepoShadow); - LOGGER.trace("Applying definition from provisioning first."); // MID-3317 - provisioningService.applyDefinition(newLinkRepoShadow, task, result); - projCtx.setObjectCurrent(newLinkRepoShadow); - projCtx.setOid(newLinkRepoShadow.getOid()); - projCtx.recompute(); - compensated = true; - break; - } else { - LOGGER.trace("Found new link: {}, but skipping it because it does not match the projection context", newLinkRepoShadow); - } - } - } - } - } - - } - - if (!compensated) { - LOGGER.trace("ObjectNotFound error is not compensated, setting context to tombstone"); - projCtx.markTombstone(); - } - } - - private void applyAttributesToGet(LensProjectionContext projCtx, Collection> options) throws SchemaException { - if ( !LensUtil.isPasswordReturnedByDefault(projCtx) - && LensUtil.needsFullShadowForCredentialProcessing(projCtx)) { - options.add(SelectorOptions.create(prismContext.toUniformPath(SchemaConstants.PATH_PASSWORD_VALUE), GetOperationOptions.createRetrieve())); - } - } - - public void reloadSecurityPolicyIfNeeded(@NotNull LensContext context, - @NotNull LensFocusContext focusContext, Task task, OperationResult result) - throws ExpressionEvaluationException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException { - if (focusContext.hasOrganizationalChange()) { - loadSecurityPolicy(context, true, task, result); - } - } - - private void loadSecurityPolicy(LensContext context, - Task task, OperationResult result) throws ExpressionEvaluationException, - SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - loadSecurityPolicy(context, false, task, result); - } - - @SuppressWarnings("unchecked") - private void loadSecurityPolicy(LensContext context, boolean forceReload, - Task task, OperationResult result) throws ExpressionEvaluationException, - SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext genericFocusContext = context.getFocusContext(); - if (genericFocusContext == null || !genericFocusContext.represents(FocusType.class)) { - LOGGER.trace("Skipping load of security policy because focus is not of FocusType"); - return; - } - LensFocusContext focusContext = (LensFocusContext) genericFocusContext; - PrismObject focus = focusContext.getObjectAny(); - SecurityPolicyType globalSecurityPolicy = determineAndSetGlobalSecurityPolicy(context, focus, task, result); - SecurityPolicyType focusSecurityPolicy = determineAndSetFocusSecurityPolicy(focusContext, focus, globalSecurityPolicy, - forceReload, task, result); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Security policy:\n Global:\n{}\n Focus:\n{}", - globalSecurityPolicy.asPrismObject().debugDump(2), - focusSecurityPolicy==null?null:focusSecurityPolicy.asPrismObject().debugDump(2)); - } else { - LOGGER.debug("Security policy: global: {}, focus: {}", globalSecurityPolicy, focusSecurityPolicy); - } - } - - @NotNull - private SecurityPolicyType determineAndSetGlobalSecurityPolicy(LensContext context, - PrismObject focus, Task task, OperationResult result) - throws CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - SecurityPolicyType existingPolicy = context.getGlobalSecurityPolicy(); - if (existingPolicy != null) { - return existingPolicy; - } else { - SecurityPolicyType loadedPolicy = securityHelper.locateGlobalSecurityPolicy(focus, context.getSystemConfiguration(), - task, result); - SecurityPolicyType resultingPolicy; - if (loadedPolicy != null) { - resultingPolicy = loadedPolicy; - } else { - // use empty policy to avoid repeated lookups - resultingPolicy = new SecurityPolicyType(); - } - context.setGlobalSecurityPolicy(resultingPolicy); - return resultingPolicy; - } - } - - private SecurityPolicyType determineAndSetFocusSecurityPolicy(LensFocusContext focusContext, - PrismObject focus, SecurityPolicyType globalSecurityPolicy, boolean forceReload, Task task, - OperationResult result) throws SchemaException { - SecurityPolicyType existingPolicy = focusContext.getSecurityPolicy(); - if (existingPolicy != null && !forceReload) { - return existingPolicy; - } else { - SecurityPolicyType loadedPolicy = securityHelper.locateFocusSecurityPolicy(focus, task, result); - SecurityPolicyType resultingPolicy; - if (loadedPolicy != null) { - resultingPolicy = loadedPolicy; - } else { - // Not very clean. In fact we should store focus security policy separate from global - // policy to avoid confusion. But need to do this to fix MID-4793 and backport the fix. - // Therefore avoiding big changes. TODO: fix properly later - resultingPolicy = globalSecurityPolicy; - } - focusContext.setSecurityPolicy(resultingPolicy); - return resultingPolicy; - } - } -} +/* + * 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.projector; + +import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; +import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; +import static com.evolveum.midpoint.schema.result.OperationResult.DEFAULT; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.internals.InternalsConfig; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; +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.LensObjectDeltaOperation; +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.security.SecurityHelper; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +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.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.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +/** + * Context loader loads the missing parts of the context. The context enters the projector with just the minimum information. + * Context loader gets missing data such as accounts. It gets them from the repository or provisioning as necessary. It follows + * the account links in focus (linkRef) and focus deltas. + * + * @author Radovan Semancik + * + */ +@Component +public class ContextLoader { + + @Autowired + @Qualifier("cacheRepositoryService") + private transient RepositoryService cacheRepositoryService; + + @Autowired private SystemObjectCache systemObjectCache; + @Autowired private ArchetypeManager archetypeManager; + @Autowired private ProvisioningService provisioningService; + @Autowired private PrismContext prismContext; + @Autowired private SecurityHelper securityHelper; + @Autowired private ClockworkMedic medic; + + private static final Trace LOGGER = TraceManager.getTrace(ContextLoader.class); + + public static final String CLASS_DOT = ContextLoader.class.getName() + "."; + private static final String OPERATION_LOAD = CLASS_DOT + "load"; + private static final String OPERATION_LOAD_PROJECTION = CLASS_DOT + "loadProjection"; + + public void load(LensContext context, String activityDescription, + Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + + context.checkAbortRequested(); + + context.recompute(); + + OperationResult result = parentResult.createMinorSubresult(OPERATION_LOAD); + ProjectorComponentTraceType trace; + if (result.isTraced()) { + trace = new ProjectorComponentTraceType(prismContext); + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } + trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); + result.addTrace(trace); + } else { + trace = null; + } + + try { + + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + preprocessProjectionContext(context, projectionContext, task, result); + } + + if (consistencyChecks) context.checkConsistence(); + + determineFocusContext(context, task, result); + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + + context.recomputeFocus(); + + loadFromSystemConfig(context, task, result); + + if (FocusType.class.isAssignableFrom(context.getFocusClass())) { + // this also removes the accountRef deltas + //noinspection unchecked + loadLinkRefs((LensContext)context, task, result); + LOGGER.trace("loadLinkRefs done"); + } + + // Some cleanup + if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().isModify() && focusContext.getPrimaryDelta().isEmpty()) { + focusContext.setPrimaryDelta(null); + } + + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + if (projectionContext.getSynchronizationIntent() != null) { + // Accounts with explicitly set intent are never rotten. These are explicitly requested actions + // if they fail then they really should fail. + projectionContext.setFresh(true); + } + } + + setPrimaryDeltaOldValue(focusContext); + + } else { + // Projection contexts are not rotten in this case. There is no focus so there is no way to refresh them. + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + projectionContext.setFresh(true); + } + } + + removeRottenContexts(context); + + if (consistencyChecks) context.checkConsistence(); + + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + context.checkAbortRequested(); + // TODO: not perfect. Practically, we want loadProjection operation to contain all the projection + // results. But for that we would need code restructure. + OperationResult projectionResult = result.createMinorSubresult(OPERATION_LOAD_PROJECTION); + try { + finishLoadOfProjectionContext(context, projectionContext, task, projectionResult); + } catch (Throwable e) { + projectionResult.recordFatalError(e); + throw e; + } + projectionResult.computeStatus(); + } + + if (consistencyChecks) context.checkConsistence(); + + context.recompute(); + + if (consistencyChecks) { + fullCheckConsistence(context); + } + + medic.traceContext(LOGGER, activityDescription, "after load", false, context, false); + + result.computeStatusComposite(); + + } catch (Throwable e) { + result.recordFatalError(e); + throw e; + } finally { + if (trace != null) { + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } + trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); + } + } + } + + + /** + * Removes projection contexts that are not fresh. + * These are usually artifacts left after the context reload. E.g. an account that used to be linked to a user before + * but was removed in the meantime. + */ + private void removeRottenContexts(LensContext context) { + Iterator projectionIterator = context.getProjectionContextsIterator(); + while (projectionIterator.hasNext()) { + LensProjectionContext projectionContext = projectionIterator.next(); + if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { + // We must never remove contexts with primary delta. Even though it fails later on. + // What the user wishes should be done (or at least attempted) regardless of the consequences. + // Vox populi vox dei + continue; + } + if (projectionContext.getWave() >= context.getExecutionWave()) { + // We must not remove context from this and later execution waves. These haven't had the + // chance to be executed yet + continue; + } + ResourceShadowDiscriminator discr = projectionContext.getResourceShadowDiscriminator(); + if (discr != null && discr.getOrder() > 0) { + // HACK never rot higher-order context. TODO: check if lower-order context is rotten, the also rot this one + continue; + } + if (!projectionContext.isFresh()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Removing rotten context {}", projectionContext.getHumanReadableName()); + } + + if (projectionContext.isToBeArchived()) { + context.getHistoricResourceObjects().add(projectionContext.getResourceShadowDiscriminator()); + } + + List> executedDeltas = projectionContext.getExecutedDeltas(); + context.getRottenExecutedDeltas().addAll(executedDeltas); + projectionIterator.remove(); + } + } + } + + + /** + * Make sure that the projection context is loaded as appropriate. + */ + public void makeSureProjectionIsLoaded(LensContext context, + LensProjectionContext projectionContext, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + preprocessProjectionContext(context, projectionContext, task, result); + finishLoadOfProjectionContext(context, projectionContext, task, result); + } + + /** + * Make sure that the context is OK and consistent. It means that is has a resource, it has correctly processed + * discriminator, etc. + */ + private void preprocessProjectionContext(LensContext context, + LensProjectionContext projectionContext, Task task, OperationResult result) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (!ShadowType.class.isAssignableFrom(projectionContext.getObjectTypeClass())) { + return; + } + String resourceOid = null; + boolean isThombstone = false; + ShadowKindType kind = ShadowKindType.ACCOUNT; + String intent = null; + String tag = null; + int order = 0; + ResourceShadowDiscriminator rsd = projectionContext.getResourceShadowDiscriminator(); + if (rsd != null) { + resourceOid = rsd.getResourceOid(); + isThombstone = rsd.isTombstone(); + kind = rsd.getKind(); + intent = rsd.getIntent(); + tag = rsd.getTag(); + order = rsd.getOrder(); + } + if (resourceOid == null && projectionContext.getObjectCurrent() != null) { + resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectCurrent().asObjectable()); + } + if (resourceOid == null && projectionContext.getObjectNew() != null) { + resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectNew().asObjectable()); + } + // We still may not have resource OID here. E.g. in case of the delete when the account is not loaded yet. It is + // perhaps safe to skip this. It will be sorted out later. + + if (resourceOid != null) { + if (intent == null && projectionContext.getObjectNew() != null) { + ShadowType shadowNewType = projectionContext.getObjectNew().asObjectable(); + kind = ShadowUtil.getKind(shadowNewType); + intent = ShadowUtil.getIntent(shadowNewType); + tag = shadowNewType.getTag(); + } + ResourceType resource = projectionContext.getResource(); + if (resource == null) { + resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + projectionContext.setResource(resource); + } + String refinedIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); + rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, isThombstone); + rsd.setOrder(order); + projectionContext.setResourceShadowDiscriminator(rsd); + } + if (projectionContext.getOid() == null && rsd != null && rsd.getOrder() != 0) { + // Try to determine OID from lower-order contexts + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (rsd.equivalent(aDiscr) && aProjCtx.getOid() != null) { + projectionContext.setOid(aProjCtx.getOid()); + break; + } + } + } + } + + /** + * try to load focus context from oid, delta, projections (e.g. by determining account owners) + */ + public void determineFocusContext(LensContext context, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + + OperationResult result = parentResult.subresult(CLASS_DOT + "determineFocusContext") + .setMinor() + .build(); + FocusLoadedTraceType trace; + if (result.isTraced()) { + trace = new FocusLoadedTraceType(prismContext); + if (result.isTracingNormal(FocusLoadedTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } + trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); + result.addTrace(trace); + } else { + trace = null; + } + LensFocusContext focusContext = context.getFocusContext(); + try { + if (focusContext == null) { + focusContext = determineFocusContextFromProjections(context, result); + } + + if (focusContext == null) { + result.addReturnComment("Nothing to load"); + return; + } + + // Make sure that we RELOAD the focus object if the context is not fresh + // the focus may have changed in the meantime + if (focusContext.getObjectCurrent() != null && focusContext.isFresh()) { + result.addReturnComment("Already loaded"); + return; + } + ObjectDelta objectDelta = focusContext.getDelta(); + if (objectDelta != null && objectDelta.isAdd() && focusContext.getExecutedDeltas().isEmpty()) { + //we're adding the focal object. No need to load it, it is in the delta + focusContext.setFresh(true); + result.addReturnComment("Obtained from delta"); + return; + } + if (focusContext.getObjectCurrent() != null && objectDelta != null && objectDelta.isDelete()) { + // do not reload if the delta is delete. the reload will most likely fail anyway + // but DO NOT set the fresh flag in this case, it may be misleading + result.addReturnComment("Not loading as delta is DELETE"); + return; + } + + String focusOid = focusContext.getOid(); + if (StringUtils.isBlank(focusOid)) { + throw new IllegalArgumentException("No OID in primary focus delta"); + } + + PrismObject object; + if (ObjectTypes.isClassManagedByProvisioning(focusContext.getObjectTypeClass())) { + object = provisioningService.getObject(focusContext.getObjectTypeClass(), focusOid, + SelectorOptions.createCollection(GetOperationOptions.createNoFetch()), task, result); + setLoadedFocusInTrace(object, trace); + result.addReturnComment("Loaded via provisioning"); + } else { + + // Always load a complete object here, including the not-returned-by-default properties. + // This is temporary measure to make sure that the mappings will have all they need. + // See MID-2635 + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE)); + object = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusOid, options, result); + setLoadedFocusInTrace(object, trace); + result.addReturnComment("Loaded from repository"); + } + + focusContext.setLoadedObject(object); + focusContext.setFresh(true); + LOGGER.trace("Focal object loaded: {}", object); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + if (trace != null) { + if (result.isTracingNormal(FocusLoadedTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } + trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); + } + result.computeStatusIfUnknown(); + } + } + + private void setLoadedFocusInTrace(PrismObject object, FocusLoadedTraceType trace) { + if (trace != null) { + trace.setFocusLoadedRef(ObjectTypeUtil.createObjectRefWithFullObject(object, prismContext)); + } + } + + private LensFocusContext determineFocusContextFromProjections(LensContext context, OperationResult result) { + String focusOid = null; + LensProjectionContext projectionContextThatYeildedFocusOid = null; + PrismObject focusOwner = null; + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + String projectionOid = projectionContext.getOid(); + if (projectionOid != null) { + PrismObject shadowOwner = cacheRepositoryService.searchShadowOwner(projectionOid, + SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), + result); + if (shadowOwner != null) { + if (focusOid == null || focusOid.equals(shadowOwner.getOid())) { + focusOid = shadowOwner.getOid(); + //noinspection unchecked + focusOwner = (PrismObject) shadowOwner; + projectionContextThatYeildedFocusOid = projectionContext; + } else { + throw new IllegalArgumentException("The context does not have explicit focus. Attempt to determine focus failed because two " + + "projections points to different foci: "+projectionContextThatYeildedFocusOid+"->"+focusOid+"; "+ + projectionContext+"->"+shadowOwner); + } + } + } + } + + if (focusOid != null) { + LensFocusContext focusCtx = context.getOrCreateFocusContext(focusOwner.getCompileTimeClass()); + focusCtx.setOid(focusOid); + return focusCtx; + } + + return null; + } + + private void setPrimaryDeltaOldValue(LensElementContext ctx) { + if (ctx.getPrimaryDelta() != null && ctx.getObjectOld() != null && ctx.isModify()) { + boolean freezeAfterChange; + if (ctx.getPrimaryDelta().isImmutable()) { + ctx.setPrimaryDelta(ctx.getPrimaryDelta().clone()); + freezeAfterChange = true; + } else { + freezeAfterChange = false; + } + for (ItemDelta itemDelta: ctx.getPrimaryDelta().getModifications()) { + LensUtil.setDeltaOldValue(ctx, itemDelta); + } + if (freezeAfterChange) { + ctx.getPrimaryDelta().freeze(); + } + } + } + + private void loadFromSystemConfig(LensContext context, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException { + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + if (systemConfiguration == null) { + // This happens in some tests. And also during first startup. + return; + } + context.setSystemConfiguration(systemConfiguration); + SystemConfigurationType systemConfigurationType = systemConfiguration.asObjectable(); + + if (context.getFocusContext() != null) { + if (context.getFocusContext().getArchetypePolicyType() == null) { + ArchetypePolicyType archetypePolicy = determineArchetypePolicy(context, task, result); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Selected archetype policy:\n{}", + archetypePolicy==null?null:archetypePolicy.asPrismContainerValue().debugDump(1)); + } + context.getFocusContext().setArchetypePolicyType(archetypePolicy); + } + } + + if (context.getFocusTemplate() == null) { + // TODO is the nullity check needed here? + setFocusTemplate(context, result); + } + + if (context.getAccountSynchronizationSettings() == null) { + ProjectionPolicyType globalAccountSynchronizationSettings = systemConfigurationType.getGlobalAccountSynchronizationSettings(); + LOGGER.trace("Applying globalAccountSynchronizationSettings to context: {}", globalAccountSynchronizationSettings); + context.setAccountSynchronizationSettings(globalAccountSynchronizationSettings); + } + + loadSecurityPolicy(context, task, result); + } + + private ArchetypePolicyType determineArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { + PrismObject systemConfiguration = context.getSystemConfiguration(); + if (systemConfiguration == null) { + return null; + } + if (context.getFocusContext() == null) { + return null; + } + PrismObject object = context.getFocusContext().getObjectAny(); + String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); + return archetypeManager.determineArchetypePolicy(object, explicitArchetypeOid, result); + } + + public ArchetypeType updateArchetype(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { + PrismObject systemConfiguration = context.getSystemConfiguration(); + if (systemConfiguration == null) { + return null; + } + if (context.getFocusContext() == null) { + return null; + } + + PrismObject object = context.getFocusContext().getObjectAny(); + + String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); + PrismObject archetype = archetypeManager.determineArchetype(object, explicitArchetypeOid, result); + ArchetypeType archetypeType = null; + if (archetype != null) { + archetypeType = archetype.asObjectable(); + } + + context.getFocusContext().setArchetype(archetypeType); + + return archetypeType; + } + + public void updateArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { + if (context.getFocusContext() == null) { + return; + } + ArchetypePolicyType newArchetypePolicy = determineArchetypePolicy(context, task, result); + if (newArchetypePolicy != context.getFocusContext().getArchetypePolicyType()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Changed policy configuration because of changed subtypes:\n{}", + newArchetypePolicy==null?null:newArchetypePolicy.asPrismContainerValue().debugDump(1)); + } + context.getFocusContext().setArchetypePolicyType(newArchetypePolicy); + } + } + + // expects that object policy configuration is already set in focusContext + public void setFocusTemplate(LensContext context, OperationResult result) + throws ObjectNotFoundException, SchemaException { + + // 1. When this method is called after inbound processing, we might want to change the existing template + // (because e.g. subtype or archetype was determined and we want to move from generic to more specific template). + // 2. On the other hand, if focus template is set up explicitly from the outside (e.g. in synchronization section) + // we probably do not want to change it here. + if (context.getFocusTemplate() != null && context.isFocusTemplateExternallySet()) { + return; + } + + String currentOid = context.getFocusTemplate() != null ? context.getFocusTemplate().getOid() : null; + String newOid; + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + newOid = null; + } else { + ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); + if (archetypePolicy == null) { + LOGGER.trace("No default object template (no policy)"); + newOid = null; + } else { + ObjectReferenceType templateRef = archetypePolicy.getObjectTemplateRef(); + if (templateRef == null) { + LOGGER.trace("No default object template (no templateRef)"); + newOid = null; + } else { + newOid = templateRef.getOid(); + } + } + } + + LOGGER.trace("current focus template OID = {}, new = {}", currentOid, newOid); + if (!java.util.Objects.equals(currentOid, newOid)) { + ObjectTemplateType template; + if (newOid != null) { + template = cacheRepositoryService.getObject(ObjectTemplateType.class, newOid, null, result).asObjectable(); + } else { + template = null; + } + context.setFocusTemplate(template); + } + } + + private void loadLinkRefs(LensContext context, Task task, OperationResult result) throws ObjectNotFoundException, + SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + // Nothing to load + return; + } + + LOGGER.trace("loadLinkRefs starting"); + + PrismObject focusCurrent = focusContext.getObjectCurrent(); + if (focusCurrent != null) { + loadLinkRefsFromFocus(context, focusCurrent, task, result); + LOGGER.trace("loadLinkRefsFromFocus done"); + } + + if (consistencyChecks) context.checkConsistence(); + + loadLinkRefsFromDelta(context, focusCurrent, focusContext, task, result); + LOGGER.trace("loadLinkRefsFromDelta done"); + + if (consistencyChecks) context.checkConsistence(); + + loadProjectionContextsSync(context, task, result); + LOGGER.trace("loadProjectionContextsSync done"); + + if (consistencyChecks) context.checkConsistence(); + } + + /** + * Does not overwrite existing account contexts, just adds new ones. + */ + private void loadLinkRefsFromFocus(LensContext context, PrismObject focus, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); + if (linkRef == null) { + return; + } + for (PrismReferenceValue linkRefVal : linkRef.getValues()) { + String oid = linkRefVal.getOid(); + if (StringUtils.isBlank(oid)) { + LOGGER.trace("Null or empty OID in link reference {} in:\n{}", linkRef, + focus.debugDump(1)); + throw new SchemaException("Null or empty OID in link reference in " + focus); + } + LensProjectionContext existingAccountContext = findAccountContext(oid, context); + +// if (!canBeLoaded(context, existingAccountContext)) { +// continue; +// } + + if (existingAccountContext != null) { + // TODO: do we need to reload the account inside here? yes we need + + existingAccountContext.setFresh(true); + continue; + } + PrismObject shadow; + //noinspection unchecked + PrismObject shadowFromLink = linkRefVal.getObject(); + if (shadowFromLink == null) { + GetOperationOptions rootOpts; + if (context.isDoReconciliationForAllProjections()) { + rootOpts = GetOperationOptions.createForceRetry(); + } else { + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + rootOpts = GetOperationOptions.createNoFetch(); + rootOpts.setPointInTimeType(PointInTimeType.FUTURE); + } + + Collection> options = SelectorOptions.createCollection(rootOpts); + LOGGER.trace("Loading shadow {} from linkRef, options={}", oid, options); + try { + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + } catch (ObjectNotFoundException e) { + // Broken accountRef. We need to mark it for deletion + LensProjectionContext projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); + projectionContext.setFresh(true); + projectionContext.setExists(false); + projectionContext.setShadowExistsInRepo(false); + OperationResult getObjectSubresult = result.getLastSubresult(); + getObjectSubresult.setErrorsHandled(); + continue; + } + } else { + shadow = shadowFromLink; + // Make sure it has a proper definition. This may come from outside of the model. + provisioningService.applyDefinition(shadow, task, result); + } + LensProjectionContext projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setFresh(true); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); + if (ShadowUtil.isDead(shadow.asObjectable())) { + projectionContext.markTombstone(); + LOGGER.trace("Loading dead shadow {} for projection {}.", shadow, projectionContext.getHumanReadableName()); + continue; + } + if (context.isDoReconciliationForAllProjections()) { + projectionContext.setDoReconciliation(true); + } + if (projectionContext.isDoReconciliation()) { + // Do not load old account now. It will get loaded later in the + // reconciliation step. + continue; + } + projectionContext.setLoadedObject(shadow); + } + } + + private void loadLinkRefsFromDelta(LensContext context, PrismObject focus, + LensFocusContext focusContext, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + + ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); + if (focusPrimaryDelta == null) { + return; + } + + ReferenceDelta linkRefDelta; + if (focusPrimaryDelta.getChangeType() == ChangeType.ADD) { + PrismReference linkRef = focusPrimaryDelta.getObjectToAdd().findReference( + FocusType.F_LINK_REF); + if (linkRef == null) { + // Adding new focus with no linkRef -> nothing to do + return; + } + linkRefDelta = linkRef.createDelta(FocusType.F_LINK_REF); + linkRefDelta.addValuesToAdd(PrismValueCollectionsUtil.cloneValues(linkRef.getValues())); + } else if (focusPrimaryDelta.getChangeType() == ChangeType.MODIFY) { + linkRefDelta = focusPrimaryDelta.findReferenceModification(FocusType.F_LINK_REF); + if (linkRefDelta == null) { + return; + } + } else { + // delete, all existing account are already marked for delete + return; + } + + if (linkRefDelta.isReplace()) { + // process "replace" by distributing values to delete and add + linkRefDelta = (ReferenceDelta) linkRefDelta.clone(); + PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); + linkRefDelta.distributeReplace(linkRef == null ? null : linkRef.getValues()); + } + + if (linkRefDelta.getValuesToAdd() != null) { + for (PrismReferenceValue refVal : linkRefDelta.getValuesToAdd()) { + String oid = refVal.getOid(); + LensProjectionContext projectionContext = null; + PrismObject shadow = null; + boolean isCombinedAdd = false; + if (oid == null) { + // Adding new account + shadow = refVal.getObject(); + if (shadow == null) { + throw new SchemaException("Null or empty OID in account reference " + refVal + " in " + + focus); + } + provisioningService.applyDefinition(shadow, task, result); + if (consistencyChecks) ShadowUtil.checkConsistence(shadow, "account from "+linkRefDelta); + // Check for conflicting change + projectionContext = LensUtil.getProjectionContext(context, shadow, provisioningService, prismContext, task, result); + if (projectionContext != null) { + // There is already existing context for the same discriminator. Tolerate this only if + // the deltas match. It is an error otherwise. + ObjectDelta primaryDelta = projectionContext.getPrimaryDelta(); + if (primaryDelta == null) { + throw new SchemaException("Attempt to add "+shadow+" to a focus that already contains "+ + projectionContext.getHumanReadableKind()+" of type '"+ + projectionContext.getResourceShadowDiscriminator().getIntent()+"' on "+projectionContext.getResource()); + } + if (!primaryDelta.isAdd()) { + throw new SchemaException("Conflicting changes in the context. " + + "Add of linkRef in the focus delta with embedded object conflicts with explicit delta "+primaryDelta); + } + if (!shadow.equals(primaryDelta.getObjectToAdd())) { + throw new SchemaException("Conflicting changes in the context. " + + "Add of linkRef in the focus delta with embedded object is not adding the same object as explicit delta "+primaryDelta); + } + } else { + // Create account context from embedded object + projectionContext = createProjectionContext(context, shadow, task, result); + } + // This is a new account that is to be added. So it should + // go to account primary delta + ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + projectionContext.setFullShadow(true); + projectionContext.setExists(false); + isCombinedAdd = true; + } else { + // We have OID. This is either linking of existing account or + // add of new account + // therefore check for account existence to decide + try { + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); + rootOpts.setPointInTimeType(PointInTimeType.FUTURE); + Collection> options = SelectorOptions.createCollection(rootOpts); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + // Create account context from retrieved object + projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setLoadedObject(shadow); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); + } catch (ObjectNotFoundException e) { + if (refVal.getObject() == null) { + // account does not exist, no composite account in + // ref -> this is really an error + throw e; + } else { + // New account (with OID) + result.muteLastSubresultError(); + shadow = refVal.getObject(); + if (!shadow.hasCompleteDefinition()) { + provisioningService.applyDefinition(shadow, task, result); + } + // Create account context from embedded object + projectionContext = createProjectionContext(context, shadow, task, result); + ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + projectionContext.setFullShadow(true); + projectionContext.setExists(false); + projectionContext.setShadowExistsInRepo(false); + isCombinedAdd = true; + } + } + } + if (context.isDoReconciliationForAllProjections() && !isCombinedAdd) { + projectionContext.setDoReconciliation(true); + } + projectionContext.setFresh(true); + } + } + + if (linkRefDelta.getValuesToDelete() != null) { + for (PrismReferenceValue refVal : linkRefDelta.getValuesToDelete()) { + String oid = refVal.getOid(); + LensProjectionContext projectionContext = null; + PrismObject shadow = null; + if (oid == null) { + throw new SchemaException("Cannot delete account ref without an oid in " + focus); + } else { + try { + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + // Create account context from retrieved object + projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setLoadedObject(shadow); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); + } catch (ObjectNotFoundException e) { + try{ + // Broken accountRef. We need to try again with raw options, because the error should be thrown because of non-existent resource + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); + projectionContext.setFresh(true); + projectionContext.setExists(false); + projectionContext.setShadowExistsInRepo(false); + OperationResult getObjectSubresult = result.getLastSubresult(); + getObjectSubresult.setErrorsHandled(); + } catch (ObjectNotFoundException ex){ + // This is still OK. It means deleting an accountRef + // that points to non-existing object + // just log a warning + LOGGER.warn("Deleting accountRef of " + focus + " that points to non-existing OID " + + oid); + } + + } + } + if (projectionContext != null) { + if (refVal.getObject() == null) { + projectionContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); + } else { + projectionContext.setSynchronizationIntent(SynchronizationIntent.DELETE); + ObjectDelta accountPrimaryDelta = shadow.createDeleteDelta(); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + } + projectionContext.setFresh(true); + } + + } + } + + // remove the accountRefs without oid. These will get into the way now. + // The accounts + // are in the context now and will be linked at the end of the process + // (it they survive the policy) + // We need to make sure this happens on the real primary focus delta + + ObjectDelta primaryDeltaToUpdate; + if (focusPrimaryDelta.isImmutable()) { + primaryDeltaToUpdate = focusPrimaryDelta.clone(); + focusContext.setPrimaryDelta(primaryDeltaToUpdate); + } else { + primaryDeltaToUpdate = focusPrimaryDelta; + } + + if (primaryDeltaToUpdate.getChangeType() == ChangeType.ADD) { + primaryDeltaToUpdate.getObjectToAdd().removeReference(FocusType.F_LINK_REF); + } else if (primaryDeltaToUpdate.getChangeType() == ChangeType.MODIFY) { + primaryDeltaToUpdate.removeReferenceModification(FocusType.F_LINK_REF); + } + // It is little bit questionable whether we need to make primary delta immutable. It makes some sense, but I am not sure. + // Note that (as a side effect) this can make "focus new" immutable as well, in the case of ADD delta. + primaryDeltaToUpdate.freeze(); + } + + private void loadProjectionContextsSync(LensContext context, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + for (LensProjectionContext projCtx : context.getProjectionContexts()) { + if (projCtx.isFresh() && projCtx.getObjectCurrent() != null) { + // already loaded + continue; + } + ObjectDelta syncDelta = projCtx.getSyncDelta(); + if (syncDelta != null) { + if (projCtx.isDoReconciliation()) { + // Do not load old account now. It will get loaded later in the + // reconciliation step. Just mark it as fresh. + projCtx.setFresh(true); + continue; + } + String oid = syncDelta.getOid(); + PrismObject shadow = null; + + if (syncDelta.getChangeType() == ChangeType.ADD) { + shadow = syncDelta.getObjectToAdd().clone(); + projCtx.setLoadedObject(shadow); + projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); + + } else { + + if (oid == null) { + throw new IllegalArgumentException("No OID in sync delta in " + projCtx); + } + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + GetOperationOptions option = GetOperationOptions.createNoFetch(); + option.setDoNotDiscovery(true); + option.setPointInTimeType(PointInTimeType.FUTURE); + Collection> options = SelectorOptions.createCollection(option); + + try { + + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + + } catch (ObjectNotFoundException e) { + LOGGER.trace("Loading shadow {} from sync delta failed: not found", oid); + projCtx.setExists(false); + projCtx.setObjectCurrent(null); + projCtx.setShadowExistsInRepo(false); + } + + // We will not set old account if the delta is delete. The + // account does not really exists now. + // (but the OID and resource will be set from the repo + // shadow) + if (syncDelta.getChangeType() == ChangeType.DELETE) { + projCtx.markTombstone(); + } else if (shadow != null) { + syncDelta.applyTo(shadow); + projCtx.setLoadedObject(shadow); + projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); + } + } + + // Make sure OID is set correctly + projCtx.setOid(oid); + // Make sure that resource is also resolved + if (projCtx.getResource() == null && shadow != null) { + String resourceOid = ShadowUtil.getResourceOid(shadow.asObjectable()); + if (resourceOid == null) { + throw new IllegalArgumentException("No resource OID in " + shadow); + } + ResourceType resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + projCtx.setResource(resourceType); + } + projCtx.setFresh(true); + } + } + } + +// private boolean canBeLoaded(LensContext context, LensProjectionContext projCtx){ +// if (QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_DISCOVERY).equals(context.getChannel()) && projCtx == null && ModelExecuteOptions.isLimitPropagation(context.getOptions())) { +// // avoid to create projection context if the channel which +// // triggered this operation is discovery..we need only +// // projection context of discovered shadow +// return false; +// } +// return true; +// } + + private LensProjectionContext getOrCreateAccountContext(LensContext context, + PrismObject projection, Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + ShadowType shadowType = projection.asObjectable(); + String resourceOid = ShadowUtil.getResourceOid(shadowType); + if (resourceOid == null) { + throw new SchemaException("The " + projection + " has null resource reference OID"); + } + + LensProjectionContext projectionContext = context.findProjectionContextByOid(shadowType.getOid()); + + if (projectionContext == null) { + String intent = ShadowUtil.getIntent(shadowType); + ShadowKindType kind = ShadowUtil.getKind(shadowType); + ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + intent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); + boolean thombstone = false; + if (ShadowUtil.isDead(shadowType)) { + thombstone = true; + } + ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, intent, shadowType.getTag(), thombstone); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + + if (projectionContext.getOid() == null) { + projectionContext.setOid(projection.getOid()); + } else if (projection.getOid() != null && !projectionContext.getOid().equals(projection.getOid())) { + // Conflict. We have existing projection and another project that is added (with the same discriminator). + // Chances are that the old object is already deleted (e.g. during rename). So let's be + // slightly inefficient here and check for existing shadow existence + try { + GetOperationOptions rootOpt = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); + rootOpt.setDoNotDiscovery(true); + Collection> opts = SelectorOptions.createCollection(rootOpt); + LOGGER.trace("Projection conflict detected, existing: {}, new {}", projectionContext.getOid(), projection.getOid()); + PrismObject existingShadow = provisioningService.getObject(ShadowType.class, projectionContext.getOid(), opts, task, result); + // Maybe it is the other way around + try { + PrismObject newShadow = provisioningService.getObject(ShadowType.class, projection.getOid(), opts, task, result); + // Obviously, two projections with the same discriminator exists + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Projection {} already exists in context\nExisting:\n{}\nNew:\n{}", rsd, + existingShadow.debugDump(1), newShadow.debugDump(1)); + } + if (!ShadowUtil.isDead(newShadow.asObjectable())) { + throw new PolicyViolationException("Projection "+rsd+" already exists in context (existing "+existingShadow+", new "+projection); + } + // Dead shadow. This is somehow expected, fix it and we can go on + rsd.setTombstone(true); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + projectionContext.setExists(ShadowUtil.isExists(newShadow.asObjectable())); + projectionContext.setFullShadow(false); + } catch (ObjectNotFoundException e) { + // This is somehow expected, fix it and we can go on + result.muteLastSubresultError(); + // We have to create new context in this case, but it has to have thumbstone set + rsd.setTombstone(true); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + // We have to mark it as dead right now, otherwise the uniqueness check may fail + markShadowDead(projection.getOid(), result); + projectionContext.setShadowExistsInRepo(false); + } + } catch (ObjectNotFoundException e) { + // This is somehow expected, fix it and we can go on + result.muteLastSubresultError(); + String shadowOid = projectionContext.getOid(); + projectionContext.getResourceShadowDiscriminator().setTombstone(true); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + projectionContext.setShadowExistsInRepo(false); + // We have to mark it as dead right now, otherwise the uniqueness check may fail + markShadowDead(shadowOid, result); + } + } + } + return projectionContext; + } + + private void markShadowDead(String oid, OperationResult result) { + if (oid == null) { + // nothing to mark + return; + } + Collection> modifications = MiscSchemaUtil.createCollection( + prismContext.deltaFactory().property().createReplaceDelta(prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class), + ShadowType.F_DEAD, true)); + try { + cacheRepositoryService.modifyObject(ShadowType.class, oid, modifications, result); + // TODO report to task? + } catch (ObjectNotFoundException e) { + // Done already + result.muteLastSubresultError(); + } catch (ObjectAlreadyExistsException | SchemaException e) { + // Should not happen + throw new SystemException(e.getMessage(), e); + } + } + + + private LensProjectionContext createProjectionContext(LensContext context, + PrismObject account, Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ShadowType shadowType = account.asObjectable(); + String resourceOid = ShadowUtil.getResourceOid(shadowType); + if (resourceOid == null) { + throw new SchemaException("The " + account + " has null resource reference OID"); + } + String intent = ShadowUtil.getIntent(shadowType); + ShadowKindType kind = ShadowUtil.getKind(shadowType); + ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + String accountIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); + ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, accountIntent, shadowType.getTag(), false); + LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); + if (accountSyncContext != null) { + throw new SchemaException("Attempt to add "+account+" to a focus that already contains projection of type '"+accountIntent+"' on "+resource); + } + accountSyncContext = context.createProjectionContext(rsd); + accountSyncContext.setResource(resource); + accountSyncContext.setOid(account.getOid()); + return accountSyncContext; + } + + private LensProjectionContext findAccountContext(String accountOid, LensContext context) { + for (LensProjectionContext accContext : context.getProjectionContexts()) { + if (accountOid.equals(accContext.getOid())) { + return accContext; + } + } + + return null; + } + + private LensProjectionContext getOrCreateEmptyThombstoneProjectionContext(LensContext context, + String missingShadowOid) { + LensProjectionContext projContext = context.findProjectionContextByOid(missingShadowOid); + if (projContext == null) { + projContext = context.createProjectionContext(null); + projContext.setOid(missingShadowOid); + } + + if (projContext.getResourceShadowDiscriminator() == null) { + projContext.setResourceShadowDiscriminator(new ResourceShadowDiscriminator(null, null, null, null, true)); + } else { + projContext.markTombstone(); + } + + projContext.setFullShadow(false); + projContext.setObjectCurrent(null); + + return projContext; + } + + /** + * Check reconcile flag in account sync context and set accountOld + * variable if it's not set (from provisioning), load resource (if not set already), etc. + */ + private void finishLoadOfProjectionContext(LensContext context, + LensProjectionContext projContext, Task task, OperationResult result) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + + if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + return; + } + + // MID-2436 (volatile objects) - as a quick but effective hack, we set reconciliation:=TRUE for volatile accounts + ResourceObjectTypeDefinitionType objectDefinition = projContext.getResourceObjectTypeDefinitionType(); + if (objectDefinition != null && objectDefinition.getVolatility() == ResourceObjectVolatilityType.UNPREDICTABLE && !projContext.isDoReconciliation()) { + LOGGER.trace("Resource object volatility is UNPREDICTABLE => setting doReconciliation to TRUE for {}", projContext.getResourceShadowDiscriminator()); + projContext.setDoReconciliation(true); + } + + // Remember OID before the object could be wiped + String projectionObjectOid = projContext.getOid(); + if (projContext.isDoReconciliation() && !projContext.isFullShadow()) { + // The current object is useless here. So lets just wipe it so it will get loaded + projContext.setObjectCurrent(null); + } + + // Load current object + boolean tombstone = false; + PrismObject projectionObject = projContext.getObjectCurrent(); + if (projContext.getObjectCurrent() == null || needToReload(context, projContext)) { + if (projContext.isAdd()) { + // No need to load old object, there is none + projContext.setExists(false); + projContext.recompute(); + projectionObject = projContext.getObjectNew(); + } else { + if (projectionObjectOid == null) { + projContext.setExists(false); + if (projContext.getResourceShadowDiscriminator() == null || projContext.getResourceShadowDiscriminator().getResourceOid() == null) { + throw new SystemException( + "Projection "+projContext.getHumanReadableName()+" with null OID, no representation and no resource OID in account sync context "+projContext); + } + } else { + GetOperationOptions rootOptions = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); + if (projContext.isDoReconciliation()) { + rootOptions.setForceRefresh(true); + if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { + // Avoid discovery loops + rootOptions.setDoNotDiscovery(true); + } + } else { + rootOptions.setNoFetch(true); + } + rootOptions.setAllowNotFound(true); + Collection> options = SelectorOptions.createCollection(rootOptions); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Loading shadow {} for projection {}, options={}", projectionObjectOid, projContext.getHumanReadableName(), options); + } + + try { + PrismObject objectOld = provisioningService.getObject( + projContext.getObjectTypeClass(), projectionObjectOid, options, task, result); + if (LOGGER.isTraceEnabled()) { + if (!GetOperationOptions.isNoFetch(rootOptions) && !GetOperationOptions.isRaw(rootOptions)) { + LOGGER.trace("Full shadow loaded for {}:\n{}", projContext.getHumanReadableName(), objectOld.debugDump(1)); + } + } + Validate.notNull(objectOld.getOid()); + if (InternalsConfig.consistencyChecks) { + String resourceOid = projContext.getResourceOid(); + if (resourceOid != null && !resourceOid.equals(objectOld.asObjectable().getResourceRef().getOid())) { + throw new IllegalStateException("Loaded shadow with wrong resourceRef. Loading shadow "+projectionObjectOid+", got "+ + objectOld.getOid()+", expected resourceRef "+resourceOid+", but was "+objectOld.asObjectable().getResourceRef().getOid()+ + " for context "+projContext.getHumanReadableName()); + } + } + projContext.setLoadedObject(objectOld); + if (projContext.isDoReconciliation()) { + projContext.determineFullShadowFlag(objectOld); + } else { + projContext.setFullShadow(false); + } + projectionObject = objectOld; + if (ShadowUtil.isExists(objectOld.asObjectable())) { + projContext.setExists(true); + } else { + projContext.setExists(false); + if (ShadowUtil.isDead(objectOld.asObjectable())) { + projContext.markTombstone(); + } + LOGGER.debug("Foud only dead {} for projection context {}.", objectOld, projContext.getHumanReadableName()); + tombstone = true; + } + + } catch (ObjectNotFoundException ex) { + LOGGER.debug("Could not find object with oid {} for projection context {}.", projectionObjectOid, projContext.getHumanReadableName()); + // This does not mean BROKEN. The projection was there, but it gone now. + // Consistency mechanism might have kicked in and fixed the shadow. + // What we really want here is a thombstone projection or a refreshed projection. + result.muteLastSubresultError(); + projContext.setShadowExistsInRepo(false); + refreshContextAfterShadowNotFound(context, projContext, options, task, result); + + } catch (CommunicationException | SchemaException | ConfigurationException | SecurityViolationException + | RuntimeException | Error e) { + + LOGGER.warn("Problem while getting object with oid {}. Projection context {} is marked as broken: {}: {}", + projectionObjectOid, projContext.getHumanReadableName(), e.getClass().getSimpleName(), e.getMessage()); + projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); + + ResourceType resourceType = projContext.getResource(); + if (resourceType == null) { + throw e; + } else { + ErrorSelectorType errorSelector = null; + if (resourceType.getConsistency() != null) { + errorSelector = resourceType.getConsistency().getConnectorErrorCriticality(); + } + if (errorSelector == null) { + if (e instanceof SchemaException) { + // 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. + return; + } else { + throw e; + } + } else { + if (CriticalityType.FATAL.equals(ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.FATAL))) { + throw e; + } else { + return; + } + } + } + } + + } + projContext.setFresh(true); + } + } else { + projectionObject = projContext.getObjectCurrent(); + if (projectionObjectOid != null) { + projContext.setExists(ShadowUtil.isExists(projectionObject.asObjectable())); + } + } + + + // Determine Resource + ResourceType resourceType = projContext.getResource(); + String resourceOid = null; + if (resourceType == null) { + if (projectionObject != null) { + ShadowType shadowType = projectionObject.asObjectable(); + resourceOid = ShadowUtil.getResourceOid(shadowType); + } else if (projContext.getResourceShadowDiscriminator() != null) { + resourceOid = projContext.getResourceShadowDiscriminator().getResourceOid(); + } else if (!tombstone) { + throw new IllegalStateException("No shadow, no discriminator and not tombstone? That won't do. Projection "+projContext.getHumanReadableName()); + } + } else { + resourceOid = resourceType.getOid(); + } + + // Determine discriminator + ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); + if (discr == null) { + if (projectionObject != null) { + ShadowType accountShadowType = projectionObject.asObjectable(); + String intent = ShadowUtil.getIntent(accountShadowType); + ShadowKindType kind = ShadowUtil.getKind(accountShadowType); + discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, accountShadowType.getTag(), tombstone); + } else { + discr = new ResourceShadowDiscriminator(null, null, null, null, tombstone); + } + projContext.setResourceShadowDiscriminator(discr); + } else { + if (tombstone) { + // We do not want to reset tombstone flag if it was set before + projContext.markTombstone(); + } + } + + // Load resource + if (resourceType == null && resourceOid != null) { + resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + projContext.setResource(resourceType); + } + + //Determine refined schema and password policies for account type + RefinedObjectClassDefinition structuralObjectClassDef = projContext.getStructuralObjectClassDefinition(); + if (structuralObjectClassDef != null) { + LOGGER.trace("Finishing loading of projection context: security policy"); + SecurityPolicyType projectionSecurityPolicy = securityHelper.locateProjectionSecurityPolicy(projContext.getStructuralObjectClassDefinition(), task, result); + LOGGER.trace("Located security policy for: {},\n {}", projContext, projectionSecurityPolicy); + projContext.setProjectionSecurityPolicy(projectionSecurityPolicy); + } else { + LOGGER.trace("No structural object class definition, skipping determining security policy"); + } + + //set limitation, e.g. if this projection context should be recomputed and processed by projector + if (ModelExecuteOptions.isLimitPropagation(context.getOptions())){ + if (context.getTriggeredResourceOid() != null){ + if (!context.getTriggeredResourceOid().equals(resourceOid)){ + projContext.setCanProject(false); + } + } + } + + setPrimaryDeltaOldValue(projContext); + } + + private boolean needToReload(LensContext context, + LensProjectionContext projContext) { + ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); + if (discr == null) { + return false; + } + // This is kind of brutal. But effective. We are reloading all higher-order dependencies + // before they are processed. This makes sure we have fresh state when they are re-computed. + // Because higher-order dependencies may have more than one projection context and the + // changes applied to one of them are not automatically reflected on on other. therefore we need to reload. + if (discr.getOrder() == 0) { + return false; + } + int executionWave = context.getExecutionWave(); + int projCtxWave = projContext.getWave(); + if (executionWave == projCtxWave - 1) { + // Reload right before its execution wave + return true; + } + return false; + } + + private void fullCheckConsistence(LensContext context) { + context.checkConsistence(); + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + continue; + } + if (projectionContext.getResourceShadowDiscriminator() == null) { + throw new IllegalStateException("No discriminator in "+projectionContext); + } + } + } + + public void loadFullShadow(LensContext context, LensProjectionContext projCtx, String reason, Task task, OperationResult parentResult) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (projCtx.isFullShadow()) { + // already loaded + return; + } + if (projCtx.isAdd() && projCtx.getOid() == null) { + // nothing to load yet + return; + } + if (projCtx.isTombstone()) { + // loading is futile + return; + } + OperationResult result = parentResult.subresult(CLASS_DOT + "loadFullShadow") + .setMinor() + .build(); + FullShadowLoadedTraceType trace; + if (result.isTraced()) { + trace = new FullShadowLoadedTraceType(prismContext); + if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + ResourceType resource = projCtx.getResource(); + PolyStringType name = resource != null ? resource.getName() : null; + trace.setResourceName(name != null ? name : PolyStringType.fromOrig(projCtx.getResourceOid())); + } + trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); + result.addTrace(trace); + } else { + trace = null; + } + try { + ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator(); + if (discr != null && discr.getOrder() > 0) { + // It may be just too early to load the projection + if (LensUtil.hasLowerOrderContext(context, projCtx) && (context.getExecutionWave() < projCtx.getWave())) { + // We cannot reliably load the context now + result.addReturn(DEFAULT, "too early"); + return; + } + } + + GetOperationOptions getOptions = GetOperationOptions.createAllowNotFound(); + getOptions.setPointInTimeType(PointInTimeType.FUTURE); + if (projCtx.isDoReconciliation()) { + getOptions.setForceRefresh(true); + } + if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { + LOGGER.trace("Loading full resource object {} from provisioning - with doNotDiscover to avoid loops; reason: {}", + projCtx, reason); + // Avoid discovery loops + getOptions.setDoNotDiscovery(true); + } else { + LOGGER.trace("Loading full resource object {} from provisioning (discovery enabled), reason: {}, channel: {}", + projCtx, reason, context.getChannel()); + } + Collection> options = SelectorOptions.createCollection(getOptions); + applyAttributesToGet(projCtx, options); + try { + PrismObject objectCurrent = provisioningService + .getObject(ShadowType.class, projCtx.getOid(), options, task, result); + Validate.notNull(objectCurrent.getOid()); + if (trace != null) { + trace.setShadowLoadedRef(ObjectTypeUtil.createObjectRefWithFullObject(objectCurrent, prismContext)); + } + // TODO: use setLoadedObject() instead? + projCtx.setObjectCurrent(objectCurrent); + projCtx.determineFullShadowFlag(objectCurrent); + if (ShadowUtil.isExists(objectCurrent.asObjectable())) { + result.addReturn(DEFAULT, "found"); + } else { + LOGGER.debug("Load of full resource object {} ended with non-existent shadow (options={})", projCtx, + getOptions); + projCtx.setExists(false); + refreshContextAfterShadowNotFound(context, projCtx, options, task, result); + result.addReturn(DEFAULT, "not found"); + } + + } catch (ObjectNotFoundException ex) { + LOGGER.debug("Load of full resource object {} ended with ObjectNotFoundException (options={})", projCtx, + getOptions); + result.muteLastSubresultError(); + projCtx.setShadowExistsInRepo(false); + refreshContextAfterShadowNotFound(context, projCtx, options, task, result); + result.addReturn(DEFAULT, "not found"); + } + + projCtx.recompute(); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Loaded full resource object:\n{}", projCtx.debugDump(1)); + } + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + if (trace != null) { + if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } + trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); + } + result.computeStatusIfUnknown(); + } + } + + public void refreshContextAfterShadowNotFound(LensContext context, LensProjectionContext projCtx, Collection> options, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + if (projCtx.isDelete()){ + //this is OK, shadow was deleted, but we will continue in processing with old shadow..and set it as full so prevent from other full loading + projCtx.setFullShadow(true); + return; + } + + boolean compensated = false; + if (!GetOperationOptions.isDoNotDiscovery(SelectorOptions.findRootOptions(options))) { + // The account might have been re-created by the discovery. + // Reload focus, try to find out if there is a new matching link (and the old is gone) + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + Class focusClass = focusContext.getObjectTypeClass(); + if (FocusType.class.isAssignableFrom(focusClass)) { + LOGGER.trace("Reloading focus to check for new links"); + PrismObject focusCurrent; + try { + focusCurrent = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusContext.getOid(), null, result); + } catch (ObjectNotFoundException e) { + if (focusContext.isDelete()) { + // This may be OK. This may be later wave and the focus may be already deleted. + // Therefore let's just keep what we have. We have no way how to refresh context + // in that situation. + result.muteLastSubresultError(); + LOGGER.trace("ObjectNotFound error is not compensated (focus already deleted), setting context to tombstone"); + projCtx.markTombstone(); + return; + } else { + throw e; + } + } + FocusType focusType = (FocusType) focusCurrent.asObjectable(); + for (ObjectReferenceType linkRef: focusType.getLinkRef()) { + if (linkRef.getOid().equals(projCtx.getOid())) { + // The deleted shadow is still in the linkRef. This should not happen, but it obviously happens sometimes. + // Maybe some strange race condition? Anyway, we want a robust behavior and this linkRef should NOT be there. + // So simple remove it. + LOGGER.warn("The OID "+projCtx.getOid()+" of deleted shadow still exists in the linkRef after discovery ("+focusCurrent+"), removing it"); + ReferenceDelta unlinkDelta = prismContext.deltaFactory().reference().createModificationDelete( + FocusType.F_LINK_REF, focusContext.getObjectDefinition(), linkRef.asReferenceValue().clone()); + focusContext.swallowToSecondaryDelta(unlinkDelta); + continue; + } + boolean found = false; + for (LensProjectionContext pCtx: context.getProjectionContexts()) { + if (linkRef.getOid().equals(pCtx.getOid())) { + found = true; + break; + } + } + if (!found) { + // This link is new, it is not in the existing lens context + PrismObject newLinkRepoShadow = cacheRepositoryService.getObject(ShadowType.class, linkRef.getOid(), null, result); + if (ShadowUtil.matches(newLinkRepoShadow, projCtx.getResourceShadowDiscriminator())) { + LOGGER.trace("Found new matching link: {}, updating projection context", newLinkRepoShadow); + LOGGER.trace("Applying definition from provisioning first."); // MID-3317 + provisioningService.applyDefinition(newLinkRepoShadow, task, result); + projCtx.setObjectCurrent(newLinkRepoShadow); + projCtx.setOid(newLinkRepoShadow.getOid()); + projCtx.recompute(); + compensated = true; + break; + } else { + LOGGER.trace("Found new link: {}, but skipping it because it does not match the projection context", newLinkRepoShadow); + } + } + } + } + } + + } + + if (!compensated) { + LOGGER.trace("ObjectNotFound error is not compensated, setting context to tombstone"); + projCtx.markTombstone(); + } + } + + private void applyAttributesToGet(LensProjectionContext projCtx, Collection> options) throws SchemaException { + if ( !LensUtil.isPasswordReturnedByDefault(projCtx) + && LensUtil.needsFullShadowForCredentialProcessing(projCtx)) { + options.add(SelectorOptions.create(prismContext.toUniformPath(SchemaConstants.PATH_PASSWORD_VALUE), GetOperationOptions.createRetrieve())); + } + } + + public void reloadSecurityPolicyIfNeeded(@NotNull LensContext context, + @NotNull LensFocusContext focusContext, Task task, OperationResult result) + throws ExpressionEvaluationException, SchemaException, + CommunicationException, ConfigurationException, SecurityViolationException { + if (focusContext.hasOrganizationalChange()) { + loadSecurityPolicy(context, true, task, result); + } + } + + private void loadSecurityPolicy(LensContext context, + Task task, OperationResult result) throws ExpressionEvaluationException, + SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + loadSecurityPolicy(context, false, task, result); + } + + @SuppressWarnings("unchecked") + private void loadSecurityPolicy(LensContext context, boolean forceReload, + Task task, OperationResult result) throws ExpressionEvaluationException, + SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + LensFocusContext genericFocusContext = context.getFocusContext(); + if (genericFocusContext == null || !genericFocusContext.represents(FocusType.class)) { + LOGGER.trace("Skipping load of security policy because focus is not of FocusType"); + return; + } + LensFocusContext focusContext = (LensFocusContext) genericFocusContext; + PrismObject focus = focusContext.getObjectAny(); + SecurityPolicyType globalSecurityPolicy = determineAndSetGlobalSecurityPolicy(context, focus, task, result); + SecurityPolicyType focusSecurityPolicy = determineAndSetFocusSecurityPolicy(focusContext, focus, globalSecurityPolicy, + forceReload, task, result); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Security policy:\n Global:\n{}\n Focus:\n{}", + globalSecurityPolicy.asPrismObject().debugDump(2), + focusSecurityPolicy==null?null:focusSecurityPolicy.asPrismObject().debugDump(2)); + } else { + LOGGER.debug("Security policy: global: {}, focus: {}", globalSecurityPolicy, focusSecurityPolicy); + } + } + + @NotNull + private SecurityPolicyType determineAndSetGlobalSecurityPolicy(LensContext context, + PrismObject focus, Task task, OperationResult result) + throws CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + SecurityPolicyType existingPolicy = context.getGlobalSecurityPolicy(); + if (existingPolicy != null) { + return existingPolicy; + } else { + SecurityPolicyType loadedPolicy = securityHelper.locateGlobalSecurityPolicy(focus, context.getSystemConfiguration(), + task, result); + SecurityPolicyType resultingPolicy; + if (loadedPolicy != null) { + resultingPolicy = loadedPolicy; + } else { + // use empty policy to avoid repeated lookups + resultingPolicy = new SecurityPolicyType(); + } + context.setGlobalSecurityPolicy(resultingPolicy); + return resultingPolicy; + } + } + + private SecurityPolicyType determineAndSetFocusSecurityPolicy(LensFocusContext focusContext, + PrismObject focus, SecurityPolicyType globalSecurityPolicy, boolean forceReload, Task task, + OperationResult result) throws SchemaException { + SecurityPolicyType existingPolicy = focusContext.getSecurityPolicy(); + if (existingPolicy != null && !forceReload) { + return existingPolicy; + } else { + SecurityPolicyType loadedPolicy = securityHelper.locateFocusSecurityPolicy(focus, task, result); + SecurityPolicyType resultingPolicy; + if (loadedPolicy != null) { + resultingPolicy = loadedPolicy; + } else { + // Not very clean. In fact we should store focus security policy separate from global + // policy to avoid confusion. But need to do this to fix MID-4793 and backport the fix. + // Therefore avoiding big changes. TODO: fix properly later + resultingPolicy = globalSecurityPolicy; + } + focusContext.setSecurityPolicy(resultingPolicy); + return resultingPolicy; + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java index 83e193d0b9f..b3da95829bd 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java @@ -228,6 +228,7 @@ private Ma mappingBuilder.rootNode(focusOdo); mappingBuilder.originType(OriginType.OUTBOUND); mappingBuilder.mappingKind(MappingKindType.OUTBOUND); + mappingBuilder.implicitTargetPath(ItemPath.create(ShadowType.F_ATTRIBUTES, attributeQName)); mappingBuilder.refinedObjectClassDefinition(rOcDef); if (projCtx.isDelete()) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java index c93ee3127db..0477a713067 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java @@ -181,7 +181,9 @@ public ValuePolicyType resolve() { MappingInitializer,PrismPropertyDefinition> initializer = (builder) -> { - builder.mappingKind(MappingKindType.OUTBOUND); + builder.mappingKind(MappingKindType.OUTBOUND) + .implicitSourcePath(SchemaConstants.PATH_PASSWORD_VALUE) + .implicitTargetPath(SchemaConstants.PATH_PASSWORD_VALUE); builder.defaultTargetDefinition(projPasswordPropertyDefinition); builder.defaultSource(new Source<>(focusPasswordIdi, ExpressionConstants.VAR_INPUT_QNAME)); builder.valuePolicyResolver(stringPolicyResolver); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java index fca63d3ad7b..e723fae42f0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java @@ -723,6 +723,7 @@ private vo .variableResolver(variableProducer) .valuePolicyResolver(createStringPolicyResolver(context)) .mappingKind(MappingKindType.INBOUND) + .implicitSourcePath(ShadowType.F_ATTRIBUTES.append(accountAttributeQName)) .originType(OriginType.INBOUND) .originObject(resource); @@ -1317,6 +1318,8 @@ private void processSpecialPropertyInbound(Collection void evaluateOutboundMapping(final LensContext 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; + } + + ObjectQuery q; + try { + if (!satisfyCondition(currentShadow.asObjectable(), conditionalFilter, expressionProfile, + resourceType, configurationType, "", task, result)) { + LOGGER.trace("Skipping evaluating correlation rule. Condition in {} not satisfied.", conditionalFilter); + return false; + } + 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/test/java/com/evolveum/midpoint/model/impl/sync/TestCorrelationConfirmationEvaluator.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/sync/TestCorrelationConfirmationEvaluator.java index a36a2e85bfc..a29e4ebad2d 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/sync/TestCorrelationConfirmationEvaluator.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/sync/TestCorrelationConfirmationEvaluator.java @@ -7,14 +7,9 @@ package com.evolveum.midpoint.model.impl.sync; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertNotNull; - import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.DirtiesContext; @@ -37,6 +32,8 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import static org.testng.AssertJUnit.*; + @ContextConfiguration(locations = { "classpath:ctx-model-test-main.xml" }) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) public class TestCorrelationConfirmationEvaluator extends AbstractInternalModelIntegrationTest { @@ -49,6 +46,7 @@ public class TestCorrelationConfirmationEvaluator extends AbstractInternalModelI private static final String CORRELATION_SECOND_FILTER = TEST_DIR + "/correlation-second-filter.xml"; private static final String CORRELATION_WITH_CONDITION = TEST_DIR + "/correlation-with-condition.xml"; private static final String CORRELATION_WITH_CONDITION_EMPL_NUMBER = TEST_DIR + "/correlation-with-condition-emplNumber.xml"; + private static final String CORRELATION_WITH_CONDITION_NAME = TEST_DIR + "/correlation-with-condition-name.xml"; @Autowired private RepositoryService repositoryService; @Autowired private SynchronizationExpressionsEvaluator evaluator; @@ -152,7 +150,7 @@ public void test004CorrelationMatchCaseInsensitive() throws Exception { assertNotNull(userType); userType.asObjectable().setName(new PolyStringType("JACK")); - SynchronizationContext syncCtx = createSynchronizationContext(ACCOUNT_SHADOW_JACK_DUMMY_FILE, CORRELATION_CASE_INSENSITIVE, RESOURCE_DUMMY_FILE, task, result); + SynchronizationContext syncCtx = createSynchronizationContext(ACCOUNT_SHADOW_JACK_DUMMY_FILE, Collections.singletonList(CORRELATION_CASE_INSENSITIVE), RESOURCE_DUMMY_FILE, task, result); try { boolean matchedUsers = evaluator.matchFocusByCorrelationRule(syncCtx, userType, result); @@ -176,7 +174,7 @@ public void test005CorrelationMatchCaseInsensitive() throws Exception { assertNotNull(userType); userType.asObjectable().setEmployeeNumber("JaCk"); - SynchronizationContext syncCtx = createSynchronizationContext(ACCOUNT_SHADOW_JACK_DUMMY_FILE, CORRELATION_CASE_INSENSITIVE_EMPL_NUMBER, RESOURCE_DUMMY_FILE, task, result); + SynchronizationContext syncCtx = createSynchronizationContext(ACCOUNT_SHADOW_JACK_DUMMY_FILE, Collections.singletonList(CORRELATION_CASE_INSENSITIVE_EMPL_NUMBER), RESOURCE_DUMMY_FILE, task, result); try { boolean matchedUsers = evaluator.matchFocusByCorrelationRule(syncCtx, userType, result); @@ -190,15 +188,20 @@ public void test005CorrelationMatchCaseInsensitive() throws Exception { } } - private SynchronizationContext createSynchronizationContext(File account, String correlationFilter, File resource, Task task, OperationResult result) throws SchemaException, IOException { + private SynchronizationContext createSynchronizationContext(File account, List correlationFilters, File resource, Task task, OperationResult result) throws SchemaException, IOException { ShadowType shadow = parseObjectType(account, ShadowType.class); - ConditionalSearchFilterType query = PrismTestUtil.parseAtomicValue(new File(correlationFilter), ConditionalSearchFilterType.COMPLEX_TYPE); + List conditionalSearchFilterTypes = new ArrayList<>(correlationFilters.size()); + for (String correlationFilter : correlationFilters) { + ConditionalSearchFilterType query = PrismTestUtil.parseAtomicValue(new File(correlationFilter), ConditionalSearchFilterType.COMPLEX_TYPE); + conditionalSearchFilterTypes.add(query); + } + ResourceType resourceType = parseObjectType(resource, ResourceType.class); resourceType.getSynchronization().getObjectSynchronization().get(0).getCorrelation().clear(); - resourceType.getSynchronization().getObjectSynchronization().get(0).getCorrelation().add(query); + resourceType.getSynchronization().getObjectSynchronization().get(0).getCorrelation().addAll(conditionalSearchFilterTypes); ObjectSynchronizationType objectSynchronizationType = resourceType.getSynchronization().getObjectSynchronization().get(0); PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); @@ -244,4 +247,40 @@ public void test006CorrelationFindCaseInsensitive() throws Exception { PrismObject jack = matchedUsers.get(0); assertUser(jack, "c0c010c0-d34d-b33f-f00d-111111111111", "JACK", "Jack Sparrow", "Jack", "Sparrow"); } + + @Test + public void test007CorrelationMatchWithCondition() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userType = repositoryService.getObject(UserType.class, USER_JACK_OID, null, result); + //assert jack + assertNotNull(userType); + + + SynchronizationContext syncCtx = createSynchronizationContext(ACCOUNT_SHADOW_JACK_DUMMY_FILE, + Arrays.asList(CORRELATION_WITH_CONDITION, CORRELATION_WITH_CONDITION_EMPL_NUMBER), RESOURCE_DUMMY_FILE, task, result); + + boolean matches = evaluator.matchFocusByCorrelationRule(syncCtx, userType, result); + + assertTrue("At least one correlation matches", matches); + } + + @Test + public void test008CorrelationNoMatchWithCondition() throws Exception { + Task task = getTestTask(); + OperationResult result = task.getResult(); + + PrismObject userType = repositoryService.getObject(UserType.class, USER_JACK_OID, null, result); + //assert jack + assertNotNull(userType); + + + SynchronizationContext syncCtx = createSynchronizationContext(ACCOUNT_SHADOW_JACK_DUMMY_FILE, + Arrays.asList(CORRELATION_WITH_CONDITION, CORRELATION_WITH_CONDITION_NAME), RESOURCE_DUMMY_FILE, task, result); + + boolean matches = evaluator.matchFocusByCorrelationRule(syncCtx, userType, result); + + assertFalse("No correlation condition should match", matches); + } } diff --git a/model/model-impl/src/test/resources/sync/correlation-with-condition-name.xml b/model/model-impl/src/test/resources/sync/correlation-with-condition-name.xml new file mode 100644 index 00000000000..473365dcab4 --- /dev/null +++ b/model/model-impl/src/test/resources/sync/correlation-with-condition-name.xml @@ -0,0 +1,35 @@ + + + + + + + + + + c:name + + + + + + 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 a5ee6a3d2f0..13914d9e491 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 @@ -7,6 +7,7 @@ package com.evolveum.midpoint.report.impl; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; import com.evolveum.midpoint.model.api.ModelPublicConstants; import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.model.api.context.ModelContext; @@ -33,6 +34,8 @@ import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.ReportTypeUtil; +import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters; +import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; import com.evolveum.midpoint.task.api.ClusterExecutionHelper; import com.evolveum.midpoint.task.api.ClusterExecutionOptions; import com.evolveum.midpoint.task.api.Task; @@ -95,6 +98,7 @@ public class ReportManagerImpl implements ReportManager, ChangeHook, ReadHook { @Autowired private ModelService modelService; @Autowired private ClusterExecutionHelper clusterExecutionHelper; @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; + @Autowired private SecurityEnforcer securityEnforcer; @PostConstruct public void init() { @@ -427,6 +431,12 @@ public InputStream getReportOutputData(String reportOutputOid, OperationResult p ReportOutputType reportOutput = modelService.getObject(ReportOutputType.class, reportOutputOid, null, task, result).asObjectable(); + // Extra safety check: traces can be retrieved only when special authorization is present + if (ObjectTypeUtil.hasArchetype(reportOutput, SystemObjectsType.ARCHETYPE_TRACE.value())) { + securityEnforcer.authorize(ModelAuthorizationAction.READ_TRACE.getUrl(), null, + AuthorizationParameters.EMPTY, null, task, result); + } + String filePath = reportOutput.getFilePath(); if (StringUtils.isEmpty(filePath)) { result.recordFatalError("Report output file path is not defined."); diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java index 361dc356c68..bd9a08378e6 100644 --- a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java @@ -123,7 +123,9 @@ public void storeTrace(Task task, OperationResult result, @Nullable OperationRes try { long start = System.currentTimeMillis(); TracingOutputType tracingOutput = createTracingOutput(task, result, tracingProfile); - String xml = prismContext.xmlSerializer().serializeRealValue(tracingOutput); + String xml = prismContext.xmlSerializer() + .options(SerializationOptions.createSerializeReferenceNames()) + .serializeRealValue(tracingOutput); if (zip) { MiscUtil.writeZipFile(file, ZIP_ENTRY_NAME, xml, StandardCharsets.UTF_8); LOGGER.info("Trace was written to {} ({} chars uncompressed) in {} milliseconds", file, xml.length(),