From 87cf8b64dfb6df3fe39562700af4ac508c660ac1 Mon Sep 17 00:00:00 2001 From: kate Date: Thu, 16 Apr 2020 13:34:16 +0200 Subject: [PATCH 01/27] polystring translate fix --- .../evolveum/midpoint/common/LocalizationServiceImpl.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java b/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java index dd232bbd877..2f6da999421 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/LocalizationServiceImpl.java @@ -235,7 +235,10 @@ public String translate(PolyString polyString, Locale locale, boolean allowOrig) } } if (polyString.getTranslation() != null) { - return translate(polyString.getTranslation(), locale); + String value = translate(polyString.getTranslation(), locale); + if (value != null){ + return value; + } } if (allowOrig) { return polyString.getOrig(); From 4a677c5e4a0fc3d03a85f504b3b1ea6fe7843393 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Thu, 16 Apr 2020 14:25:07 +0200 Subject: [PATCH 02/27] improved getIntent and getKind in constructionValueWrapper --- .../impl/prism/ConstructionValueWrapper.java | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/ConstructionValueWrapper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/ConstructionValueWrapper.java index 80a6f62e865..038863000b2 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/ConstructionValueWrapper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/ConstructionValueWrapper.java @@ -10,14 +10,21 @@ import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.component.prism.ValueStatus; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; +import org.apache.commons.lang.StringUtils; + public class ConstructionValueWrapper extends PrismContainerValueWrapperImpl { + private static final transient Trace LOGGER = TraceManager.getTrace(ConstructionValueWrapper.class); + private PrismObject resource; private transient RefinedResourceSchema resourceSchema; @@ -45,11 +52,38 @@ public RefinedResourceSchema getResourceSchema() throws SchemaException { } public ShadowKindType getKind() { - return getNewValue().asContainerable().getKind(); + ShadowKindType kind = getNewValue().asContainerable().getKind(); + if (kind == null) { + kind = ShadowKindType.ACCOUNT; + } + return kind; } public String getIntent() { - return getNewValue().asContainerable().getIntent(); + String intent = getNewValue().asContainerable().getIntent(); + if (StringUtils.isBlank(intent)) { + ObjectClassComplexTypeDefinition def; + try { + def = findDefaultObjectClassDefinition(); + if (def != null) { + intent = def.getIntent(); + } + } catch (SchemaException e) { + LOGGER.error("Cannot get default object class definition, {}", e.getMessage(), e); + intent = "default"; + } + + } + return intent; + } + + private ObjectClassComplexTypeDefinition findDefaultObjectClassDefinition() throws SchemaException{ + RefinedResourceSchema schema = getResourceSchema(); + if (schema == null) { + return null; + } + + return schema.findDefaultObjectClassDefinition(getKind()); } } From 342f524d19524aa91efbfa388e043343753ab823 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Thu, 16 Apr 2020 14:43:59 +0200 Subject: [PATCH 03/27] assignment panels cleanup --- .../prism/PrismContainerValueWrapper.java | 178 ++++---- .../AbstractRoleAssignmentPanel.java | 422 ++++++++---------- .../component/assignment/AssignmentPanel.java | 173 +++---- .../ConstructionAssignmentPanel.java | 8 +- .../FocusMappingsAssignmentPanel.java | 4 - .../assignment/PolicyRulesPanel.java | 11 +- 6 files changed, 370 insertions(+), 426 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismContainerValueWrapper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismContainerValueWrapper.java index 3e2b47e3dfd..2b82e1b8514 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismContainerValueWrapper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/impl/prism/PrismContainerValueWrapper.java @@ -1,92 +1,86 @@ -/* - * 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.gui.impl.prism; - -import java.util.List; - -import com.evolveum.midpoint.gui.api.prism.ItemWrapper; -import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; -import com.evolveum.midpoint.prism.Containerable; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismContainerDefinition; -import com.evolveum.midpoint.prism.PrismContainerValue; -import com.evolveum.midpoint.prism.Referencable; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.path.ItemName; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.web.component.prism.ValueStatus; -import com.evolveum.midpoint.xml.ns._public.common.common_3.VirtualContainerItemSpecificationType; - -/** - * @author katka - * - */ -public interface PrismContainerValueWrapper extends PrismValueWrapper>{ - - String getDisplayName(); - String getHelpText(); - - boolean isExpanded(); - - void setExpanded(boolean expanded); - - boolean hasMetadata(); - boolean isShowMetadata(); - - void setShowMetadata(boolean showMetadata); - - boolean isSorted(); - void setSorted(boolean sorted); - - List> getChildContainers() throws SchemaException; - - ValueStatus getStatus(); - void setStatus(ValueStatus status); - - List> getContainers(); - - List> getNonContainers(); - -// PrismContainerWrapper getParent(); - - List> getItems(); - -// PrismContainerValue getNewValue(); - - PrismContainerWrapper findContainer(ItemPath path) throws SchemaException; - PrismPropertyWrapper findProperty(ItemPath propertyPath) throws SchemaException; - PrismReferenceWrapper findReference(ItemPath path) throws SchemaException; - IW findItem(ItemPath path, Class type) throws SchemaException; - - ItemPath getPath(); - - boolean isSelected(); - boolean setSelected(boolean selected); //TODO why return boolean? - - - boolean isReadOnly(); - void setReadOnly(boolean readOnly, boolean recursive); - - @Deprecated - boolean hasChanged(); - - boolean isShowEmpty(); - void setShowEmpty(boolean setShowEmpty); - - //void sort(); - - void applyDelta(ID delta) throws SchemaException; - PrismContainerValue getValueToAdd() throws SchemaException; - - boolean isHeterogenous(); - void setHeterogenous(boolean heterogenous); - - void setVirtualContainerItems(List virtualItems); - boolean isVirtual(); - -} +/* + * 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.gui.impl.prism; + +import java.util.List; + +import com.evolveum.midpoint.gui.api.prism.ItemWrapper; +import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; +import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.PrismContainerDefinition; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.Referencable; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.web.component.prism.ValueStatus; +import com.evolveum.midpoint.xml.ns._public.common.common_3.VirtualContainerItemSpecificationType; + +/** + * @author katka + * + */ +public interface PrismContainerValueWrapper extends PrismValueWrapper>{ + + String getDisplayName(); + String getHelpText(); + + boolean isExpanded(); + + void setExpanded(boolean expanded); + + boolean hasMetadata(); + boolean isShowMetadata(); + + void setShowMetadata(boolean showMetadata); + + boolean isSorted(); + void setSorted(boolean sorted); + + List> getChildContainers() throws SchemaException; + + ValueStatus getStatus(); + void setStatus(ValueStatus status); + + List> getContainers(); + + List> getNonContainers(); + + List> getItems(); + + PrismContainerWrapper findContainer(ItemPath path) throws SchemaException; + PrismPropertyWrapper findProperty(ItemPath propertyPath) throws SchemaException; + PrismReferenceWrapper findReference(ItemPath path) throws SchemaException; + IW findItem(ItemPath path, Class type) throws SchemaException; + + ItemPath getPath(); + + boolean isSelected(); + boolean setSelected(boolean selected); //TODO why return boolean? + + + boolean isReadOnly(); + void setReadOnly(boolean readOnly, boolean recursive); + + @Deprecated + boolean hasChanged(); + + boolean isShowEmpty(); + void setShowEmpty(boolean setShowEmpty); + + //void sort(); + + void applyDelta(ID delta) throws SchemaException; + PrismContainerValue getValueToAdd() throws SchemaException; + + boolean isHeterogenous(); + void setHeterogenous(boolean heterogenous); + + void setVirtualContainerItems(List virtualItems); + boolean isVirtual(); + +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java index f4f0ff23200..a3dbaa09d1a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractRoleAssignmentPanel.java @@ -1,235 +1,187 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.web.component.assignment; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.gui.api.prism.ItemWrapper; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.web.component.prism.ItemVisibility; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -import org.apache.commons.lang.StringUtils; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; - -import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; -import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; -import com.evolveum.midpoint.gui.impl.component.data.column.AbstractItemWrapperColumn.ColumnType; -import com.evolveum.midpoint.gui.impl.component.data.column.PrismReferenceWrapperColumn; -import com.evolveum.midpoint.gui.impl.prism.PrismContainerValueWrapper; -import com.evolveum.midpoint.prism.PrismConstants; -import com.evolveum.midpoint.prism.PrismContainerDefinition; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.prism.query.RefFilter; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.web.component.search.SearchFactory; -import com.evolveum.midpoint.web.component.search.SearchItemDefinition; -import com.evolveum.midpoint.web.session.UserProfileStorage; - -/** - * Created by honchar. - */ -public class AbstractRoleAssignmentPanel extends AssignmentPanel { - - private static final long serialVersionUID = 1L; - - private static final Trace LOGGER = TraceManager.getTrace(AssignmentPanel.class); - - protected static final String DOT_CLASS = AbstractRoleAssignmentPanel.class.getName() + "."; - private static final String OPERATION_LOAD_TARGET_REF_OBJECT = DOT_CLASS + "loadAssignmentTargetRefObject"; - - public AbstractRoleAssignmentPanel(String id, IModel> assignmentContainerWrapperModel){ - super(id, assignmentContainerWrapperModel); - } - -// protected Fragment initCustomButtonToolbar(String contentAreaId){ -// Fragment searchContainer = new Fragment(contentAreaId, ID_BUTTON_TOOLBAR_FRAGMENT, this); -// -// MultifunctionalButton newObjectIcon = getMultivalueContainerListPanel().getNewItemButton(ID_NEW_ITEM_BUTTON); -// searchContainer.add(newObjectIcon); -// -// AjaxIconButton showAllAssignmentsButton = new AjaxIconButton(ID_SHOW_ALL_ASSIGNMENTS_BUTTON, new Model<>("fa fa-address-card"), -// createStringResource("AssignmentTablePanel.menu.showAllAssignments")) { -// -// private static final long serialVersionUID = 1L; -// -// @Override -// public void onClick(AjaxRequestTarget ajaxRequestTarget) { -// showAllAssignments(ajaxRequestTarget); -// } -// }; -// searchContainer.addOrReplace(showAllAssignmentsButton); -// showAllAssignmentsButton.setOutputMarkupId(true); -// showAllAssignmentsButton.add(new VisibleEnableBehaviour(){ -// -// private static final long serialVersionUID = 1L; -// -// public boolean isVisible(){ -// return showAllAssignmentsVisible(); -// } -// }); -// return searchContainer; -// } - - protected List, String>> initColumns() { - List, String>> columns = new ArrayList<>(); - - columns.add(new AbstractColumn, String>( - createStringResource("AbstractRoleAssignmentPanel.relationLabel")) { - @Override - public void populateItem(Item>> item, String componentId, IModel> assignmentModel) { - item.add(new Label(componentId, getRelationLabelValue(assignmentModel.getObject()))); - } - }); - - if (!OrgType.COMPLEX_TYPE.equals(getAssignmentType())) { - columns.add(new PrismReferenceWrapperColumn(getModel(), AssignmentType.F_TENANT_REF, ColumnType.STRING, getPageBase())); - columns.add(new PrismReferenceWrapperColumn(getModel(), AssignmentType.F_ORG_REF, ColumnType.STRING, getPageBase())); - } - - columns.add(new AbstractColumn, String>(createStringResource("AbstractRoleAssignmentPanel.identifierLabel")){ - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(Item>> item, String componentId, - final IModel> rowModel) { - item.add(new Label(componentId, getIdentifierLabelModel(rowModel.getObject()))); - } - }); - - return columns; - } - - private String getRelationLabelValue(PrismContainerValueWrapper assignmentWrapper){ - if (assignmentWrapper == null || assignmentWrapper.getRealValue() == null - || assignmentWrapper.getRealValue().getTargetRef() == null - || assignmentWrapper.getRealValue().getTargetRef().getRelation() == null){ - return ""; - } - return assignmentWrapper.getRealValue().getTargetRef().getRelation().getLocalPart(); - } - - - protected void initCustomPaging(){ - getAssignmentsTabStorage().setPaging(getPrismContext().queryFactory() - .createPaging(0, (int) getParentPage().getItemsPerPage(UserProfileStorage.TableId.ASSIGNMENTS_TAB_TABLE))); - } - - @Override - protected UserProfileStorage.TableId getTableId() { - return UserProfileStorage.TableId.ASSIGNMENTS_TAB_TABLE; - } - - protected ObjectQuery createObjectQuery() { - Collection delegationRelations = getParentPage().getRelationRegistry() - .getAllRelationsFor(RelationKindType.DELEGATION); - ObjectFilter deputyFilter = getParentPage().getPrismContext().queryFor(AssignmentType.class) - .item(AssignmentType.F_TARGET_REF) - .refRelation(delegationRelations.toArray(new QName[0])) - .buildFilter(); - - QName targetType = getAssignmentType(); - RefFilter targetRefFilter = null; - if (targetType != null){ - ObjectReferenceType ort = new ObjectReferenceType(); - ort.setType(targetType); - ort.setRelation(new QName(PrismConstants.NS_QUERY, "any")); - targetRefFilter = (RefFilter) getParentPage().getPrismContext().queryFor(AssignmentType.class) - .item(AssignmentType.F_TARGET_REF) - .ref(ort.asReferenceValue()) - .buildFilter(); - targetRefFilter.setOidNullAsAny(true); - targetRefFilter.setRelationNullAsAny(true); - } - ObjectQuery query = getParentPage().getPrismContext().queryFor(AssignmentType.class) - .not() - .exists(AssignmentType.F_POLICY_RULE) - .build(); - query.addFilter(getPrismContext().queryFactory().createNot(deputyFilter)); - query.addFilter(targetRefFilter); - return query; - } - - protected QName getAssignmentType(){ - return AbstractRoleType.COMPLEX_TYPE; - } - - private IModel getIdentifierLabelModel(PrismContainerValueWrapper assignmentContainer){ - if (assignmentContainer == null || assignmentContainer.getRealValue() == null){ - return Model.of(""); - } - AssignmentType assignment = assignmentContainer.getRealValue(); - if (assignment.getTargetRef() == null){ - return Model.of(""); - } - - PrismObject object = WebModelServiceUtils.loadObject(assignment.getTargetRef(), getPageBase(), - getPageBase().createSimpleTask(OPERATION_LOAD_TARGET_REF_OBJECT), new OperationResult(OPERATION_LOAD_TARGET_REF_OBJECT)); - if (object == null || !(object.asObjectable() instanceof AbstractRoleType)){ - return Model.of(""); - } - AbstractRoleType targetRefObject = (AbstractRoleType) object.asObjectable(); - if (StringUtils.isNotEmpty(targetRefObject.getIdentifier())){ - return Model.of(targetRefObject.getIdentifier()); - } - if (targetRefObject.getDisplayName() != null && !targetRefObject.getName().getOrig().equals(targetRefObject.getDisplayName().getOrig())){ - return Model.of(targetRefObject.getName().getOrig()); - } - return Model.of(""); - } - -// protected List getObjectTypesList(){ -// return WebComponentUtil.createAssignableTypesList(); -// } - - @Override - protected List createSearchableItems(PrismContainerDefinition containerDef) { - List defs = new ArrayList<>(); - - SearchFactory.addSearchRefDef(containerDef, AssignmentType.F_TARGET_REF, defs, AreaCategoryType.ADMINISTRATION, getPageBase()); - SearchFactory.addSearchRefDef(containerDef, ItemPath.create(AssignmentType.F_CONSTRUCTION, ConstructionType.F_RESOURCE_REF), defs, AreaCategoryType.ADMINISTRATION, getPageBase()); - SearchFactory.addSearchPropertyDef(containerDef, ItemPath.create(AssignmentType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS), defs); - SearchFactory.addSearchPropertyDef(containerDef, ItemPath.create(AssignmentType.F_ACTIVATION, ActivationType.F_EFFECTIVE_STATUS), defs); - - defs.addAll(SearchFactory.createExtensionDefinitionList(containerDef)); - - return defs; - } - - @Override - protected ItemVisibility getTypedContainerVisibility(ItemWrapper wrapper) { - if (QNameUtil.match(PolicyRuleType.COMPLEX_TYPE, wrapper.getTypeName())){ - return ItemVisibility.HIDDEN; - } - if (QNameUtil.match(PersonaConstructionType.COMPLEX_TYPE, wrapper.getTypeName())){ - return ItemVisibility.HIDDEN; - } - - if (QNameUtil.match(ConstructionType.COMPLEX_TYPE, wrapper.getTypeName())){ - return ItemVisibility.HIDDEN; - } - - return ItemVisibility.AUTO; - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.web.component.assignment; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import javax.xml.namespace.QName; + +import org.apache.commons.lang.StringUtils; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; + +import com.evolveum.midpoint.gui.api.prism.ItemWrapper; +import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; +import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; +import com.evolveum.midpoint.gui.impl.component.data.column.AbstractItemWrapperColumn.ColumnType; +import com.evolveum.midpoint.gui.impl.component.data.column.PrismReferenceWrapperColumn; +import com.evolveum.midpoint.gui.impl.prism.PrismContainerValueWrapper; +import com.evolveum.midpoint.prism.PrismConstants; +import com.evolveum.midpoint.prism.PrismContainerDefinition; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.prism.query.RefFilter; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.web.component.prism.ItemVisibility; +import com.evolveum.midpoint.web.component.search.SearchFactory; +import com.evolveum.midpoint.web.component.search.SearchItemDefinition; +import com.evolveum.midpoint.web.session.UserProfileStorage; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +/** + * Created by honchar. + */ +public class AbstractRoleAssignmentPanel extends AssignmentPanel { + + private static final long serialVersionUID = 1L; + + protected static final String DOT_CLASS = AbstractRoleAssignmentPanel.class.getName() + "."; + private static final String OPERATION_LOAD_TARGET_REF_OBJECT = DOT_CLASS + "loadAssignmentTargetRefObject"; + + public AbstractRoleAssignmentPanel(String id, IModel> assignmentContainerWrapperModel){ + super(id, assignmentContainerWrapperModel); + } + + protected List, String>> initColumns() { + List, String>> columns = new ArrayList<>(); + + columns.add(new AbstractColumn, String>( + createStringResource("AbstractRoleAssignmentPanel.relationLabel")) { + @Override + public void populateItem(Item>> item, String componentId, IModel> assignmentModel) { + item.add(new Label(componentId, getRelationLabelValue(assignmentModel.getObject()))); + } + }); + + if (!OrgType.COMPLEX_TYPE.equals(getAssignmentType())) { + columns.add(new PrismReferenceWrapperColumn(getModel(), AssignmentType.F_TENANT_REF, ColumnType.STRING, getPageBase())); + columns.add(new PrismReferenceWrapperColumn(getModel(), AssignmentType.F_ORG_REF, ColumnType.STRING, getPageBase())); + } + + columns.add(new AbstractColumn, String>(createStringResource("AbstractRoleAssignmentPanel.identifierLabel")){ + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(Item>> item, String componentId, + final IModel> rowModel) { + item.add(new Label(componentId, getIdentifierLabelModel(rowModel.getObject()))); + } + }); + + return columns; + } + + private String getRelationLabelValue(PrismContainerValueWrapper assignmentWrapper){ + if (assignmentWrapper == null || assignmentWrapper.getRealValue() == null + || assignmentWrapper.getRealValue().getTargetRef() == null + || assignmentWrapper.getRealValue().getTargetRef().getRelation() == null){ + return ""; + } + return assignmentWrapper.getRealValue().getTargetRef().getRelation().getLocalPart(); + } + + + protected void initCustomPaging(){ + getAssignmentsTabStorage().setPaging(getPrismContext().queryFactory() + .createPaging(0, (int) getParentPage().getItemsPerPage(UserProfileStorage.TableId.ASSIGNMENTS_TAB_TABLE))); + } + + @Override + protected UserProfileStorage.TableId getTableId() { + return UserProfileStorage.TableId.ASSIGNMENTS_TAB_TABLE; + } + + protected ObjectQuery createObjectQuery() { + Collection delegationRelations = getParentPage().getRelationRegistry() + .getAllRelationsFor(RelationKindType.DELEGATION); + ObjectFilter deputyFilter = getParentPage().getPrismContext().queryFor(AssignmentType.class) + .item(AssignmentType.F_TARGET_REF) + .refRelation(delegationRelations.toArray(new QName[0])) + .buildFilter(); + + QName targetType = getAssignmentType(); + RefFilter targetRefFilter = null; + if (targetType != null){ + ObjectReferenceType ort = new ObjectReferenceType(); + ort.setType(targetType); + ort.setRelation(new QName(PrismConstants.NS_QUERY, "any")); + targetRefFilter = (RefFilter) getParentPage().getPrismContext().queryFor(AssignmentType.class) + .item(AssignmentType.F_TARGET_REF) + .ref(ort.asReferenceValue()) + .buildFilter(); + targetRefFilter.setOidNullAsAny(true); + targetRefFilter.setRelationNullAsAny(true); + } + ObjectQuery query = getParentPage().getPrismContext().queryFor(AssignmentType.class) + .not() + .exists(AssignmentType.F_POLICY_RULE) + .build(); + query.addFilter(getPrismContext().queryFactory().createNot(deputyFilter)); + query.addFilter(targetRefFilter); + return query; + } + + protected QName getAssignmentType(){ + return AbstractRoleType.COMPLEX_TYPE; + } + + private IModel getIdentifierLabelModel(PrismContainerValueWrapper assignmentContainer){ + if (assignmentContainer == null || assignmentContainer.getRealValue() == null){ + return Model.of(""); + } + AssignmentType assignment = assignmentContainer.getRealValue(); + if (assignment.getTargetRef() == null){ + return Model.of(""); + } + + PrismObject object = WebModelServiceUtils.loadObject(assignment.getTargetRef(), getPageBase(), + getPageBase().createSimpleTask(OPERATION_LOAD_TARGET_REF_OBJECT), new OperationResult(OPERATION_LOAD_TARGET_REF_OBJECT)); + if (object == null || !(object.asObjectable() instanceof AbstractRoleType)){ + return Model.of(""); + } + AbstractRoleType targetRefObject = (AbstractRoleType) object.asObjectable(); + if (StringUtils.isNotEmpty(targetRefObject.getIdentifier())){ + return Model.of(targetRefObject.getIdentifier()); + } + if (targetRefObject.getDisplayName() != null && !targetRefObject.getName().getOrig().equals(targetRefObject.getDisplayName().getOrig())){ + return Model.of(targetRefObject.getName().getOrig()); + } + return Model.of(""); + } + + @Override + protected List createSearchableItems(PrismContainerDefinition containerDef) { + List defs = super.createSearchableItems(containerDef); + SearchFactory.addSearchRefDef(containerDef, AssignmentType.F_TARGET_REF, defs, AreaCategoryType.ADMINISTRATION, getPageBase()); + return defs; + } + + @Override + protected ItemVisibility getTypedContainerVisibility(ItemWrapper wrapper) { + if (QNameUtil.match(PolicyRuleType.COMPLEX_TYPE, wrapper.getTypeName())){ + return ItemVisibility.HIDDEN; + } + if (QNameUtil.match(PersonaConstructionType.COMPLEX_TYPE, wrapper.getTypeName())){ + return ItemVisibility.HIDDEN; + } + + if (QNameUtil.match(ConstructionType.COMPLEX_TYPE, wrapper.getTypeName())){ + return ItemVisibility.HIDDEN; + } + + return ItemVisibility.AUTO; + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java index 428262b55f0..a426224b080 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentPanel.java @@ -6,6 +6,27 @@ */ package com.evolveum.midpoint.web.component.assignment; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.xml.namespace.QName; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; + import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.component.AssignmentPopup; import com.evolveum.midpoint.gui.api.component.BasePanel; @@ -37,7 +58,6 @@ import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.logging.LoggingUtils; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.component.MultiCompositedButtonPanel; @@ -51,27 +71,9 @@ import com.evolveum.midpoint.web.component.search.SearchFactory; import com.evolveum.midpoint.web.component.search.SearchItemDefinition; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; -import com.evolveum.midpoint.web.model.PrismContainerWrapperModel; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.list.ListItem; -import org.apache.wicket.markup.html.panel.Fragment; -import org.apache.wicket.markup.html.panel.Panel; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; - -import javax.xml.namespace.QName; -import java.util.*; public class AssignmentPanel extends BasePanel> { @@ -146,39 +148,7 @@ protected void newItemPerformed(AjaxRequestTarget target, AssignmentObjectRelati @Override protected List createNewButtonDescription() { - List buttonDtoList = new ArrayList<>(); - if (AssignmentPanel.this.getModelObject() == null) { - return null; - } - if (isInducement()) { - return null; - } - - List relations = getAssignmentObjectRelationList(); -// List relations = WebComponentUtil.divideAssignmentRelationsByAllValues(loadAssignmentTargetRelationsList()); - if (relations == null) { - return null; - } - - - relations.forEach(relation -> { - MultiFunctinalButtonDto buttonDto = new MultiFunctinalButtonDto(); - buttonDto.setAssignmentObjectRelation(relation); - - DisplayType additionalButtonDisplayType = WebComponentUtil.getAssignmentObjectRelationDisplayType(AssignmentPanel.this.getPageBase(), relation, - isInducement() ? "AssignmentPanel.newInducementTitle" : "AssignmentPanel.newAssignmentTitle"); - buttonDto.setAdditionalButtonDisplayType(additionalButtonDisplayType); - - CompositedIconBuilder builder = WebComponentUtil.getAssignmentRelationIconBuilder(AssignmentPanel.this.getPageBase(), relation, - additionalButtonDisplayType.getIcon(), WebComponentUtil.createIconType(GuiStyleConstants.EVO_ASSIGNMENT_ICON, "green")); - CompositedIcon icon = null; - if (builder != null) { - icon = builder.build(); - } - buttonDto.setCompositedIcon(icon); - buttonDtoList.add(buttonDto); - }); - return buttonDtoList; + return newButtonDescription(); } @@ -255,6 +225,41 @@ protected WebMarkupContainer initButtonToolbar(String id) { setOutputMarkupId(true); } + private List newButtonDescription() { + List buttonDtoList = new ArrayList<>(); + if (AssignmentPanel.this.getModelObject() == null) { + return null; + } + if (isInducement()) { + return null; + } + + List relations = getAssignmentObjectRelationList(); + if (relations == null) { + return null; + } + + + relations.forEach(relation -> { + MultiFunctinalButtonDto buttonDto = new MultiFunctinalButtonDto(); + buttonDto.setAssignmentObjectRelation(relation); + + DisplayType additionalButtonDisplayType = WebComponentUtil.getAssignmentObjectRelationDisplayType(AssignmentPanel.this.getPageBase(), relation, + isInducement() ? "AssignmentPanel.newInducementTitle" : "AssignmentPanel.newAssignmentTitle"); + buttonDto.setAdditionalButtonDisplayType(additionalButtonDisplayType); + + CompositedIconBuilder builder = WebComponentUtil.getAssignmentRelationIconBuilder(AssignmentPanel.this.getPageBase(), relation, + additionalButtonDisplayType.getIcon(), WebComponentUtil.createIconType(GuiStyleConstants.EVO_ASSIGNMENT_ICON, "green")); + CompositedIcon icon = null; + if (builder != null) { + icon = builder.build(); + } + buttonDto.setCompositedIcon(icon); + buttonDtoList.add(buttonDto); + }); + return buttonDtoList; + } + private List getAssignmentObjectRelationList() { if (AssignmentPanel.this.getModelObject() == null){ return null; @@ -588,12 +593,12 @@ protected boolean isEntitlementAssignment(){ } protected void addSelectedAssignmentsPerformed(AjaxRequestTarget target, List newAssignmentsList){ - if (newAssignmentsList == null || newAssignmentsList.isEmpty()) { + if (CollectionUtils.isEmpty(newAssignmentsList)) { warn(getParentPage().getString("AssignmentTablePanel.message.noAssignmentSelected")); target.add(getPageBase().getFeedbackPanel()); return; } - boolean isAssignmentsLimitReached = isAssignmentsLimitReached(newAssignmentsList != null ? newAssignmentsList.size() : 0, true); + boolean isAssignmentsLimitReached = isAssignmentsLimitReached(newAssignmentsList.size(), true); if (isAssignmentsLimitReached) { warn(getParentPage().getString("AssignmentPanel.assignmentsLimitReachedWarning", assignmentsRequestsLimit)); target.add(getPageBase().getFeedbackPanel()); @@ -645,6 +650,11 @@ protected void addBasicContainerValuePanel(String idPanel) { add(getBasicContainerPanel(idPanel, item.getModel())); } + @Override + protected boolean getBasicTabEditability(ItemWrapper itemWrapper) { + return getContainerReadability(itemWrapper); + } + @Override protected DisplayNamePanel createDisplayNamePanel(String displayNamePanelId) { IModel displayNameModel = getDisplayModel(item.getModelObject().getRealValue()); @@ -759,32 +769,28 @@ private QName getRelationForDisplayNamePanel(PrismContainerValueWrapper getKindIntentLabelModelForDisplayNamePanel(PrismContainerValueWrapper modelObject) { AssignmentType assignment = modelObject.getRealValue(); if (assignment.getConstruction() != null){ - ShadowKindType kind = assignment.getConstruction().getKind(); - if (kind == null) { - kind = ShadowKindType.ACCOUNT; - } - String intent = assignment.getConstruction().getIntent(); - if (StringUtils.isEmpty(intent)) { - intent = "default"; + PrismContainerValueWrapper constructionValue = null; + try { + PrismContainerWrapper construction = modelObject.findContainer(AssignmentType.F_CONSTRUCTION); + constructionValue = construction.getValue(); + } catch (SchemaException e) { + LOGGER.error("Unexpected problem during construction wrapper lookup, {}", e.getMessage(), e); + } + ShadowKindType kind; + String intent; + if (constructionValue instanceof ConstructionValueWrapper) { + kind = ((ConstructionValueWrapper) constructionValue).getKind(); + intent = ((ConstructionValueWrapper) constructionValue).getIntent(); + } else { + kind = assignment.getConstruction().getKind(); + intent = assignment.getConstruction().getIntent(); } + return createStringResource("DisplayNamePanel.kindIntentLabel", kind, intent); } return Model.of(); } - //TODO add to basic visibility - private ItemVisibility getActivationVisibileItems(ItemPath pathToCheck, ItemPath assignmentPath) { - if (assignmentPath.append(ItemPath.create(AssignmentType.F_ACTIVATION, ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP)).equivalent(pathToCheck)) { - return ItemVisibility.HIDDEN; - } - - if (assignmentPath.append(ItemPath.create(AssignmentType.F_ACTIVATION, ActivationType.F_LOCKOUT_STATUS)).equivalent(pathToCheck)) { - return ItemVisibility.HIDDEN; - } - - return ItemVisibility.AUTO; - } - private List mergeNewAssignmentTargetTypeLists(List allowedByAssignmentTargetSpecification, List availableTypesList){ if (CollectionUtils.isEmpty(allowedByAssignmentTargetSpecification)){ return availableTypesList; @@ -805,6 +811,7 @@ private List mergeNewAssignmentTargetTypeLists(List allowedB return mergedList; } + @SuppressWarnings("unchecked") private IModel getDisplayModel(AssignmentType assignment){ return (IModel) () -> { if (assignment.getTargetRef() != null && assignment.getTargetRef().getOid() != null) { @@ -816,7 +823,10 @@ private IModel getDisplayModel(AssignmentType assig if (assignment.getConstruction() != null && assignment.getConstruction().getResourceRef() != null) { Task task = getPageBase().createSimpleTask("Load resource"); OperationResult result = task.getResult(); - return (C) WebModelServiceUtils.loadObject(assignment.getConstruction().getResourceRef(), getPageBase(), task, result).asObjectable(); + PrismObject object = WebModelServiceUtils.loadObject(assignment.getConstruction().getResourceRef(), getPageBase(), task, result); + if (object != null) { + return (C) object.asObjectable(); + } } else if (assignment.getPersonaConstruction() != null) { return (C) assignment.getPersonaConstruction(); } else if (assignment.getPolicyRule() !=null) { @@ -834,7 +844,7 @@ private List getAssignmentMenu AuthorizationPhaseType.REQUEST, obj, null, null, null); if (isUnassignAuthorized) { - menuItems.add(new ButtonInlineMenuItem(getAssignmentsLimitReachedTitleModel("pageAdminFocus.menu.unassign")) { + menuItems.add(new ButtonInlineMenuItem(getAssignmentsLimitReachedUnassignTitleModel()) { private static final long serialVersionUID = 1L; @Override @@ -901,7 +911,7 @@ public void onClick(AjaxRequestTarget target) { targetRef = assignmentContainer.findReference(ItemPath.create(AssignmentType.F_TARGET_REF)); } catch (SchemaException e) { getSession().error("Couldn't show details page. More information provided in log."); - LOGGER.error("Couldn't show detials page, no targetRef reference wrapper found: {}", e.getMessage(), e); + LOGGER.error("Couldn't show details page, no targetRef reference wrapper found: {}", e.getMessage(), e); target.add(getPageBase().getFeedbackPanel()); return; } @@ -925,11 +935,11 @@ public boolean isHeaderMenuItem(){ return menuItems; } + @SuppressWarnings("unchecked") protected MultivalueContainerListPanelWithDetailsPanel getMultivalueContainerListPanel() { - return ((MultivalueContainerListPanelWithDetailsPanel)get(ID_ASSIGNMENTS)); + return ((MultivalueContainerListPanelWithDetailsPanel) get(ID_ASSIGNMENTS)); } - //TODO override for each type ? protected TableId getTableId() { return UserProfileStorage.TableId.ASSIGNMENTS_TAB_TABLE; } @@ -938,13 +948,13 @@ protected PageBase getParentPage() { return getPageBase(); } - private IModel getAssignmentsLimitReachedTitleModel(String defaultTitleKey) { + private IModel getAssignmentsLimitReachedUnassignTitleModel() { return new LoadableModel(true) { @Override protected String load() { return isAssignmentsLimitReached() ? AssignmentPanel.this.getPageBase().createStringResource("RoleCatalogItemButton.assignmentsLimitReachedTitle", - assignmentsRequestsLimit).getString() : createStringResource(defaultTitleKey).getString(); + assignmentsRequestsLimit).getString() : createStringResource("pageAdminFocus.menu.unassign").getString(); } }; } @@ -953,6 +963,7 @@ protected boolean isAssignmentsLimitReached() { return isAssignmentsLimitReached(0, false); } + @SuppressWarnings("deprecation") protected boolean isAssignmentsLimitReached(int selectedAssignmentsCount, boolean actionPerformed) { if (assignmentsRequestsLimit < 0){ return false; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/ConstructionAssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/ConstructionAssignmentPanel.java index f76a0aadec6..2469ad4b937 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/ConstructionAssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/ConstructionAssignmentPanel.java @@ -39,14 +39,8 @@ public ConstructionAssignmentPanel(String id, IModel createSearchableItems(PrismContainerDefinition containerDef) { - List defs = new ArrayList<>(); - + List defs = super.createSearchableItems(containerDef); SearchFactory.addSearchRefDef(containerDef, ItemPath.create(AssignmentType.F_CONSTRUCTION, ConstructionType.F_RESOURCE_REF), defs, AreaCategoryType.ADMINISTRATION, getPageBase()); - SearchFactory.addSearchPropertyDef(containerDef, ItemPath.create(AssignmentType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS), defs); - SearchFactory.addSearchPropertyDef(containerDef, ItemPath.create(AssignmentType.F_ACTIVATION, ActivationType.F_EFFECTIVE_STATUS), defs); - - defs.addAll(SearchFactory.createExtensionDefinitionList(containerDef)); - return defs; } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/FocusMappingsAssignmentPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/FocusMappingsAssignmentPanel.java index 06fb24f601a..e6c101328bc 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/FocusMappingsAssignmentPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/FocusMappingsAssignmentPanel.java @@ -79,8 +79,4 @@ protected ItemVisibility getTypedContainerVisibility(ItemWrapper wra return ItemVisibility.AUTO; } - @Override - protected boolean getContainerReadability(ItemWrapper wrapper) { - return false; - } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java index 83b821f460c..27e1c0f83f9 100755 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/PolicyRulesPanel.java @@ -7,20 +7,14 @@ package com.evolveum.midpoint.web.component.assignment; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; -import com.evolveum.midpoint.gui.api.prism.ItemWrapper; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.web.component.prism.ItemVisibility; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.model.IModel; +import com.evolveum.midpoint.gui.api.prism.ItemWrapper; import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; import com.evolveum.midpoint.gui.impl.component.data.column.AbstractItemWrapperColumn.ColumnType; import com.evolveum.midpoint.gui.impl.component.data.column.PrismContainerWrapperColumn; @@ -31,13 +25,16 @@ import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.web.component.prism.ItemVisibility; import com.evolveum.midpoint.web.component.search.SearchFactory; import com.evolveum.midpoint.web.component.search.SearchItemDefinition; import com.evolveum.midpoint.web.session.UserProfileStorage; import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; /** * Created by honchar. From 3a1000d70760627e4ba4a97f03c8455cd0307ef6 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Thu, 16 Apr 2020 15:19:11 +0200 Subject: [PATCH 04/27] fix for MID-6214 --- .../midpoint/web/page/admin/reports/PageAuditLogDetails.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageAuditLogDetails.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageAuditLogDetails.java index d2618e8033e..5cef6076fd9 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageAuditLogDetails.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/reports/PageAuditLogDetails.java @@ -9,6 +9,7 @@ import java.util.*; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.prism.Referencable; import com.evolveum.midpoint.web.component.AjaxButton; import com.evolveum.midpoint.web.component.data.BoxedTablePanel; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; @@ -491,7 +492,8 @@ private Collection connectDeltas(List Date: Thu, 16 Apr 2020 15:21:56 +0200 Subject: [PATCH 05/27] Fix model->workflow dependency When implementing cascaded deletion of cases (MID-6183) I have introduced a runtime dependency of model-impl on workflow-impl. This is a problem e.g. for tests. Here the dependency is relaxed. --- .../com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java index 3c135b35322..b242c00360e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java @@ -102,7 +102,7 @@ public class ChangeExecutor { private static final String OPERATION_UPDATE_SITUATION_IN_SHADOW = ChangeExecutor.class.getName() + ".updateSituationInShadow"; @Autowired private TaskManager taskManager; - @Autowired private WorkflowManager workflowManager; + @Autowired(required = false) private WorkflowManager workflowManager; // not available e.g. during tests @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; @Autowired private ProvisioningService provisioning; @Autowired private PrismContext prismContext; @@ -1420,7 +1420,7 @@ private PrismObject executeDelet taskManager.deleteTask(oid, result); } else if (NodeType.class.isAssignableFrom(objectTypeClass)) { taskManager.deleteNode(oid, result); - } else if (CaseType.class.isAssignableFrom(objectTypeClass)) { + } else if (workflowManager != null && CaseType.class.isAssignableFrom(objectTypeClass)) { workflowManager.deleteCase(oid, task, result); } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options, From 9e2d3049f4bc24dccdcbb9d5da9faadb8052884d Mon Sep 17 00:00:00 2001 From: kate Date: Thu, 16 Apr 2020 16:51:12 +0200 Subject: [PATCH 06/27] approval tab -> unifying display names and names --- .../page/admin/workflow/dto/DecisionDto.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/DecisionDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/DecisionDto.java index 568c9fb19e6..d07c99f9c32 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/DecisionDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/DecisionDto.java @@ -15,9 +15,11 @@ import com.evolveum.midpoint.web.component.util.Selectable; import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.collections.CollectionUtils; +import java.util.stream.Collectors; + import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -84,7 +86,7 @@ public Integer getEscalationLevelNumber() { // if pageBase is null, references will not be resolved @Nullable - public static DecisionDto create(CaseEventType e, @Nullable PageBase pageBase) { + public static DecisionDto create(CaseEventType e, @Nullable PageBase pageBase) { // we want to show user decisions, automatic decisions and delegations DecisionDto rv = new DecisionDto(); @@ -125,9 +127,10 @@ public static DecisionDto create(CaseEventType e, @Nullable PageBase pageBase) { rv.comment = event.getComment(); } - String assigneeBeforeNamesList = WebComponentUtil.getReferencedObjectDisplayNamesAndNames(event.getAssigneeBefore(), false); - String assigneeAfterNamesList = WebComponentUtil.getReferencedObjectDisplayNamesAndNames(event.getDelegatedTo(), false); - rv.assigneeChange = assigneeBeforeNamesList + " -> " + assigneeAfterNamesList; + String assigneeBeforeNames = getReferencedObjectListDisplayNames(event.getAssigneeBefore(), pageBase); + String assigneeAfterNames = getReferencedObjectListDisplayNames(event.getDelegatedTo(), pageBase); + + rv.assigneeChange = assigneeBeforeNames + " -> " + assigneeAfterNames; return rv; } else if (e instanceof StageCompletionEventType) { StageCompletionEventType completion = (StageCompletionEventType) e; @@ -149,4 +152,16 @@ public static DecisionDto create(CaseEventType e, @Nullable PageBase pageBase) { return null; } } + + private static String getReferencedObjectListDisplayNames(List referencedObjects, PageBase pageBase){ + if (referencedObjects == null){ + return ""; + } + List assigneeBeforeObjectsList = WebComponentUtil.loadReferencedObjectList(referencedObjects, + pageBase.getClass().getSimpleName() + ".resolveReferenceName", pageBase); + return assigneeBeforeObjectsList + .stream() + .map(obj -> WebComponentUtil.getDisplayNameOrName(obj.asPrismObject())) + .collect(Collectors.joining(", ")); + } } From 52884c848eed01954c7da7fa91fb75a552a72706 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Thu, 16 Apr 2020 17:54:04 +0200 Subject: [PATCH 07/27] Untangle Clockwork code before fixing MID-6213 Simplifies the Clockwork code by separating various aspects (auditing, hook invocation, conflict resolution, operation execution recording) to helper classes. --- .../model/impl/lens/ChangeExecutor.java | 9 +- .../midpoint/model/impl/lens/Clockwork.java | 1046 +---------------- .../model/impl/lens/ClockworkAuditHelper.java | 279 +++++ .../impl/lens/ClockworkConflictResolver.java | 223 ++++ .../model/impl/lens/ClockworkHookHelper.java | 180 +++ .../midpoint/model/impl/lens/LensContext.java | 96 +- .../impl/lens/OperationExecutionRecorder.java | 324 +++++ .../midpoint/model/impl/util/AuditHelper.java | 91 +- 8 files changed, 1206 insertions(+), 1042 deletions(-) create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkAuditHelper.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkConflictResolver.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/OperationExecutionRecorder.java diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java index b242c00360e..e2543a7c8fc 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java @@ -113,6 +113,7 @@ public class ChangeExecutor { @Autowired private ModelObjectResolver objectResolver; @Autowired private OperationalDataManager metadataManager; @Autowired private CredentialsProcessor credentialsProcessor; + @Autowired private ClockworkConflictResolver clockworkConflictResolver; private PrismObjectDefinition userDefinition = null; private PrismObjectDefinition shadowDefinition = null; @@ -182,12 +183,8 @@ public boolean executeChanges(LensContext context, Tas executeDelta(focusDelta, focusContext, context, null, conflictResolution, null, task, subResult); if (focusDelta.isAdd() && focusDelta.getOid() != null) { - // The watcher can already exist; if the OID was pre-existing in the object. - if (context.getFocusConflictWatcher() == null) { - ConflictWatcher watcher = context - .createAndRegisterFocusConflictWatcher(focusDelta.getOid(), cacheRepositoryService); - watcher.setExpectedVersion(focusDelta.getObjectToAdd().getVersion()); - } + clockworkConflictResolver.createConflictWatcherAfterFocusAddition(context, focusDelta.getOid(), + focusDelta.getObjectToAdd().getVersion()); } subResult.computeStatus(); 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 2d606df39a9..47b24d73c9d 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 @@ -7,60 +7,29 @@ package com.evolveum.midpoint.model.impl.lens; import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.CLOCKWORK; -import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.WAITING; import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.EXITING; import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportTypeTraceOrReduced; -import static java.util.Collections.emptyList; -import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; -import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; -import java.util.Date; -import java.util.Iterator; -import java.util.List; - 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.*; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.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.audit.api.AuditEventRecord; import com.evolveum.midpoint.audit.api.AuditEventStage; -import com.evolveum.midpoint.audit.api.AuditEventType; import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.model.api.ModelAuthorizationAction; import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.ProgressInformation; import com.evolveum.midpoint.model.api.ProgressListener; -import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.model.api.context.ModelState; -import com.evolveum.midpoint.model.api.hooks.ChangeHook; import com.evolveum.midpoint.model.api.hooks.HookOperationMode; -import com.evolveum.midpoint.model.api.hooks.HookRegistry; import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.common.expression.evaluator.caching.AssociationSearchExpressionEvaluatorCache; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; -import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; import com.evolveum.midpoint.model.impl.lens.projector.Projector; import com.evolveum.midpoint.model.impl.lens.projector.focus.FocusConstraintsChecker; @@ -69,43 +38,36 @@ import com.evolveum.midpoint.model.impl.migrator.Migrator; import com.evolveum.midpoint.model.impl.sync.RecomputeTaskHandler; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.delta.ChangeType; -import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.util.CloneUtil; -import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener; import com.evolveum.midpoint.provisioning.api.ResourceOperationListener; -import com.evolveum.midpoint.repo.api.ConflictWatcher; import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; import com.evolveum.midpoint.schema.ObjectDeltaOperation; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; +import com.evolveum.midpoint.schema.cache.CacheType; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.result.OperationResultBuilder; import com.evolveum.midpoint.schema.result.OperationResultStatus; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.SystemConfigurationTypeUtil; +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.DebugUtil; import com.evolveum.midpoint.util.Holder; -import com.evolveum.midpoint.util.exception.CommonException; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LevelOverrideTurboFilter; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.util.logging.TracingAppender; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.query_3.QueryType; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; @@ -116,30 +78,20 @@ @Component public class Clockwork { - private static final int DEFAULT_NUMBER_OF_RESULTS_TO_KEEP = 5; - - private static final int DEFAULT_MAX_CONFLICT_RESOLUTION_ATTEMPTS = 1; // synchronize with common-core-3.xsd - private static final int DEFAULT_CONFLICT_RESOLUTION_DELAY_UNIT = 5000; // synchronize with common-core-3.xsd - private static final int MAX_PRECONDITION_CONFLICT_RESOLUTION_ATTEMPTS = 3; - private static final Trace LOGGER = TraceManager.getTrace(Clockwork.class); @Autowired private Projector projector; @Autowired private ContextLoader contextLoader; @Autowired private ChangeExecutor changeExecutor; - @Autowired private AuditHelper auditHelper; @Autowired private Clock clock; @Autowired private SystemObjectCache systemObjectCache; @Autowired private transient ProvisioningService provisioningService; @Autowired private transient ChangeNotificationDispatcher changeNotificationDispatcher; - @Autowired private ExpressionFactory expressionFactory; - @Autowired private ScriptExpressionFactory scriptExpressionFactory; @Autowired private PersonaProcessor personaProcessor; @Autowired private PrismContext prismContext; @Autowired private TaskManager taskManager; @Autowired private Tracer tracer; @Autowired private OperationalDataManager metadataManager; - @Autowired private ContextFactory contextFactory; @Autowired private Migrator migrator; @Autowired private ClockworkMedic medic; @Autowired private PolicyRuleScriptExecutor policyRuleScriptExecutor; @@ -147,9 +99,10 @@ public class Clockwork { @Autowired private ClockworkAuthorizationHelper clockworkAuthorizationHelper; @Autowired private CacheConfigurationManager cacheConfigurationManager; @Autowired private SecurityEnforcer securityEnforcer; - - @Autowired(required = false) - private HookRegistry hookRegistry; + @Autowired private OperationExecutionRecorder operationExecutionRecorder; + @Autowired private ClockworkAuditHelper clockworkAuditHelper; + @Autowired private ClockworkHookHelper clockworkHookHelper; + @Autowired private ClockworkConflictResolver clockworkConflictResolver; @Autowired @Qualifier("cacheRepositoryService") @@ -167,7 +120,7 @@ public HookOperationMode run(LensContext context, Task ClockworkRunTraceType trace = null; try { if (result.isTraced()) { - trace = recordTraceAtStart(context, task, result); + trace = recordTraceAtStart(context, result); } LOGGER.trace("Running clockwork for context {}", context); @@ -176,15 +129,12 @@ public HookOperationMode run(LensContext context, Task } int clicked = 0; - boolean focusConflictPresent = false; - ConflictResolutionType conflictResolutionPolicy = null; + ClockworkConflictResolver.Context conflictResolutionContext = new ClockworkConflictResolver.Context(); HookOperationMode finalMode; try { context.reportProgress(new ProgressInformation(CLOCKWORK, ENTERING)); - if (context.getFocusContext() != null && context.getFocusContext().getOid() != null) { - context.createAndRegisterFocusConflictWatcher(context.getFocusContext().getOid(), repositoryService); - } + clockworkConflictResolver.createConflictWatcherOnStart(context); FocusConstraintsChecker .enterCache(cacheConfigurationManager.getConfiguration(CacheType.LOCAL_FOCUS_CONSTRAINT_CHECKER_CACHE)); enterAssociationSearchExpressionEvaluatorCache(); @@ -193,8 +143,7 @@ public HookOperationMode run(LensContext context, Task while (context.getState() != ModelState.FINAL) { - // TODO implement in model context (as transient or even non-transient attribute) to allow for checking in more complex scenarios - int maxClicks = getMaxClicks(context, result); + int maxClicks = getMaxClicks(result); if (clicked >= maxClicks) { throw new IllegalStateException( "Model operation took too many clicks (limit is " + maxClicks + "). Is there a cycle?"); @@ -213,11 +162,10 @@ public HookOperationMode run(LensContext context, Task // One last click in FINAL state finalMode = click(context, task, result); if (finalMode == HookOperationMode.FOREGROUND) { - conflictResolutionPolicy = ModelImplUtils.getConflictResolution(context); - focusConflictPresent = checkFocusConflicts(context, conflictResolutionPolicy, task, result); + clockworkConflictResolver.checkFocusConflicts(context, conflictResolutionContext, result); } } finally { - context.unregisterConflictWatchers(repositoryService); + clockworkConflictResolver.unregisterConflictWatcher(context); FocusConstraintsChecker.exitCache(); //exitDefaultSearchExpressionEvaluatorCache(); exitAssociationSearchExpressionEvaluatorCache(); @@ -226,12 +174,7 @@ public HookOperationMode run(LensContext context, Task } // intentionally outside the "try-finally" block to start with clean caches - if (focusConflictPresent) { - assert finalMode == HookOperationMode.FOREGROUND; - finalMode = resolveFocusConflict(context, conflictResolutionPolicy, task, result); - } else if (context.getConflictResolutionAttemptNumber() > 0) { - LOGGER.info("Resolved update conflict on attempt number {}", context.getConflictResolutionAttemptNumber()); - } + finalMode = clockworkConflictResolver.resolveFocusConflictIfPresent(context, conflictResolutionContext, finalMode, task, result); result.computeStatusIfUnknown(); return finalMode; } catch (Throwable t) { @@ -241,8 +184,8 @@ public HookOperationMode run(LensContext context, Task recordTraceAtEnd(context, trace, result); if (tracingRequested) { tracer.storeTrace(task, result, parentResult); - TracingAppender.terminateCollecting(); // todo reconsider - LevelOverrideTurboFilter.cancelLoggingOverride(); // todo reconsider + TracingAppender.terminateCollecting(); // reconsider + LevelOverrideTurboFilter.cancelLoggingOverride(); // reconsider } result.computeStatusIfUnknown(); } @@ -270,7 +213,7 @@ private boolean startTracingIfRequested(LensContext co } } - private ClockworkRunTraceType recordTraceAtStart(LensContext context, Task task, + private ClockworkRunTraceType recordTraceAtStart(LensContext context, OperationResult result) throws SchemaException { ClockworkRunTraceType trace = new ClockworkRunTraceType(prismContext); trace.setInputLensContextText(context.debugDump()); @@ -298,18 +241,11 @@ public LensContext previewChanges(LensContext conte try { context.setPreview(true); -// context.setOptions(options); LOGGER.trace("Preview changes context:\n{}", context.debugDumpLazily()); context.setProgressListeners(listeners); projector.projectAllWaves(context, "preview", task, result); - - if (hookRegistry != null) { - for (ChangeHook hook : hookRegistry.getAllChangeHooks()) { - hook.invokePreview(context, task, result); - } - } - + clockworkHookHelper.invokePreview(context, task, result); policyRuleSuspendTaskExecutor.execute(context, task, result); } catch (ConfigurationException | SecurityViolationException | ObjectNotFoundException | SchemaException | @@ -325,12 +261,9 @@ public LensContext previewChanges(LensContext conte // ... and we do not really need that in 3.6.1 // TODO: expose PreconditionViolationException in 3.7 throw new SystemException(e); - } - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Preview changes output:\n{}", context.debugDump()); - } + LOGGER.debug("Preview changes output:\n{}", context.debugDumpLazily()); result.computeStatus(); result.cleanupResult(); @@ -338,142 +271,6 @@ public LensContext previewChanges(LensContext conte return context; } - private boolean checkFocusConflicts(LensContext context, ConflictResolutionType resolutionPolicy, - Task task, OperationResult result) { - ConflictWatcher watcher = context.getFocusConflictWatcher(); - if (watcher != null && resolutionPolicy != null && resolutionPolicy.getAction() != ConflictResolutionActionType.NONE && - repositoryService.hasConflict(watcher, result)) { - LOGGER.debug("Found modify-modify conflict on {}", watcher); - return true; - } else { - return false; - } - } - - private HookOperationMode resolveFocusConflict(LensContext context, - ConflictResolutionType resolutionPolicy, Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, ConfigurationException, - CommunicationException, SecurityViolationException, PolicyViolationException, ObjectAlreadyExistsException { - if (resolutionPolicy == null || resolutionPolicy.getAction() == ConflictResolutionActionType.NONE) { - return HookOperationMode.FOREGROUND; - } - PrismObject focusObject = context.getFocusContext() != null ? context.getFocusContext().getObjectAny() : null; - ModelExecuteOptions options = new ModelExecuteOptions(); - switch (resolutionPolicy.getAction()) { - case FAIL: throw new SystemException("Conflict detected while updating " + focusObject); - case LOG: - LOGGER.warn("Conflict detected while updating {}", focusObject); - return HookOperationMode.FOREGROUND; - case RECOMPUTE: - break; - case RECONCILE: - options.setReconcile(); - break; - default: - throw new IllegalStateException("Unsupported conflict resolution action: " + resolutionPolicy.getAction()); - } - - // so, recompute is the action - LOGGER.debug("CONFLICT: Conflict detected while updating {}, recomputing (options={})", focusObject, options); - - if (context.getFocusContext() == null) { - LOGGER.warn("No focus context, not possible to resolve conflict by focus recomputation"); // should really never occur - return HookOperationMode.FOREGROUND; - } - String oid = context.getFocusContext().getOid(); - if (oid == null) { - LOGGER.warn("No focus OID, not possible to resolve conflict by focus recomputation"); // should really never occur - return HookOperationMode.FOREGROUND; - } - Class focusClass = context.getFocusContext().getObjectTypeClass(); - if (focusClass == null) { - LOGGER.warn("Focus class not known, not possible to resolve conflict by focus recomputation"); // should really never occur - return HookOperationMode.FOREGROUND; - } - if (TaskType.class.isAssignableFrom(focusClass)) { - return HookOperationMode.FOREGROUND; // this is actually quite expected, so don't bother anyone with that - } - if (!FocusType.class.isAssignableFrom(focusClass)) { - LOGGER.warn("Focus is not of FocusType (it is {}); not possible to resolve conflict by focus recomputation", focusClass.getName()); - return HookOperationMode.FOREGROUND; - } - - ConflictResolutionType focusConflictResolution = new ConflictResolutionType(); - focusConflictResolution.setAction(ConflictResolutionActionType.ERROR); - options.setFocusConflictResolution(focusConflictResolution); - - int preconditionAttempts = 0; - while (true) { - - int attemptOld = context.getConflictResolutionAttemptNumber(); - int attemptNew = attemptOld + 1; - boolean shouldExecuteAttempt = shouldExecuteAttempt(context, resolutionPolicy, attemptNew); - if (!shouldExecuteAttempt) { - LOGGER.warn("CONFLICT: Couldn't resolve conflict even after {} resolution attempt(s), giving up.", attemptOld); - return HookOperationMode.FOREGROUND; - } - - delay(context, resolutionPolicy, attemptNew + preconditionAttempts); - - PrismObject focus = repositoryService.getObject(focusClass, oid, null, result); - LensContext contextNew = contextFactory.createRecomputeContext(focus, options, task, result); - contextNew.setProgressListeners(new ArrayList<>(emptyIfNull(context.getProgressListeners()))); - contextNew.setConflictResolutionAttemptNumber(attemptNew); - - LOGGER.debug("CONFLICT: Recomputing {} as reaction to conflict (options={}, attempts={},{}, readVersion={})", - context.getFocusContext().getHumanReadableName(), options, attemptNew, preconditionAttempts, contextNew.getFocusContext().getObjectReadVersion()); - - try { - - // this is a recursion; but limited to max attempts which should not be a large number - HookOperationMode hookOperationMode = run(contextNew, task, result); - - // This may be in fact a giveup after recompute that was not able to cleanly proceed. - LOGGER.debug("CONFLICT: Clean recompute (or giveup) of {} achieved (options={}, attempts={},{})", - context.getFocusContext().getHumanReadableName(), options, attemptNew, preconditionAttempts); - - return hookOperationMode; - - } catch (PreconditionViolationException e) { - preconditionAttempts++; - LOGGER.debug("CONFLICT: Recompute precondition failed (attempt {}, precondition attempt {}), trying again", attemptNew, preconditionAttempts); - if (preconditionAttempts < MAX_PRECONDITION_CONFLICT_RESOLUTION_ATTEMPTS) { - continue; - } - LOGGER.warn("CONFLICT: Couldn't resolve conflict even after {} resolution attempt(s) and {} precondition attempts, giving up.", - attemptOld, preconditionAttempts); - return HookOperationMode.FOREGROUND; - } - } - } - - private boolean shouldExecuteAttempt(LensContext context, @NotNull ConflictResolutionType resolutionPolicy, int attemptNew) { - int maxAttempts = defaultIfNull(resolutionPolicy.getMaxAttempts(), DEFAULT_MAX_CONFLICT_RESOLUTION_ATTEMPTS); - if (attemptNew > maxAttempts) { - return false; - } - return true; - } - - private void delay(LensContext context, @NotNull ConflictResolutionType resolutionPolicy, int attempts) { - long delayUnit = defaultIfNull(resolutionPolicy.getDelayUnit(), DEFAULT_CONFLICT_RESOLUTION_DELAY_UNIT); - for (int i = 0; i < attempts; i++) { - delayUnit *= 2; - } - long delay = (long) (Math.random() * delayUnit); - String message = "CONFLICT: Waiting "+delay+" milliseconds before starting conflict resolution (delay exponent: "+attempts+")"; - // TODO convey information about waiting time after some GUI mechanism for displaying it is available - // (showing text messages is currently really ugly) - context.reportProgress(new ProgressInformation(WAITING, EXITING)); - LOGGER.debug(message); - try { - Thread.sleep(delay); - } catch (InterruptedException e) { - // ignore - } - context.reportProgress(new ProgressInformation(WAITING, EXITING)); - } - private void enterAssociationSearchExpressionEvaluatorCache() { AssociationSearchExpressionEvaluatorCache cache = AssociationSearchExpressionEvaluatorCache.enterCache( cacheConfigurationManager.getConfiguration(CacheType.LOCAL_ASSOCIATION_TARGET_SEARCH_EVALUATOR_CACHE)); @@ -503,15 +300,17 @@ private void exitAssociationSearchExpressionEvaluatorCache() { } } + @SuppressWarnings("unused") private void enterDefaultSearchExpressionEvaluatorCache() { //DefaultSearchExpressionEvaluatorCache.enterCache(cacheConfigurationManager.getConfiguration(CacheType.LOCAL_DEFAULT_SEARCH_EVALUATOR_CACHE)); } + @SuppressWarnings("unused") private void exitDefaultSearchExpressionEvaluatorCache() { //DefaultSearchExpressionEvaluatorCache.exitCache(); } - private int getMaxClicks(LensContext context, OperationResult result) throws SchemaException, ObjectNotFoundException { + private int getMaxClicks(OperationResult result) throws SchemaException { PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); Integer maxClicks = SystemConfigurationTypeUtil.getMaxModelClicks(systemConfiguration); if (maxClicks == null) { @@ -566,7 +365,7 @@ public HookOperationMode click(LensContext context, Ta context.generateRequestIdentifierIfNeeded(); // We need to do this BEFORE projection. If we would do that after projection // there will be secondary changes that are not part of the request. - audit(context, AuditEventStage.REQUEST, task, result, parentResult); // we need to take the overall ("run" operation result) not the current one + clockworkAuditHelper.audit(context, AuditEventStage.REQUEST, task, result, parentResult); // we need to take the overall ("run" operation result) not the current one } boolean recompute = false; @@ -608,24 +407,26 @@ public HookOperationMode click(LensContext context, Ta context.checkEncrypted(); } - // LOGGER.info("CLOCKWORK: {}: {}", state, context); - switch (state) { case INITIAL: - processInitialToPrimary(context, task, result); + processInitialToPrimary(context); break; case PRIMARY: processPrimaryToSecondary(context, task, result); break; case SECONDARY: - processSecondary(context, task, result, parentResult); + if (context.getExecutionWave() > context.getMaxWave() + 1) { + processSecondaryToFinal(context, task, result); + } else { + processSecondary(context, task, result, parentResult); + } break; case FINAL: HookOperationMode mode = processFinal(context, task, result, parentResult); medic.clockworkFinish(context); return mode; } - return invokeHooks(context, task, result); + return clockworkHookHelper.invokeHooks(context, task, result); } catch (CommunicationException | ConfigurationException | ExpressionEvaluationException | ObjectNotFoundException | PolicyViolationException | SchemaException | SecurityViolationException | RuntimeException | Error | @@ -644,137 +445,12 @@ public HookOperationMode click(LensContext context, Ta } } - /** - * Invokes hooks, if there are any. - * - * @return - * - ERROR, if any hook reported error; otherwise returns - * - BACKGROUND, if any hook reported switching to background; otherwise - * - FOREGROUND (if all hooks reported finishing on foreground) - */ - private HookOperationMode invokeHooks(LensContext context, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - // TODO: following two parts should be merged together in later versions - - // Execute configured scripting hooks - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - // systemConfiguration may be null in some tests - if (systemConfiguration != null) { - ModelHooksType modelHooks = systemConfiguration.asObjectable().getModelHooks(); - if (modelHooks != null) { - HookListType changeHooks = modelHooks.getChange(); - if (changeHooks != null) { - for (HookType hookType: changeHooks.getHook()) { - String shortDesc; - if (hookType.getName() != null) { - shortDesc = "hook '"+hookType.getName()+"'"; - } else { - shortDesc = "scripting hook in system configuration"; - } - if (hookType.isEnabled() != null && !hookType.isEnabled()) { - // Disabled hook, skip - continue; - } - if (hookType.getState() != null) { - if (!context.getState().toModelStateType().equals(hookType.getState())) { - continue; - } - } - if (hookType.getFocusType() != null) { - if (context.getFocusContext() == null) { - continue; - } - QName hookFocusTypeQname = hookType.getFocusType(); - ObjectTypes hookFocusType = ObjectTypes.getObjectTypeFromTypeQName(hookFocusTypeQname); - if (hookFocusType == null) { - throw new SchemaException("Unknown focus type QName "+hookFocusTypeQname+" in "+shortDesc); - } - Class focusClass = context.getFocusClass(); - Class hookFocusClass = hookFocusType.getClassDefinition(); - if (!hookFocusClass.isAssignableFrom(focusClass)) { - continue; - } - } - - ScriptExpressionEvaluatorType scriptExpressionEvaluatorType = hookType.getScript(); - if (scriptExpressionEvaluatorType == null) { - continue; - } - try { - evaluateScriptingHook(context, hookType, scriptExpressionEvaluatorType, shortDesc, task, result); - } catch (ExpressionEvaluationException e) { - LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); - throw new ExpressionEvaluationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); - } catch (ObjectNotFoundException e) { - LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); - throw new ObjectNotFoundException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); - } catch (SchemaException e) { - LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); - throw new SchemaException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); - } catch (CommunicationException e) { - LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); - throw new CommunicationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); - } catch (ConfigurationException e) { - LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); - throw new ConfigurationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); - } catch (SecurityViolationException e) { - LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); - throw new SecurityViolationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); - } - } - } - } - } - - // Execute registered Java hooks - HookOperationMode resultMode = HookOperationMode.FOREGROUND; - if (hookRegistry != null) { - for (ChangeHook hook : hookRegistry.getAllChangeHooks()) { - HookOperationMode mode = hook.invoke(context, task, result); - if (mode == HookOperationMode.ERROR) { - resultMode = HookOperationMode.ERROR; - } else if (mode == HookOperationMode.BACKGROUND) { - if (resultMode != HookOperationMode.ERROR) { - resultMode = HookOperationMode.BACKGROUND; - } - } - } - } - return resultMode; - } - - - private void evaluateScriptingHook(LensContext context, HookType hookType, - ScriptExpressionEvaluatorType scriptExpressionEvaluatorType, String shortDesc, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - - LOGGER.trace("Evaluating {}", shortDesc); - // TODO: it would be nice to cache this - // null output definition: this script has no output - ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptExpressionEvaluatorType, null, context.getPrivilegedExpressionProfile(), expressionFactory, shortDesc, task, result); - - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_PRISM_CONTEXT, prismContext, PrismContext.class); - variables.put(ExpressionConstants.VAR_MODEL_CONTEXT, context, ModelContext.class); - LensFocusContext focusContext = context.getFocusContext(); - PrismObject focus = null; - if (focusContext != null) { - variables.put(ExpressionConstants.VAR_FOCUS, focusContext.getObjectAny(), focusContext.getObjectDefinition()); - } else { - PrismObjectDefinition def = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ObjectType.class); - variables.put(ExpressionConstants.VAR_FOCUS, null, def); - } - - - ModelImplUtils.evaluateScript(scriptExpression, context, variables, false, shortDesc, task, result); - LOGGER.trace("Finished evaluation of {}", shortDesc); - } - private void switchState(LensContext context, ModelState newState) { medic.clockworkStateSwitch(context, newState); context.setState(newState); } - private void processInitialToPrimary(LensContext context, Task task, OperationResult result) { + private void processInitialToPrimary(LensContext context) { // Context loaded, nothing special do. Bump state to PRIMARY. switchState(context, ModelState.PRIMARY); } @@ -784,18 +460,13 @@ private void processPrimaryToSecondary(LensContext con switchState(context, ModelState.SECONDARY); policyRuleSuspendTaskExecutor.execute(context, task, result); - } private void processSecondary(LensContext context, Task task, OperationResult result, OperationResult overallResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException { - if (context.getExecutionWave() > context.getMaxWave() + 1) { - processSecondaryToFinal(context, task, result); - return; - } - Holder restartRequestedHolder = new Holder<>(); + Holder restartRequestedHolder = new Holder<>(false); medic.partialExecute("execution", (result1) -> { @@ -805,113 +476,31 @@ private void processSecondary(LensContext context, Tas context.getPartialProcessingOptions()::getExecution, Clockwork.class, context, result); - audit(context, AuditEventStage.EXECUTION, task, result, overallResult); + clockworkAuditHelper.audit(context, AuditEventStage.EXECUTION, task, result, overallResult); - rotContextIfNeeded(context); - - boolean restartRequested = false; - if (restartRequestedHolder.getValue() != null) { - restartRequested = restartRequestedHolder.getValue(); - } + context.rotIfNeeded(); - if (!restartRequested) { - // TODO what if restart is requested indefinitely? + if (!restartRequestedHolder.getValue()) { context.incrementExecutionWave(); } else { - // explicitly rot context? + // Shouldn't we explicitly rot context here? + // BTW, what if restart is requested indefinitely? } medic.traceContext(LOGGER, "CLOCKWORK (" + context.getState() + ")", "change execution", false, context, false); } - - private void processSecondaryToFinal(LensContext context, Task task, OperationResult result) throws PolicyViolationException { + private void processSecondaryToFinal(LensContext context, Task task, OperationResult result) { switchState(context, ModelState.FINAL); policyRuleScriptExecutor.execute(context, task, result); } - /** - * Force recompute for the next execution wave. Recompute only those contexts that were changed. - * This is more intelligent than context.rot() - */ - private void rotContextIfNeeded(LensContext context) throws SchemaException { - boolean rot = false; - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - if (projectionContext.getWave() != context.getExecutionWave()) { - LOGGER.trace("Context rot: projection {} NOT rotten because of wrong wave number", projectionContext); - continue; - } -// if (!projectionContext.isDoReconciliation()) { // meaning volatility is NONE -// LOGGER.trace("Context rot: projection {} NOT rotten because the resource is non-volatile", projectionContext); -// continue; -// } - ObjectDelta execDelta = projectionContext.getExecutableDelta(); - if (isShadowDeltaSignificant(execDelta)) { - - LOGGER.debug("Context rot: projection {} rotten because of executable delta {}", projectionContext, execDelta); - projectionContext.setFresh(false); - projectionContext.setFullShadow(false); - rot = true; - // Propagate to higher-order projections - for (LensProjectionContext relCtx: LensUtil.findRelatedContexts(context, projectionContext)) { - relCtx.setFresh(false); - relCtx.setFullShadow(false); - } - - } else { - LOGGER.trace("Context rot: projection {} NOT rotten because no delta", projectionContext); - } - } - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - ObjectDelta execDelta = focusContext.getWaveDelta(context.getExecutionWave()); - if (execDelta != null && !execDelta.isEmpty()) { - LOGGER.debug("Context rot: context rotten because of focus execution delta {}", execDelta); - rot = true; - } - if (rot) { - // It is OK to refresh focus all the time there was any change. This is cheap. - focusContext.setFresh(false); - } - //remove secondary deltas from other than execution wave - we need to recompute them.. -// cleanUpSecondaryDeltas(context); - - - } - if (rot) { - context.setFresh(false); - } - } - -// // TODO this is quite unclear. Originally here was keeping the delta from the current wave (plus delta from wave #1). -// // The reason was not clear. -// // Let us erase everything. -// private void cleanUpSecondaryDeltas(LensContext context){ -// LensFocusContext focusContext = context.getFocusContext(); -// ObjectDeltaWaves executionWaveDeltaList = focusContext.getSecondaryDeltas(); -// executionWaveDeltaList.clear(); -// } - - private

boolean isShadowDeltaSignificant(ObjectDelta

delta) { - if (delta == null || delta.isEmpty()) { - return false; - } - if (delta.isAdd() || delta.isDelete()) { - return true; - } - Collection> attrDeltas = delta.findItemDeltasSubPath(ShadowType.F_ATTRIBUTES); - if (attrDeltas != null && !attrDeltas.isEmpty()) { - return true; - } - return false; - } - private HookOperationMode processFinal(LensContext context, Task task, OperationResult result, OperationResult overallResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException { - auditFinalExecution(context, task, result, overallResult); - logFinalReadable(context, task, result); - recordOperationExecution(context, null, task, result); + clockworkAuditHelper.auditFinalExecution(context, task, result, overallResult); + logFinalReadable(context); + operationExecutionRecorder.recordOperationExecution(context, null, task, result); migrator.executeAfterOperationMigration(context, result); HookOperationMode opmode = personaProcessor.processPersonaChanges(context, task, result); @@ -922,274 +511,7 @@ private HookOperationMode processFinal(LensContext con return triggerReconcileAffected(context, task, result); } - private void recordOperationExecution(LensContext context, Throwable clockworkException, - Task task, OperationResult result) { - boolean skip = context.getInternalsConfiguration() != null && - context.getInternalsConfiguration().getOperationExecutionRecording() != null && - Boolean.TRUE.equals(context.getInternalsConfiguration().getOperationExecutionRecording().isSkip()); - if (!skip) { - XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); - try { - LOGGER.trace("recordOperationExecution starting; task = {}, clockworkException = {}", task, clockworkException); - boolean opRecordedIntoFocus = recordFocusOperationExecution(context, now, clockworkException, task, result); - for (LensProjectionContext projectionContext : context.getProjectionContexts()) { - Throwable exceptionToProjection; - if (clockworkException != null && !opRecordedIntoFocus && projectionContext.isSynchronizationSource()) { - // We need to record the exception somewhere. Because we were not able to put it into focus, - // we have to do it into sync-source projection. - exceptionToProjection = clockworkException; - } else { - exceptionToProjection = null; - } - recordProjectionOperationExecution(context, projectionContext, now, exceptionToProjection, task, result); - } - } catch (Throwable t) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't record operation execution. Model context:\n{}", t, - context.debugDump()); - // Let us ignore this for the moment. It should not have happened, sure. But it's not that crucial. - // Administrator will be able to learn about the problem from the log. - } - } else { - LOGGER.trace("Skipping operation execution recording (as set in system configuration)"); - } - } - - /** - * @return true if the operation execution was recorded (or would be recorded, but skipped because of the configuration) - */ - private boolean recordFocusOperationExecution(LensContext context, XMLGregorianCalendar now, - Throwable clockworkException, Task task, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null || focusContext.isDelete()) { - LOGGER.trace("focusContext is null or 'delete', not recording focus operation execution"); - return false; - } - PrismObject objectNew = focusContext.getObjectNew(); - Validate.notNull(objectNew, "No focus object even if the context is not of 'delete' type"); - - //noinspection unchecked - List> executedDeltas = getExecutedDeltas(focusContext, - (Class) objectNew.asObjectable().getClass(), clockworkException, result); - LOGGER.trace("recordFocusOperationExecution: executedDeltas: {}", executedDeltas.size()); - return recordOperationExecution(objectNew, false, executedDeltas, now, context.getChannel(), - getSkipWhenSuccess(context), task, result); - } - - @NotNull - private List> getExecutedDeltas(LensElementContext elementContext, - Class objectClass, Throwable clockworkException, OperationResult result) { - List> executedDeltas; - if (clockworkException != null) { - executedDeltas = new ArrayList<>(elementContext.getExecutedDeltas()); - LensObjectDeltaOperation odo = new LensObjectDeltaOperation<>(); - ObjectDelta primaryDelta = elementContext.getPrimaryDelta(); - if (primaryDelta != null) { - odo.setObjectDelta(primaryDelta); - } else { - ObjectDelta fakeDelta = prismContext.deltaFactory().object().create(objectClass, ChangeType.MODIFY); - odo.setObjectDelta(fakeDelta); - } - odo.setExecutionResult(result); // we rely on the fact that 'result' already contains record of the exception - executedDeltas.add(odo); - } else { - executedDeltas = elementContext.getExecutedDeltas(); - } - return executedDeltas; - } - - private boolean getSkipWhenSuccess(LensContext context) { - return context.getInternalsConfiguration() != null && - context.getInternalsConfiguration().getOperationExecutionRecording() != null && - Boolean.TRUE.equals(context.getInternalsConfiguration().getOperationExecutionRecording().isSkipWhenSuccess()); - } - - private void recordProjectionOperationExecution(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Throwable clockworkException, - Task task, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - PrismObject object = projectionContext.getObjectAny(); - if (object == null) { - return; // this can happen - } - List> executedDeltas = getExecutedDeltas(projectionContext, ShadowType.class, - clockworkException, result); - recordOperationExecution(object, true, executedDeltas, now, - context.getChannel(), getSkipWhenSuccess(context), task, result); - } - - /** - * @return true if the operation execution was recorded (or would be recorded, but skipped because of the configuration) - */ - private boolean recordOperationExecution(PrismObject object, boolean deletedOk, - List> executedDeltas, XMLGregorianCalendar now, - String channel, boolean skipWhenSuccess, Task task, OperationResult result) - throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { - OperationExecutionType operation = new OperationExecutionType(prismContext); - OperationResult summaryResult = new OperationResult("recordOperationExecution"); - String oid = object.getOid(); - for (LensObjectDeltaOperation deltaOperation : executedDeltas) { - operation.getOperation().add(createObjectDeltaOperation(deltaOperation)); - if (deltaOperation.getExecutionResult() != null) { - summaryResult.addSubresult(deltaOperation.getExecutionResult().clone()); // todo eliminate this clone (but beware of modifying the subresult) - } - if (oid == null && deltaOperation.getObjectDelta() != null) { - oid = deltaOperation.getObjectDelta().getOid(); - } - } - if (oid == null) { // e.g. if there is an exception in provisioning.addObject method - LOGGER.trace("recordOperationExecution: skipping because oid is null for object = {}", object); - return false; - } - summaryResult.computeStatus(); - OperationResultStatusType overallStatus = summaryResult.getStatus().createStatusType(); - setOperationContext(operation, overallStatus, now, channel, task); - storeOperationExecution(object, oid, operation, deletedOk, skipWhenSuccess, result); - return true; - } - - private void storeOperationExecution(@NotNull PrismObject object, @NotNull String oid, - @NotNull OperationExecutionType executionToAdd, boolean deletedOk, boolean skipWhenSuccess, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - Integer recordsToKeep; - Long deleteBefore; - boolean keepNoExecutions = false; - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - if (systemConfiguration != null && systemConfiguration.asObjectable().getCleanupPolicy() != null - && systemConfiguration.asObjectable().getCleanupPolicy().getObjectResults() != null) { - CleanupPolicyType policy = systemConfiguration.asObjectable().getCleanupPolicy().getObjectResults(); - recordsToKeep = policy.getMaxRecords(); - if (recordsToKeep != null && recordsToKeep == 0) { - LOGGER.trace("objectResults.recordsToKeep is 0, will skip storing operationExecutions"); - keepNoExecutions = true; - } - if (policy.getMaxAge() != null) { - XMLGregorianCalendar limit = XmlTypeConverter.addDuration( - XmlTypeConverter.createXMLGregorianCalendar(new Date()), policy.getMaxAge().negate()); - deleteBefore = XmlTypeConverter.toMillis(limit); - } else { - deleteBefore = null; - } - } else { - recordsToKeep = DEFAULT_NUMBER_OF_RESULTS_TO_KEEP; - deleteBefore = null; - } - - String taskOid = executionToAdd.getTaskRef() != null ? executionToAdd.getTaskRef().getOid() : null; - if (executionToAdd.getStatus() == OperationResultStatusType.SUCCESS && skipWhenSuccess) { - // We want to skip writing operationExecution. But let's check if there are some older non-success results - // related to the current task - if (taskOid != null) { - boolean hasNonSuccessFromCurrentTask = object.asObjectable().getOperationExecution().stream() - .anyMatch(oe -> oe.getTaskRef() != null && taskOid.equals(oe.getTaskRef().getOid()) && - oe.getStatus() != OperationResultStatusType.SUCCESS); - if (hasNonSuccessFromCurrentTask) { - LOGGER.trace("Cannot skip OperationExecution recording because there's an older non-success record from the current task"); - } else { - LOGGER.trace("Skipping OperationExecution recording because status is SUCCESS and skipWhenSuccess is true " - + "(and no older non-success records for current task {} exist)", taskOid); - return; - } - } else { - LOGGER.trace("Skipping OperationExecution recording because status is SUCCESS and skipWhenSuccess is true"); - return; - } - } - List executionsToDelete = new ArrayList<>(); - List executions = new ArrayList<>(object.asObjectable().getOperationExecution()); - // delete all executions related to current task and all old ones - for (Iterator iterator = executions.iterator(); iterator.hasNext(); ) { - OperationExecutionType execution = iterator.next(); - boolean isPreviousTaskResult = taskOid != null && execution.getTaskRef() != null && taskOid.equals(execution.getTaskRef().getOid()); - boolean isOld = deleteBefore != null && XmlTypeConverter.toMillis(execution.getTimestamp()) < deleteBefore; - if (isPreviousTaskResult || isOld) { - executionsToDelete.add(execution); - iterator.remove(); - } - } - - // delete all surplus executions - if (recordsToKeep != null && executions.size() > recordsToKeep - 1) { - if (keepNoExecutions) { - executionsToDelete.addAll(executions); - } else { - executions.sort(Comparator.nullsFirst(Comparator.comparing(e -> XmlTypeConverter.toDate(e.getTimestamp())))); - executionsToDelete.addAll(executions.subList(0, executions.size() - (recordsToKeep - 1))); - } - } - // construct and execute the delta - Class objectClass = object.asObjectable().getClass(); - List> deltas = new ArrayList<>(); - if (!keepNoExecutions) { - deltas.add(prismContext.deltaFor(objectClass) - .item(ObjectType.F_OPERATION_EXECUTION) - .add(executionToAdd) - .asItemDelta()); - } - if (!executionsToDelete.isEmpty()) { - deltas.add(prismContext.deltaFor(objectClass) - .item(ObjectType.F_OPERATION_EXECUTION) - .delete(PrismContainerValue.toPcvList(CloneUtil.cloneCollectionMembers(executionsToDelete))) - .asItemDelta()); - } - LOGGER.trace("Operation execution delta:\n{}", DebugUtil.debugDumpLazily(deltas)); - try { - if (!deltas.isEmpty()) { - repositoryService.modifyObject(objectClass, oid, deltas, result); - } - } catch (ObjectNotFoundException e) { - if (!deletedOk) { - throw e; - } else { - LOGGER.trace("Object {} deleted but this was expected.", oid); - result.deleteLastSubresultIfError(); - } - } - } - - private void setOperationContext(OperationExecutionType operation, - OperationResultStatusType overallStatus, XMLGregorianCalendar now, String channel, Task task) { - if (task instanceof RunningTask && ((RunningTask) task).getParentForLightweightAsynchronousTask() != null) { - task = ((RunningTask) task).getParentForLightweightAsynchronousTask(); - } - if (task.isPersistent()) { - operation.setTaskRef(task.getSelfReference()); - } - operation.setStatus(overallStatus); - operation.setInitiatorRef(ObjectTypeUtil.createObjectRef(task.getOwner(), prismContext)); // TODO what if the real initiator is different? (e.g. when executing approved changes) - operation.setChannel(channel); - operation.setTimestamp(now); - } - - private ObjectDeltaOperationType createObjectDeltaOperation(LensObjectDeltaOperation deltaOperation) { - ObjectDeltaOperationType odo; - try { - odo = simplifyOperation(deltaOperation).toLensObjectDeltaOperationType().getObjectDeltaOperation(); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't create operation information", e); - odo = new ObjectDeltaOperationType(); - OperationResult r = new OperationResult(Clockwork.class.getName() + ".createObjectDeltaOperation"); - r.recordFatalError("Couldn't create operation information: " + e.getMessage(), e); - odo.setExecutionResult(r.createOperationResultType()); - } - return odo; - } - - private LensObjectDeltaOperation simplifyOperation(ObjectDeltaOperation operation) { - LensObjectDeltaOperation rv = new LensObjectDeltaOperation<>(); - rv.setObjectDelta(simplifyDelta(operation.getObjectDelta())); - rv.setExecutionResult(OperationResult.keepRootOnly(operation.getExecutionResult())); - rv.setObjectName(operation.getObjectName()); - rv.setResourceName(operation.getResourceName()); - rv.setResourceOid(operation.getResourceOid()); - return rv; - } - - private ObjectDelta simplifyDelta(ObjectDelta delta) { - return prismContext.deltaFactory().object().create(delta.getObjectTypeClass(), delta.getChangeType()); - } - private HookOperationMode triggerReconcileAffected(LensContext context, Task task, OperationResult result) throws SchemaException { - // check applicability if (!ModelExecuteOptions.isReconcileAffected(context.getOptions())) { return HookOperationMode.FOREGROUND; } @@ -1202,9 +524,9 @@ private HookOperationMode triggerReconcileAffected(LensCo if (context.getFocusContext() == null) { throw new IllegalStateException("No focus context when expected it"); } - PrismObject role = (PrismObject) context.getFocusContext().getObjectAny(); + PrismObject role = context.getFocusContext().getObjectAny(); if (role == null) { - throw new IllegalStateException("No role when expected it"); + throw new IllegalStateException("No focus object when expected it"); } // preparing the recompute/reconciliation task @@ -1226,6 +548,7 @@ private HookOperationMode triggerReconcileAffected(LensCo SearchFilterType filterType = prismContext.getQueryConverter().createSearchFilterType(refFilter); QueryType queryType = new QueryType(); queryType.setFilter(filterType); + //noinspection unchecked PrismProperty property = propertyDef.instantiate(); property.setRealValue(queryType); reconTask.addExtensionProperty(property); @@ -1242,263 +565,20 @@ private HookOperationMode triggerReconcileAffected(LensCo return HookOperationMode.BACKGROUND; } - // "overallResult" covers the whole clockwork run - // while "result" is - most of the time - related to the current clockwork click - private void audit(LensContext context, AuditEventStage stage, Task task, OperationResult result, OperationResult overallResult) throws SchemaException { - if (context.isLazyAuditRequest()) { - if (stage == AuditEventStage.REQUEST) { - // We skip auditing here, we will do it before execution - } else if (stage == AuditEventStage.EXECUTION) { - Collection> unauditedExecutedDeltas = context.getUnauditedExecutedDeltas(); - if ((unauditedExecutedDeltas == null || unauditedExecutedDeltas.isEmpty())) { - // No deltas, nothing to audit in this wave - return; - } - if (!context.isRequestAudited()) { - auditEvent(context, AuditEventStage.REQUEST, context.getStats().getRequestTimestamp(), false, task, result, overallResult); - } - auditEvent(context, stage, null, false, task, result, overallResult); - } - } else { - auditEvent(context, stage, null, false, task, result, overallResult); - } - } - - /** - * Make sure that at least one execution is audited if a request was already audited. We don't want - * request without execution in the audit logs. - */ - private void auditFinalExecution(LensContext context, Task task, OperationResult result, - OperationResult overallResult) throws SchemaException { - if (!context.isRequestAudited()) { - return; - } - if (context.isExecutionAudited()) { - return; - } - auditEvent(context, AuditEventStage.EXECUTION, null, true, task, result, overallResult); - } - private void processClockworkException(LensContext context, Throwable e, Task task, OperationResult result, OperationResult overallResult) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + throws SchemaException { LOGGER.trace("Processing clockwork exception {}", e.toString()); result.recordFatalErrorNotFinish(e); - auditEvent(context, AuditEventStage.EXECUTION, null, true, task, result, overallResult); - recordOperationExecution(context, e, task, result); + clockworkAuditHelper.auditEvent(context, AuditEventStage.EXECUTION, null, true, task, result, overallResult); + operationExecutionRecorder.recordOperationExecution(context, e, task, result); LensUtil.reclaimSequences(context, repositoryService, task, result); result.recordEnd(); } - // "overallResult" covers the whole clockwork run - // while "result" is - most of the time - related to the current clockwork click - // - // We provide "result" here just for completeness - if any of the called methods would like to record to it. - private void auditEvent(LensContext context, AuditEventStage stage, - XMLGregorianCalendar timestamp, boolean alwaysAudit, Task task, OperationResult result, - OperationResult overallResult) throws SchemaException { - - PrismObject primaryObject; - ObjectDelta primaryDelta; - if (context.getFocusContext() != null) { - primaryObject = context.getFocusContext().getObjectOld(); - if (primaryObject == null) { - primaryObject = context.getFocusContext().getObjectNew(); - } - primaryDelta = context.getFocusContext().getDelta(); - } else { - Collection projectionContexts = context.getProjectionContexts(); - if (projectionContexts == null || projectionContexts.isEmpty()) { - throw new IllegalStateException("No focus and no projections in "+context); - } - if (projectionContexts.size() > 1) { - throw new IllegalStateException("No focus and more than one projection in "+context); - } - LensProjectionContext projection = projectionContexts.iterator().next(); - primaryObject = projection.getObjectOld(); - if (primaryObject == null) { - primaryObject = projection.getObjectNew(); - } - primaryDelta = projection.getDelta(); - } - - AuditEventType eventType; - if (primaryDelta == null) { - eventType = AuditEventType.SYNCHRONIZATION; - } else if (primaryDelta.isAdd()) { - eventType = AuditEventType.ADD_OBJECT; - } else if (primaryDelta.isModify()) { - eventType = AuditEventType.MODIFY_OBJECT; - } else if (primaryDelta.isDelete()) { - eventType = AuditEventType.DELETE_OBJECT; - } else { - throw new IllegalStateException("Unknown state of delta "+primaryDelta); - } - - AuditEventRecord auditRecord = new AuditEventRecord(eventType, stage); - auditRecord.setRequestIdentifier(context.getRequestIdentifier()); - - boolean recordResourceOids; - List propertiesToRecord; - SystemConfigurationType config = context.getSystemConfigurationType(); - if (config != null && config.getAudit() != null && config.getAudit().getEventRecording() != null) { - SystemConfigurationAuditEventRecordingType eventRecording = config.getAudit().getEventRecording(); - recordResourceOids = Boolean.TRUE.equals(eventRecording.isRecordResourceOids()); - propertiesToRecord = eventRecording.getProperty(); - } else { - recordResourceOids = false; - propertiesToRecord = emptyList(); - } - - if (primaryObject != null) { - auditRecord.setTarget(primaryObject, prismContext); - if (recordResourceOids) { - if (primaryObject.getRealValue() instanceof FocusType) { - FocusType focus = (FocusType) primaryObject.getRealValue(); - for (ObjectReferenceType shadowRef : focus.getLinkRef()) { - LensProjectionContext projectionContext = context.findProjectionContextByOid(shadowRef.getOid()); - if (projectionContext != null && StringUtils.isNotBlank(projectionContext.getResourceOid())) { - auditRecord.addResourceOid(projectionContext.getResourceOid()); - } - } - } else if (primaryObject.getRealValue() instanceof ShadowType) { - ObjectReferenceType resource = ((ShadowType) primaryObject.getRealValue()).getResourceRef(); - if (resource != null && resource.getOid() != null) { - auditRecord.addResourceOid(resource.getOid()); - } - } - } - } - - auditRecord.setChannel(context.getChannel()); - - // This is a brutal hack -- FIXME: create some "compute in-depth preview" method on operation result - OperationResult clone = overallResult.clone(2, false); - for (OperationResult subresult : clone.getSubresults()) { - subresult.computeStatusIfUnknown(); - } - clone.computeStatus(); - - if (stage == AuditEventStage.REQUEST) { - Collection> clonedDeltas = ObjectDeltaOperation.cloneDeltaCollection(context.getPrimaryChanges()); - checkNamesArePresent(clonedDeltas, primaryObject); - auditRecord.addDeltas(clonedDeltas); - if (auditRecord.getTarget() == null) { - auditRecord.setTarget(ModelImplUtils.determineAuditTargetDeltaOps(clonedDeltas, context.getPrismContext())); - } - } else if (stage == AuditEventStage.EXECUTION) { - auditRecord.setOutcome(clone.getStatus()); - Collection> unauditedExecutedDeltas = context.getUnauditedExecutedDeltas(); - if (!alwaysAudit && (unauditedExecutedDeltas == null || unauditedExecutedDeltas.isEmpty())) { - // No deltas, nothing to audit in this wave - return; - } - Collection> clonedDeltas = ObjectDeltaOperation.cloneCollection(unauditedExecutedDeltas); - checkNamesArePresent(clonedDeltas, primaryObject); - auditRecord.addDeltas(clonedDeltas); - } else { - throw new IllegalStateException("Unknown audit stage "+stage); - } - - if (timestamp != null) { - auditRecord.setTimestamp(XmlTypeConverter.toMillis(timestamp)); - } - - addRecordMessage(auditRecord, clone.getMessage()); - - for (SystemConfigurationAuditEventRecordingPropertyType property : propertiesToRecord) { - String name = property.getName(); - if (StringUtils.isBlank(name)) { - throw new IllegalArgumentException("Name of SystemConfigurationAuditEventRecordingPropertyType is empty or null in " + property); - } - ExpressionType expression = property.getExpression(); - if (expression != null) { - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_TARGET, primaryObject, PrismObject.class); - variables.put(ExpressionConstants.VAR_AUDIT_RECORD, auditRecord, AuditEventRecord.class); - String shortDesc = "value for custom column of audit table"; - try { - Collection values = ExpressionUtil.evaluateStringExpression(variables, prismContext, expression, context.getPrivilegedExpressionProfile(), expressionFactory, shortDesc, task, result); - if (values != null && !values.isEmpty()) { - if (values.size() > 1) { - throw new IllegalArgumentException("Collection of expression result contains more as one value"); - } - auditRecord.getCustomColumnProperty().put(name, values.iterator().next()); - } - } catch (CommonException e) { - LOGGER.error("Couldn't evaluate Expression " + expression.toString(), e); - } - } - } - - auditHelper.audit(auditRecord, context.getNameResolver(), task, result); - - if (stage == AuditEventStage.EXECUTION) { - // We need to clean up so these deltas will not be audited again in next wave - context.markExecutedDeltasAudited(); - context.setExecutionAudited(true); - } else { - assert stage == AuditEventStage.REQUEST; - context.setRequestAudited(true); - } - } - - private void checkNamesArePresent(Collection> deltas, PrismObject primaryObject) { - if (primaryObject != null) { - for (ObjectDeltaOperation delta : deltas) { - if (delta.getObjectName() == null) { - delta.setObjectName(primaryObject.getName()); - } - } - } - } - - /** - * Adds a message to the record by pulling the messages from individual delta results. - */ - private void addRecordMessage(AuditEventRecord auditRecord, String message) { - if (auditRecord.getMessage() != null) { - return; - } - if (!StringUtils.isEmpty(message)) { - auditRecord.setMessage(message); - return; - } - Collection> deltas = auditRecord.getDeltas(); - if (deltas == null || deltas.isEmpty()) { - return; - } - StringBuilder sb = new StringBuilder(); - for (ObjectDeltaOperation delta: deltas) { - OperationResult executionResult = delta.getExecutionResult(); - if (executionResult != null) { - String deltaMessage = executionResult.getMessage(); - if (!StringUtils.isEmpty(deltaMessage)) { - if (sb.length() != 0) { - sb.append("; "); - } - sb.append(deltaMessage); - } - } - } - auditRecord.setMessage(sb.toString()); - } - - public static void throwException(Throwable e) throws ObjectAlreadyExistsException, ObjectNotFoundException { - if (e instanceof ObjectAlreadyExistsException) { - throw (ObjectAlreadyExistsException)e; - } else if (e instanceof ObjectNotFoundException) { - throw (ObjectNotFoundException)e; - } else if (e instanceof SystemException) { - throw (SystemException)e; - } else { - throw new SystemException("Unexpected exception "+e.getClass()+" "+e.getMessage(), e); - } - } - /** * Logs the entire operation in a human-readable fashion. */ - private void logFinalReadable(LensContext context, Task task, OperationResult result) throws SchemaException { + private void logFinalReadable(LensContext context) { if (!LOGGER.isDebugEnabled()) { return; } @@ -1509,11 +589,12 @@ private void logFinalReadable(LensContext context, Tas ObjectDelta syncDelta = projectionContext.getSyncDelta(); if (syncDelta != null) { hasSyncDelta = true; + break; } } Collection> executedDeltas = context.getExecutedDeltas(); - if (!hasSyncDelta && executedDeltas == null || executedDeltas.isEmpty()) { + if (!hasSyncDelta && executedDeltas.isEmpty()) { // Not worth mentioning return; } @@ -1598,7 +679,7 @@ private void logFinalReadable(LensContext context, Tas } } - if (executedDeltas == null || executedDeltas.isEmpty()) { + if (executedDeltas.isEmpty()) { sb.append("Executed: nothing\n"); } else { sb.append("Executed:\n"); @@ -1617,5 +698,4 @@ private void logFinalReadable(LensContext context, Tas "##############################################################", sb.toString()); } - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkAuditHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkAuditHelper.java new file mode 100644 index 00000000000..cc5076beaf2 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkAuditHelper.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens; + +import com.evolveum.midpoint.audit.api.AuditEventRecord; +import com.evolveum.midpoint.audit.api.AuditEventStage; +import com.evolveum.midpoint.audit.api.AuditEventType; +import com.evolveum.midpoint.model.impl.util.AuditHelper; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.datatype.XMLGregorianCalendar; +import java.util.Collection; +import java.util.List; + +import static java.util.Collections.emptyList; + +/** + * Audit-related responsibilities during clockwork processing. + */ +@Component +public class ClockworkAuditHelper { + + private static final Trace LOGGER = TraceManager.getTrace(ClockworkAuditHelper.class); + + @Autowired private PrismContext prismContext; + @Autowired private AuditHelper auditHelper; + @Autowired private ExpressionFactory expressionFactory; + + // "overallResult" covers the whole clockwork run + // while "result" is - most of the time - related to the current clockwork click + void audit(LensContext context, AuditEventStage stage, Task task, OperationResult result, + OperationResult overallResult) throws SchemaException { + if (context.isLazyAuditRequest()) { + if (stage == AuditEventStage.REQUEST) { + // We skip auditing here, we will do it before execution + } else if (stage == AuditEventStage.EXECUTION) { + if (context.getUnauditedExecutedDeltas().isEmpty()) { + // No deltas, nothing to audit in this wave + return; + } + if (!context.isRequestAudited()) { + auditEvent(context, AuditEventStage.REQUEST, context.getStats().getRequestTimestamp(), false, task, result, overallResult); + } + auditEvent(context, stage, null, false, task, result, overallResult); + } + } else { + auditEvent(context, stage, null, false, task, result, overallResult); + } + } + + /** + * Make sure that at least one execution is audited if a request was already audited. We don't want + * request without execution in the audit logs. + */ + void auditFinalExecution(LensContext context, Task task, OperationResult result, + OperationResult overallResult) throws SchemaException { + if (context.isRequestAudited() && !context.isExecutionAudited()) { + auditEvent(context, AuditEventStage.EXECUTION, null, true, task, result, overallResult); + } + } + + // "overallResult" covers the whole clockwork run + // while "result" is - most of the time - related to the current clockwork click + // + // We provide "result" here just for completeness - if any of the called methods would like to record to it. + void auditEvent(LensContext context, AuditEventStage stage, + XMLGregorianCalendar timestamp, boolean alwaysAudit, Task task, OperationResult result, + OperationResult overallResult) throws SchemaException { + + PrismObject primaryObject; + ObjectDelta primaryDelta; + if (context.getFocusContext() != null) { + primaryObject = context.getFocusContext().getObjectOld(); + if (primaryObject == null) { + primaryObject = context.getFocusContext().getObjectNew(); + } + primaryDelta = context.getFocusContext().getDelta(); + } else { + Collection projectionContexts = context.getProjectionContexts(); + if (projectionContexts == null || projectionContexts.isEmpty()) { + throw new IllegalStateException("No focus and no projections in "+context); + } + if (projectionContexts.size() > 1) { + throw new IllegalStateException("No focus and more than one projection in "+context); + } + LensProjectionContext projection = projectionContexts.iterator().next(); + primaryObject = projection.getObjectOld(); + if (primaryObject == null) { + primaryObject = projection.getObjectNew(); + } + primaryDelta = projection.getDelta(); + } + + AuditEventType eventType; + if (primaryDelta == null) { + eventType = AuditEventType.SYNCHRONIZATION; + } else if (primaryDelta.isAdd()) { + eventType = AuditEventType.ADD_OBJECT; + } else if (primaryDelta.isModify()) { + eventType = AuditEventType.MODIFY_OBJECT; + } else if (primaryDelta.isDelete()) { + eventType = AuditEventType.DELETE_OBJECT; + } else { + throw new IllegalStateException("Unknown state of delta "+primaryDelta); + } + + AuditEventRecord auditRecord = new AuditEventRecord(eventType, stage); + auditRecord.setRequestIdentifier(context.getRequestIdentifier()); + + boolean recordResourceOids; + List propertiesToRecord; + SystemConfigurationType config = context.getSystemConfigurationType(); + if (config != null && config.getAudit() != null && config.getAudit().getEventRecording() != null) { + SystemConfigurationAuditEventRecordingType eventRecording = config.getAudit().getEventRecording(); + recordResourceOids = Boolean.TRUE.equals(eventRecording.isRecordResourceOids()); + propertiesToRecord = eventRecording.getProperty(); + } else { + recordResourceOids = false; + propertiesToRecord = emptyList(); + } + + if (primaryObject != null) { + auditRecord.setTarget(primaryObject, prismContext); + if (recordResourceOids) { + if (primaryObject.getRealValue() instanceof FocusType) { + FocusType focus = (FocusType) primaryObject.getRealValue(); + for (ObjectReferenceType shadowRef : focus.getLinkRef()) { + LensProjectionContext projectionContext = context.findProjectionContextByOid(shadowRef.getOid()); + if (projectionContext != null && StringUtils.isNotBlank(projectionContext.getResourceOid())) { + auditRecord.addResourceOid(projectionContext.getResourceOid()); + } + } + } else if (primaryObject.getRealValue() instanceof ShadowType) { + ObjectReferenceType resource = ((ShadowType) primaryObject.getRealValue()).getResourceRef(); + if (resource != null && resource.getOid() != null) { + auditRecord.addResourceOid(resource.getOid()); + } + } + } + } + + auditRecord.setChannel(context.getChannel()); + + // This is a brutal hack -- FIXME: create some "compute in-depth preview" method on operation result + OperationResult clone = overallResult.clone(2, false); + for (OperationResult subresult : clone.getSubresults()) { + subresult.computeStatusIfUnknown(); + } + clone.computeStatus(); + + if (stage == AuditEventStage.REQUEST) { + Collection> clonedDeltas = ObjectDeltaOperation.cloneDeltaCollection(context.getPrimaryChanges()); + checkNamesArePresent(clonedDeltas, primaryObject); + auditRecord.addDeltas(clonedDeltas); + if (auditRecord.getTarget() == null) { + auditRecord.setTarget(ModelImplUtils.determineAuditTargetDeltaOps(clonedDeltas, context.getPrismContext())); + } + } else if (stage == AuditEventStage.EXECUTION) { + auditRecord.setOutcome(clone.getStatus()); + Collection> unauditedExecutedDeltas = context.getUnauditedExecutedDeltas(); + if (!alwaysAudit && (unauditedExecutedDeltas == null || unauditedExecutedDeltas.isEmpty())) { + // No deltas, nothing to audit in this wave + return; + } + Collection> clonedDeltas = ObjectDeltaOperation.cloneCollection(unauditedExecutedDeltas); + checkNamesArePresent(clonedDeltas, primaryObject); + auditRecord.addDeltas(clonedDeltas); + } else { + throw new IllegalStateException("Unknown audit stage "+stage); + } + + if (timestamp != null) { + auditRecord.setTimestamp(XmlTypeConverter.toMillis(timestamp)); + } + + addRecordMessage(auditRecord, clone.getMessage()); + + for (SystemConfigurationAuditEventRecordingPropertyType property : propertiesToRecord) { + String name = property.getName(); + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("Name of SystemConfigurationAuditEventRecordingPropertyType is empty or null in " + property); + } + ExpressionType expression = property.getExpression(); + if (expression != null) { + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_TARGET, primaryObject, PrismObject.class); + variables.put(ExpressionConstants.VAR_AUDIT_RECORD, auditRecord, AuditEventRecord.class); + String shortDesc = "value for custom column of audit table"; + try { + Collection values = ExpressionUtil.evaluateStringExpression(variables, prismContext, expression, context.getPrivilegedExpressionProfile(), expressionFactory, shortDesc, task, result); + if (values != null && !values.isEmpty()) { + if (values.size() > 1) { + throw new IllegalArgumentException("Collection of expression result contains more as one value"); + } + auditRecord.getCustomColumnProperty().put(name, values.iterator().next()); + } + } catch (CommonException e) { + LOGGER.error("Couldn't evaluate Expression " + expression.toString(), e); + } + } + } + + auditHelper.audit(auditRecord, context.getNameResolver(), task, result); + + if (stage == AuditEventStage.EXECUTION) { + // We need to clean up so these deltas will not be audited again in next wave + context.markExecutedDeltasAudited(); + context.setExecutionAudited(true); + } else { + assert stage == AuditEventStage.REQUEST; + context.setRequestAudited(true); + } + } + + private void checkNamesArePresent(Collection> deltas, PrismObject primaryObject) { + if (primaryObject != null) { + for (ObjectDeltaOperation delta : deltas) { + if (delta.getObjectName() == null) { + delta.setObjectName(primaryObject.getName()); + } + } + } + } + + /** + * Adds a message to the record by pulling the messages from individual delta results. + */ + private void addRecordMessage(AuditEventRecord auditRecord, String message) { + if (auditRecord.getMessage() != null) { + return; + } + if (!StringUtils.isEmpty(message)) { + auditRecord.setMessage(message); + return; + } + Collection> deltas = auditRecord.getDeltas(); + if (deltas == null || deltas.isEmpty()) { + return; + } + StringBuilder sb = new StringBuilder(); + for (ObjectDeltaOperation delta: deltas) { + OperationResult executionResult = delta.getExecutionResult(); + if (executionResult != null) { + String deltaMessage = executionResult.getMessage(); + if (!StringUtils.isEmpty(deltaMessage)) { + if (sb.length() != 0) { + sb.append("; "); + } + sb.append(deltaMessage); + } + } + } + auditRecord.setMessage(sb.toString()); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkConflictResolver.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkConflictResolver.java new file mode 100644 index 00000000000..a0495883f03 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkConflictResolver.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens; + +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.ProgressInformation; +import com.evolveum.midpoint.model.api.hooks.HookOperationMode; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.repo.api.ConflictWatcher; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; + +import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.WAITING; +import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.EXITING; + +import static org.apache.commons.collections4.CollectionUtils.emptyIfNull; +import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; + +/** + * Resolves conflicts occurring during clockwork processing (multiple threads modifying the same focus). + */ +@Component +public class ClockworkConflictResolver { + + private static final Trace LOGGER = TraceManager.getTrace(ClockworkConflictResolver.class); + + @Autowired private Clockwork clockwork; + @Autowired private ContextFactory contextFactory; + @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService repositoryService; + + private static final int DEFAULT_MAX_CONFLICT_RESOLUTION_ATTEMPTS = 1; // synchronize with common-core-3.xsd + private static final int DEFAULT_CONFLICT_RESOLUTION_DELAY_UNIT = 5000; // synchronize with common-core-3.xsd + private static final int MAX_PRECONDITION_CONFLICT_RESOLUTION_ATTEMPTS = 3; + + static class Context { + private boolean focusConflictPresent; + private ConflictResolutionType resolutionPolicy; + } + + void createConflictWatcherOnStart(LensContext context) { + if (context.getFocusContext() != null && context.getFocusContext().getOid() != null) { + context.createAndRegisterFocusConflictWatcher(context.getFocusContext().getOid(), repositoryService); + } + } + + void createConflictWatcherAfterFocusAddition(LensContext context, String oid, String expectedVersion) { + // The watcher can already exist; if the OID was pre-existing in the object. + if (context.getFocusConflictWatcher() == null) { + ConflictWatcher watcher = context.createAndRegisterFocusConflictWatcher(oid, repositoryService); + watcher.setExpectedVersion(expectedVersion); + } + } + + void unregisterConflictWatcher(LensContext context) { + context.unregisterConflictWatcher(repositoryService); + } + + void checkFocusConflicts(LensContext context, Context resolutionContext, OperationResult result) { + resolutionContext.resolutionPolicy = ModelImplUtils.getConflictResolution(context); + ConflictWatcher watcher = context.getFocusConflictWatcher(); + if (watcher != null && resolutionContext.resolutionPolicy != null && + resolutionContext.resolutionPolicy.getAction() != ConflictResolutionActionType.NONE && + repositoryService.hasConflict(watcher, result)) { + LOGGER.debug("Found modify-modify conflict on {}", watcher); + resolutionContext.focusConflictPresent = true; + } else { + resolutionContext.focusConflictPresent = false; + } + } + + HookOperationMode resolveFocusConflictIfPresent(LensContext context, Context resolutionContext, + HookOperationMode finalMode, Task task, OperationResult result) throws CommunicationException, + ObjectNotFoundException, ObjectAlreadyExistsException, PolicyViolationException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + if (resolutionContext.focusConflictPresent) { + assert finalMode == HookOperationMode.FOREGROUND; + return resolveFocusConflict(context, resolutionContext.resolutionPolicy, task, result); + } else { + if (context.getConflictResolutionAttemptNumber() > 0) { + LOGGER.info("Resolved update conflict on attempt number {}", context.getConflictResolutionAttemptNumber()); + } + return finalMode; + } + } + + private HookOperationMode resolveFocusConflict(LensContext context, + ConflictResolutionType resolutionPolicy, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, ConfigurationException, + CommunicationException, SecurityViolationException, PolicyViolationException, ObjectAlreadyExistsException { + if (resolutionPolicy == null || resolutionPolicy.getAction() == ConflictResolutionActionType.NONE) { + return HookOperationMode.FOREGROUND; + } + PrismObject focusObject = context.getFocusContext() != null ? context.getFocusContext().getObjectAny() : null; + ModelExecuteOptions options = new ModelExecuteOptions(); + switch (resolutionPolicy.getAction()) { + case FAIL: throw new SystemException("Conflict detected while updating " + focusObject); + case LOG: + LOGGER.warn("Conflict detected while updating {}", focusObject); + return HookOperationMode.FOREGROUND; + case RECOMPUTE: + break; + case RECONCILE: + options.setReconcile(); + break; + default: + throw new IllegalStateException("Unsupported conflict resolution action: " + resolutionPolicy.getAction()); + } + + // so, recompute is the action + LOGGER.debug("CONFLICT: Conflict detected while updating {}, recomputing (options={})", focusObject, options); + + if (context.getFocusContext() == null) { + LOGGER.warn("No focus context, not possible to resolve conflict by focus recomputation"); // should really never occur + return HookOperationMode.FOREGROUND; + } + String oid = context.getFocusContext().getOid(); + if (oid == null) { + LOGGER.warn("No focus OID, not possible to resolve conflict by focus recomputation"); // should really never occur + return HookOperationMode.FOREGROUND; + } + Class focusClass = context.getFocusContext().getObjectTypeClass(); + if (focusClass == null) { + LOGGER.warn("Focus class not known, not possible to resolve conflict by focus recomputation"); // should really never occur + return HookOperationMode.FOREGROUND; + } + if (TaskType.class.isAssignableFrom(focusClass)) { + return HookOperationMode.FOREGROUND; // this is actually quite expected, so don't bother anyone with that + } + if (!FocusType.class.isAssignableFrom(focusClass)) { + LOGGER.warn("Focus is not of FocusType (it is {}); not possible to resolve conflict by focus recomputation", focusClass.getName()); + return HookOperationMode.FOREGROUND; + } + + ConflictResolutionType focusConflictResolution = new ConflictResolutionType(); + focusConflictResolution.setAction(ConflictResolutionActionType.ERROR); + options.setFocusConflictResolution(focusConflictResolution); + + int preconditionAttempts = 0; + while (true) { + + int attemptOld = context.getConflictResolutionAttemptNumber(); + int attemptNew = attemptOld + 1; + boolean shouldExecuteAttempt = shouldExecuteAttempt(resolutionPolicy, attemptNew); + if (!shouldExecuteAttempt) { + LOGGER.warn("CONFLICT: Couldn't resolve conflict even after {} resolution attempt(s), giving up.", attemptOld); + return HookOperationMode.FOREGROUND; + } + + delay(context, resolutionPolicy, attemptNew + preconditionAttempts); + + PrismObject focus = repositoryService.getObject(focusClass, oid, null, result); + LensContext contextNew = contextFactory.createRecomputeContext(focus, options, task, result); + contextNew.setProgressListeners(new ArrayList<>(emptyIfNull(context.getProgressListeners()))); + contextNew.setConflictResolutionAttemptNumber(attemptNew); + + LOGGER.debug("CONFLICT: Recomputing {} as reaction to conflict (options={}, attempts={},{}, readVersion={})", + context.getFocusContext().getHumanReadableName(), options, attemptNew, preconditionAttempts, contextNew.getFocusContext().getObjectReadVersion()); + + try { + + // this is a recursion; but limited to max attempts which should not be a large number + HookOperationMode hookOperationMode = clockwork.run(contextNew, task, result); + + // This may be in fact a give-up after recompute that was not able to cleanly proceed. + LOGGER.debug("CONFLICT: Clean recompute (or give-up) of {} achieved (options={}, attempts={},{})", + context.getFocusContext().getHumanReadableName(), options, attemptNew, preconditionAttempts); + + return hookOperationMode; + + } catch (PreconditionViolationException e) { + preconditionAttempts++; + LOGGER.debug("CONFLICT: Recompute precondition failed (attempt {}, precondition attempt {}), trying again", attemptNew, preconditionAttempts); + if (preconditionAttempts < MAX_PRECONDITION_CONFLICT_RESOLUTION_ATTEMPTS) { + continue; + } + LOGGER.warn("CONFLICT: Couldn't resolve conflict even after {} resolution attempt(s) and {} precondition attempts, giving up.", + attemptOld, preconditionAttempts); + return HookOperationMode.FOREGROUND; + } + } + } + + private boolean shouldExecuteAttempt(@NotNull ConflictResolutionType resolutionPolicy, int attempt) { + int maxAttempts = defaultIfNull(resolutionPolicy.getMaxAttempts(), DEFAULT_MAX_CONFLICT_RESOLUTION_ATTEMPTS); + return attempt <= maxAttempts; + } + + private void delay(LensContext context, @NotNull ConflictResolutionType resolutionPolicy, int attempt) { + long delayRange = defaultIfNull(resolutionPolicy.getDelayUnit(), DEFAULT_CONFLICT_RESOLUTION_DELAY_UNIT) * (1L << attempt); + long delay = (long) (Math.random() * delayRange); + String message = "CONFLICT: Waiting "+delay+" milliseconds before starting conflict resolution (delay exponent: "+attempt+")"; + // TODO convey information about waiting time after some GUI mechanism for displaying it is available + // (showing text messages is currently really ugly) + context.reportProgress(new ProgressInformation(WAITING, EXITING)); + LOGGER.debug(message); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // ignore + } + context.reportProgress(new ProgressInformation(WAITING, EXITING)); + } + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java new file mode 100644 index 00000000000..42699f046b9 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkHookHelper.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens; + +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.model.api.hooks.ChangeHook; +import com.evolveum.midpoint.model.api.hooks.HookOperationMode; +import com.evolveum.midpoint.model.api.hooks.HookRegistry; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpression; +import com.evolveum.midpoint.model.common.expression.script.ScriptExpressionFactory; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; + +/** + * Responsible for invoking hooks (both Java and scripting ones). + */ +@Component +public class ClockworkHookHelper { + + private static final Trace LOGGER = TraceManager.getTrace(ClockworkHookHelper.class); + + @Autowired(required = false) private HookRegistry hookRegistry; + @Autowired private PrismContext prismContext; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private ScriptExpressionFactory scriptExpressionFactory; + @Autowired private SystemObjectCache systemObjectCache; + + /** + * Invokes hooks, if there are any. + * + * @return + * - ERROR, if any hook reported error; otherwise returns + * - BACKGROUND, if any hook reported switching to background; otherwise + * - FOREGROUND (if all hooks reported finishing on foreground) + */ + HookOperationMode invokeHooks(LensContext context, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + // TODO: following two parts should be merged together in later versions + + // Execute configured scripting hooks + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + // systemConfiguration may be null in some tests + if (systemConfiguration != null) { + ModelHooksType modelHooks = systemConfiguration.asObjectable().getModelHooks(); + if (modelHooks != null) { + HookListType changeHooks = modelHooks.getChange(); + if (changeHooks != null) { + for (HookType hookType: changeHooks.getHook()) { + String shortDesc; + if (hookType.getName() != null) { + shortDesc = "hook '"+hookType.getName()+"'"; + } else { + shortDesc = "scripting hook in system configuration"; + } + if (hookType.isEnabled() != null && !hookType.isEnabled()) { + // Disabled hook, skip + continue; + } + if (hookType.getState() != null) { + if (!context.getState().toModelStateType().equals(hookType.getState())) { + continue; + } + } + if (hookType.getFocusType() != null) { + if (context.getFocusContext() == null) { + continue; + } + QName hookFocusTypeQname = hookType.getFocusType(); + ObjectTypes hookFocusType = ObjectTypes.getObjectTypeFromTypeQName(hookFocusTypeQname); + if (hookFocusType == null) { + throw new SchemaException("Unknown focus type QName "+hookFocusTypeQname+" in "+shortDesc); + } + Class focusClass = context.getFocusClass(); + Class hookFocusClass = hookFocusType.getClassDefinition(); + if (!hookFocusClass.isAssignableFrom(focusClass)) { + continue; + } + } + + ScriptExpressionEvaluatorType scriptExpressionEvaluatorType = hookType.getScript(); + if (scriptExpressionEvaluatorType == null) { + continue; + } + try { + evaluateScriptingHook(context, scriptExpressionEvaluatorType, shortDesc, task, result); + } catch (ExpressionEvaluationException e) { + LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); + throw new ExpressionEvaluationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); + } catch (ObjectNotFoundException e) { + LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); + throw new ObjectNotFoundException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); + } catch (SchemaException e) { + LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); + throw new SchemaException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); + } catch (CommunicationException e) { + LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); + throw new CommunicationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); + } catch (ConfigurationException e) { + LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); + throw new ConfigurationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); + } catch (SecurityViolationException e) { + LOGGER.error("Evaluation of {} failed: {}", shortDesc, e.getMessage(), e); + throw new SecurityViolationException("Evaluation of "+shortDesc+" failed: "+e.getMessage(), e); + } + } + } + } + } + + // Execute registered Java hooks + HookOperationMode resultMode = HookOperationMode.FOREGROUND; + if (hookRegistry != null) { + for (ChangeHook hook : hookRegistry.getAllChangeHooks()) { + HookOperationMode mode = hook.invoke(context, task, result); + if (mode == HookOperationMode.ERROR) { + resultMode = HookOperationMode.ERROR; + } else if (mode == HookOperationMode.BACKGROUND) { + if (resultMode != HookOperationMode.ERROR) { + resultMode = HookOperationMode.BACKGROUND; + } + } + } + } + return resultMode; + } + + private void evaluateScriptingHook(LensContext context, + ScriptExpressionEvaluatorType scriptExpressionEvaluatorType, String shortDesc, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + + LOGGER.trace("Evaluating {}", shortDesc); + // TODO: it would be nice to cache this + // null output definition: this script has no output + ScriptExpression scriptExpression = scriptExpressionFactory.createScriptExpression(scriptExpressionEvaluatorType, null, context.getPrivilegedExpressionProfile(), expressionFactory, shortDesc, task, result); + + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_PRISM_CONTEXT, prismContext, PrismContext.class); + variables.put(ExpressionConstants.VAR_MODEL_CONTEXT, context, ModelContext.class); + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + variables.put(ExpressionConstants.VAR_FOCUS, focusContext.getObjectAny(), focusContext.getObjectDefinition()); + } else { + PrismObjectDefinition def = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ObjectType.class); + variables.put(ExpressionConstants.VAR_FOCUS, null, def); + } + + ModelImplUtils.evaluateScript(scriptExpression, context, variables, false, shortDesc, task, result); + LOGGER.trace("Finished evaluation of {}", shortDesc); + } + + void invokePreview(LensContext context, Task task, OperationResult result) { + if (hookRegistry != null) { + for (ChangeHook hook : hookRegistry.getAllChangeHooks()) { + hook.invokePreview(context, task, result); + } + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java index 1909807b869..49678098608 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java @@ -17,6 +17,7 @@ import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.provisioning.api.ProvisioningService; @@ -30,10 +31,7 @@ import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ObjectDeltaSchemaLevelUtil; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.TreeNode; +import com.evolveum.midpoint.util.*; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -106,9 +104,7 @@ public enum ExportType { private Class focusClass; - private boolean lazyAuditRequest = false; // should be the request audited - // just before the execution is - // audited? + private boolean lazyAuditRequest = false; // should be the request audited just before the execution is audited? private boolean requestAudited = false; // was the request audited? private boolean executionAudited = false; // was the execution audited? private LensContextStatsType stats = new LensContextStatsType(); @@ -153,12 +149,12 @@ public enum ExportType { /** * Current wave of computation and execution. */ - int projectionWave = 0; + private int projectionWave = 0; /** * Current wave of execution. */ - int executionWave = 0; + private int executionWave = 0; private String triggeredResourceOid; @@ -467,6 +463,69 @@ public void rot(String reason) { } } + /** + * Force recompute for the next execution wave. Recompute only those contexts that were changed. + * This is more intelligent than rot() + */ + void rotIfNeeded() throws SchemaException { + Holder rotHolder = new Holder<>(false); + rotProjectionContextsIfNeeded(rotHolder); + rotFocusContextIfNeeded(rotHolder); + if (rotHolder.getValue()) { + setFresh(false); + } + } + + private void rotProjectionContextsIfNeeded(Holder rotHolder) throws SchemaException { + for (LensProjectionContext projectionContext: projectionContexts) { + if (projectionContext.getWave() != executionWave) { + LOGGER.trace("Context rot: projection {} NOT rotten because of wrong wave number", projectionContext); + } else { + ObjectDelta execDelta = projectionContext.getExecutableDelta(); + if (isShadowDeltaSignificant(execDelta)) { + LOGGER.debug("Context rot: projection {} rotten because of executable delta {}", projectionContext, execDelta); + projectionContext.setFresh(false); + projectionContext.setFullShadow(false); + rotHolder.setValue(true); + // Propagate to higher-order projections + for (LensProjectionContext relCtx : LensUtil.findRelatedContexts(this, projectionContext)) { + relCtx.setFresh(false); + relCtx.setFullShadow(false); + } + } else { + LOGGER.trace("Context rot: projection {} NOT rotten because no delta", projectionContext); + } + } + } + } + + private

boolean isShadowDeltaSignificant(ObjectDelta

delta) { + if (delta == null || delta.isEmpty()) { + return false; + } + if (delta.isAdd() || delta.isDelete()) { + return true; + } else { + Collection> attrDeltas = delta.findItemDeltasSubPath(ShadowType.F_ATTRIBUTES); + return attrDeltas != null && !attrDeltas.isEmpty(); + } + } + + private void rotFocusContextIfNeeded(Holder rotHolder) + throws SchemaException { + if (focusContext != null) { + ObjectDelta execDelta = focusContext.getWaveDelta(executionWave); + if (execDelta != null && !execDelta.isEmpty()) { + LOGGER.debug("Context rot: context rotten because of focus execution delta {}", execDelta); + rotHolder.setValue(true); + } + if (rotHolder.getValue()) { + // It is OK to refresh focus all the time there was any change. This is cheap. + focusContext.setFresh(false); + } + } + } + public String getChannel() { return channel; } @@ -658,23 +717,24 @@ public void replacePrimaryFocusDeltas(List> deltas) throws Schema /** * Returns all executed deltas, user and all accounts. */ - public Collection> getExecutedDeltas() throws SchemaException { + @NotNull + public Collection> getExecutedDeltas() { return getExecutedDeltas(null); } /** * Returns all executed deltas, user and all accounts. */ - public Collection> getUnauditedExecutedDeltas() - throws SchemaException { + @NotNull + public Collection> getUnauditedExecutedDeltas() { return getExecutedDeltas(false); } /** * Returns all executed deltas, user and all accounts. */ - Collection> getExecutedDeltas(Boolean audited) - throws SchemaException { + @NotNull + private Collection> getExecutedDeltas(Boolean audited) { Collection> executedDeltas = new ArrayList<>(); if (focusContext != null) { executedDeltas.addAll(focusContext.getExecutedDeltas(audited)); @@ -683,7 +743,7 @@ Collection> getExecutedDeltas(Boolean executedDeltas.addAll(projCtx.getExecutedDeltas(audited)); } if (audited == null) { - executedDeltas.addAll((Collection>) getRottenExecutedDeltas()); + executedDeltas.addAll(getRottenExecutedDeltas()); } return executedDeltas; } @@ -1421,18 +1481,18 @@ public void setConflictResolutionAttemptNumber(int conflictResolutionAttemptNumb this.conflictResolutionAttemptNumber = conflictResolutionAttemptNumber; } - public ConflictWatcher getFocusConflictWatcher() { + ConflictWatcher getFocusConflictWatcher() { return focusConflictWatcher; } - public ConflictWatcher createAndRegisterFocusConflictWatcher(@NotNull String oid, RepositoryService repositoryService) { + ConflictWatcher createAndRegisterFocusConflictWatcher(@NotNull String oid, RepositoryService repositoryService) { if (focusConflictWatcher != null) { throw new IllegalStateException("Focus conflict watcher defined twice"); } return focusConflictWatcher = repositoryService.createAndRegisterConflictWatcher(oid); } - public void unregisterConflictWatchers(RepositoryService repositoryService) { + void unregisterConflictWatcher(RepositoryService repositoryService) { if (focusConflictWatcher != null) { repositoryService.unregisterConflictWatcher(focusConflictWatcher); focusConflictWatcher = null; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/OperationExecutionRecorder.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/OperationExecutionRecorder.java new file mode 100644 index 00000000000..1131e63fa65 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/OperationExecutionRecorder.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens; + +import java.util.*; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.apache.commons.lang3.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.Clock; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.prism.PrismContainerValue; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.delta.ChangeType; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.util.CloneUtil; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.ObjectDeltaOperation; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.task.api.RunningTask; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.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.*; + +/** + * Responsible for maintaining "operation execution" records. + */ +@Component +public class OperationExecutionRecorder { + + private static final Trace LOGGER = TraceManager.getTrace(OperationExecutionRecorder.class); + + @Autowired private Clock clock; + @Autowired private SystemObjectCache systemObjectCache; + @Autowired private PrismContext prismContext; + @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService repositoryService; + + private static final int DEFAULT_NUMBER_OF_RESULTS_TO_KEEP = 5; + + void recordOperationExecution(LensContext context, Throwable clockworkException, + Task task, OperationResult result) { + boolean skip = context.getInternalsConfiguration() != null && + context.getInternalsConfiguration().getOperationExecutionRecording() != null && + Boolean.TRUE.equals(context.getInternalsConfiguration().getOperationExecutionRecording().isSkip()); + if (!skip) { + XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); + try { + LOGGER.trace("recordOperationExecution starting; task = {}, clockworkException = {}", task, clockworkException); + boolean opRecordedIntoFocus = recordFocusOperationExecution(context, now, clockworkException, task, result); + for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + Throwable exceptionToProjection; + if (clockworkException != null && !opRecordedIntoFocus && projectionContext.isSynchronizationSource()) { + // We need to record the exception somewhere. Because we were not able to put it into focus, + // we have to do it into sync-source projection. + exceptionToProjection = clockworkException; + } else { + exceptionToProjection = null; + } + recordProjectionOperationExecution(context, projectionContext, now, exceptionToProjection, task, result); + } + } catch (Throwable t) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't record operation execution. Model context:\n{}", t, + context.debugDump()); + // Let us ignore this for the moment. It should not have happened, sure. But it's not that crucial. + // Administrator will be able to learn about the problem from the log. + } + } else { + LOGGER.trace("Skipping operation execution recording (as set in system configuration)"); + } + } + + /** + * @return true if the operation execution was recorded (or would be recorded, but skipped because of the configuration) + */ + private boolean recordFocusOperationExecution(LensContext context, XMLGregorianCalendar now, + Throwable clockworkException, Task task, OperationResult result) + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null || focusContext.isDelete()) { + LOGGER.trace("focusContext is null or 'delete', not recording focus operation execution"); + return false; + } + PrismObject objectNew = focusContext.getObjectNew(); + Validate.notNull(objectNew, "No focus object even if the context is not of 'delete' type"); + + //noinspection unchecked + List> executedDeltas = getExecutedDeltas(focusContext, + (Class) objectNew.asObjectable().getClass(), clockworkException, result); + LOGGER.trace("recordFocusOperationExecution: executedDeltas: {}", executedDeltas.size()); + return recordOperationExecution(objectNew, false, executedDeltas, now, context.getChannel(), + getSkipWhenSuccess(context), task, result); + } + + @NotNull + private List> getExecutedDeltas(LensElementContext elementContext, + Class objectClass, Throwable clockworkException, OperationResult result) { + List> executedDeltas; + if (clockworkException != null) { + executedDeltas = new ArrayList<>(elementContext.getExecutedDeltas()); + LensObjectDeltaOperation odo = new LensObjectDeltaOperation<>(); + ObjectDelta primaryDelta = elementContext.getPrimaryDelta(); + if (primaryDelta != null) { + odo.setObjectDelta(primaryDelta); + } else { + ObjectDelta fakeDelta = prismContext.deltaFactory().object().create(objectClass, ChangeType.MODIFY); + odo.setObjectDelta(fakeDelta); + } + odo.setExecutionResult(result); // we rely on the fact that 'result' already contains record of the exception + executedDeltas.add(odo); + } else { + executedDeltas = elementContext.getExecutedDeltas(); + } + return executedDeltas; + } + + private boolean getSkipWhenSuccess(LensContext context) { + return context.getInternalsConfiguration() != null && + context.getInternalsConfiguration().getOperationExecutionRecording() != null && + Boolean.TRUE.equals(context.getInternalsConfiguration().getOperationExecutionRecording().isSkipWhenSuccess()); + } + + private void recordProjectionOperationExecution(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Throwable clockworkException, + Task task, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + PrismObject object = projectionContext.getObjectAny(); + if (object == null) { + return; // this can happen + } + List> executedDeltas = getExecutedDeltas(projectionContext, ShadowType.class, + clockworkException, result); + recordOperationExecution(object, true, executedDeltas, now, + context.getChannel(), getSkipWhenSuccess(context), task, result); + } + + /** + * @return true if the operation execution was recorded (or would be recorded, but skipped because of the configuration) + */ + private boolean recordOperationExecution(PrismObject object, boolean deletedOk, + List> executedDeltas, XMLGregorianCalendar now, + String channel, boolean skipWhenSuccess, Task task, OperationResult result) + throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException { + OperationExecutionType operation = new OperationExecutionType(prismContext); + OperationResult summaryResult = new OperationResult("recordOperationExecution"); + String oid = object.getOid(); + for (LensObjectDeltaOperation deltaOperation : executedDeltas) { + operation.getOperation().add(createObjectDeltaOperation(deltaOperation)); + if (deltaOperation.getExecutionResult() != null) { + summaryResult.addSubresult(deltaOperation.getExecutionResult().clone()); // todo eliminate this clone (but beware of modifying the subresult) + } + if (oid == null && deltaOperation.getObjectDelta() != null) { + oid = deltaOperation.getObjectDelta().getOid(); + } + } + if (oid == null) { // e.g. if there is an exception in provisioning.addObject method + LOGGER.trace("recordOperationExecution: skipping because oid is null for object = {}", object); + return false; + } + summaryResult.computeStatus(); + OperationResultStatusType overallStatus = summaryResult.getStatus().createStatusType(); + setOperationContext(operation, overallStatus, now, channel, task); + storeOperationExecution(object, oid, operation, deletedOk, skipWhenSuccess, result); + return true; + } + + private void storeOperationExecution(@NotNull PrismObject object, @NotNull String oid, + @NotNull OperationExecutionType executionToAdd, boolean deletedOk, boolean skipWhenSuccess, OperationResult result) + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + Integer recordsToKeep; + Long deleteBefore; + boolean keepNoExecutions = false; + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + if (systemConfiguration != null && systemConfiguration.asObjectable().getCleanupPolicy() != null + && systemConfiguration.asObjectable().getCleanupPolicy().getObjectResults() != null) { + CleanupPolicyType policy = systemConfiguration.asObjectable().getCleanupPolicy().getObjectResults(); + recordsToKeep = policy.getMaxRecords(); + if (recordsToKeep != null && recordsToKeep == 0) { + LOGGER.trace("objectResults.recordsToKeep is 0, will skip storing operationExecutions"); + keepNoExecutions = true; + } + if (policy.getMaxAge() != null) { + XMLGregorianCalendar limit = XmlTypeConverter.addDuration( + XmlTypeConverter.createXMLGregorianCalendar(new Date()), policy.getMaxAge().negate()); + deleteBefore = XmlTypeConverter.toMillis(limit); + } else { + deleteBefore = null; + } + } else { + recordsToKeep = DEFAULT_NUMBER_OF_RESULTS_TO_KEEP; + deleteBefore = null; + } + + String taskOid = executionToAdd.getTaskRef() != null ? executionToAdd.getTaskRef().getOid() : null; + if (executionToAdd.getStatus() == OperationResultStatusType.SUCCESS && skipWhenSuccess) { + // We want to skip writing operationExecution. But let's check if there are some older non-success results + // related to the current task + if (taskOid != null) { + boolean hasNonSuccessFromCurrentTask = object.asObjectable().getOperationExecution().stream() + .anyMatch(oe -> oe.getTaskRef() != null && taskOid.equals(oe.getTaskRef().getOid()) && + oe.getStatus() != OperationResultStatusType.SUCCESS); + if (hasNonSuccessFromCurrentTask) { + LOGGER.trace("Cannot skip OperationExecution recording because there's an older non-success record from the current task"); + } else { + LOGGER.trace("Skipping OperationExecution recording because status is SUCCESS and skipWhenSuccess is true " + + "(and no older non-success records for current task {} exist)", taskOid); + return; + } + } else { + LOGGER.trace("Skipping OperationExecution recording because status is SUCCESS and skipWhenSuccess is true"); + return; + } + } + List executionsToDelete = new ArrayList<>(); + List executions = new ArrayList<>(object.asObjectable().getOperationExecution()); + // delete all executions related to current task and all old ones + for (Iterator iterator = executions.iterator(); iterator.hasNext(); ) { + OperationExecutionType execution = iterator.next(); + boolean isPreviousTaskResult = taskOid != null && execution.getTaskRef() != null && taskOid.equals(execution.getTaskRef().getOid()); + boolean isOld = deleteBefore != null && XmlTypeConverter.toMillis(execution.getTimestamp()) < deleteBefore; + if (isPreviousTaskResult || isOld) { + executionsToDelete.add(execution); + iterator.remove(); + } + } + + // delete all surplus executions + if (recordsToKeep != null && executions.size() > recordsToKeep - 1) { + if (keepNoExecutions) { + executionsToDelete.addAll(executions); + } else { + executions.sort(Comparator.nullsFirst(Comparator.comparing(e -> XmlTypeConverter.toDate(e.getTimestamp())))); + executionsToDelete.addAll(executions.subList(0, executions.size() - (recordsToKeep - 1))); + } + } + // construct and execute the delta + Class objectClass = object.asObjectable().getClass(); + List> deltas = new ArrayList<>(); + if (!keepNoExecutions) { + deltas.add(prismContext.deltaFor(objectClass) + .item(ObjectType.F_OPERATION_EXECUTION) + .add(executionToAdd) + .asItemDelta()); + } + if (!executionsToDelete.isEmpty()) { + deltas.add(prismContext.deltaFor(objectClass) + .item(ObjectType.F_OPERATION_EXECUTION) + .delete(PrismContainerValue.toPcvList(CloneUtil.cloneCollectionMembers(executionsToDelete))) + .asItemDelta()); + } + LOGGER.trace("Operation execution delta:\n{}", DebugUtil.debugDumpLazily(deltas)); + try { + if (!deltas.isEmpty()) { + repositoryService.modifyObject(objectClass, oid, deltas, result); + } + } catch (ObjectNotFoundException e) { + if (!deletedOk) { + throw e; + } else { + LOGGER.trace("Object {} deleted but this was expected.", oid); + result.deleteLastSubresultIfError(); + } + } + } + + private void setOperationContext(OperationExecutionType operation, + OperationResultStatusType overallStatus, XMLGregorianCalendar now, String channel, Task task) { + if (task instanceof RunningTask && ((RunningTask) task).getParentForLightweightAsynchronousTask() != null) { + task = ((RunningTask) task).getParentForLightweightAsynchronousTask(); + } + if (task.isPersistent()) { + operation.setTaskRef(task.getSelfReference()); + } + operation.setStatus(overallStatus); + operation.setInitiatorRef(ObjectTypeUtil.createObjectRef(task.getOwner(), prismContext)); // TODO what if the real initiator is different? (e.g. when executing approved changes) + operation.setChannel(channel); + operation.setTimestamp(now); + } + + private ObjectDeltaOperationType createObjectDeltaOperation(LensObjectDeltaOperation deltaOperation) { + ObjectDeltaOperationType odo; + try { + odo = simplifyOperation(deltaOperation).toLensObjectDeltaOperationType().getObjectDeltaOperation(); + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't create operation information", e); + odo = new ObjectDeltaOperationType(); + OperationResult r = new OperationResult(Clockwork.class.getName() + ".createObjectDeltaOperation"); + r.recordFatalError("Couldn't create operation information: " + e.getMessage(), e); + odo.setExecutionResult(r.createOperationResultType()); + } + return odo; + } + + private LensObjectDeltaOperation simplifyOperation(ObjectDeltaOperation operation) { + LensObjectDeltaOperation rv = new LensObjectDeltaOperation<>(); + rv.setObjectDelta(simplifyDelta(operation.getObjectDelta())); + rv.setExecutionResult(OperationResult.keepRootOnly(operation.getExecutionResult())); + rv.setObjectName(operation.getObjectName()); + rv.setResourceName(operation.getResourceName()); + rv.setResourceOid(operation.getResourceOid()); + return rv; + } + + private ObjectDelta simplifyDelta(ObjectDelta delta) { + return prismContext.deltaFactory().object().create(delta.getObjectTypeClass(), delta.getChangeType()); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AuditHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AuditHelper.java index fb9adacbe37..6f1b9c01ae3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AuditHelper.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/util/AuditHelper.java @@ -22,6 +22,8 @@ import com.evolveum.midpoint.schema.util.ObjectDeltaSchemaLevelUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -30,6 +32,7 @@ import java.util.Collection; import static com.evolveum.midpoint.schema.util.ObjectDeltaSchemaLevelUtil.resolveNames; +import static com.evolveum.midpoint.util.MiscUtil.emptyIfNull; /** * Uses cache repository service to resolve object names. @@ -37,6 +40,8 @@ @Component public class AuditHelper { + private static final Trace LOGGER = TraceManager.getTrace(AuditHelper.class); + @Autowired private AuditService auditService; @Autowired private PrismContext prismContext; @Autowired private SchemaHelper schemaHelper; @@ -44,49 +49,65 @@ public class AuditHelper { @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; + private static final String OP_AUDIT = AuditHelper.class.getName() + ".audit"; + private static final String OP_RESOLVE_NAME = AuditHelper.class.getName() + ".resolveName"; + /** * @param externalNameResolver Name resolver that should be tried first. It should be fast. If it returns null it means * "I don't know". */ public void audit(AuditEventRecord record, ObjectDeltaSchemaLevelUtil.NameResolver externalNameResolver, Task task, OperationResult parentResult) { - // TODO we could obtain names for objects that were deleted e.g. from the lens context (MID-5501) - if (record.getDeltas() != null) { - for (ObjectDeltaOperation objectDeltaOperation : record.getDeltas()) { - ObjectDelta delta = objectDeltaOperation.getObjectDelta(); - ObjectDeltaSchemaLevelUtil.NameResolver nameResolver = (objectClass, oid) -> { - OperationResult result = parentResult.subresult(AuditHelper.class.getName() + ".resolveName") - .setMinor() - .build(); - try { - if (record.getNonExistingReferencedObjects().contains(oid)) { - // This information could come from upper layers (not now, but maybe in the future). - return null; - } - if (externalNameResolver != null) { - PolyString externallyResolvedName = externalNameResolver.getName(objectClass, oid); - if (externallyResolvedName != null) { - return externallyResolvedName; - } + OperationResult result = parentResult.subresult(OP_AUDIT) + .setMinor() + .build(); + try { + LOGGER.trace("Auditing the record:\n{}", record.debugDumpLazily()); + resolveNamesInDeltas(record, externalNameResolver, result); + auditService.audit(record, task); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + } + + private void resolveNamesInDeltas(AuditEventRecord record, ObjectDeltaSchemaLevelUtil.NameResolver externalNameResolver, + OperationResult parentResult) { + for (ObjectDeltaOperation objectDeltaOperation : emptyIfNull(record.getDeltas())) { + ObjectDelta delta = objectDeltaOperation.getObjectDelta(); + ObjectDeltaSchemaLevelUtil.NameResolver nameResolver = (objectClass, oid) -> { + OperationResult result = parentResult.subresult(OP_RESOLVE_NAME) + .setMinor() + .build(); + try { + if (record.getNonExistingReferencedObjects().contains(oid)) { + // This information could come from upper layers (not now, but maybe in the future). + return null; + } + if (externalNameResolver != null) { + PolyString externallyResolvedName = externalNameResolver.getName(objectClass, oid); + if (externallyResolvedName != null) { + return externallyResolvedName; } - // we use only cache-compatible options here, in order to utilize the local or global repository cache - Collection> options = schemaHelper.getOperationOptionsBuilder() - .allowNotFound().build(); - PrismObject object = repositoryService.getObject(objectClass, oid, options, result); - return object.getName(); - } catch (ObjectNotFoundException e) { - record.addNonExistingReferencedObject(oid); - return null; // we will NOT record an error here - } catch (Throwable t) { - result.recordFatalError(t.getMessage(), t); - throw t; - } finally { - result.computeStatusIfUnknown(); } - }; - resolveNames(delta, nameResolver, prismContext); - } + // we use only cache-compatible options here, in order to utilize the local or global repository cache + Collection> options = schemaHelper.getOperationOptionsBuilder() + .allowNotFound().build(); + PrismObject object = repositoryService.getObject(objectClass, oid, options, result); + return object.getName(); + } catch (ObjectNotFoundException e) { + record.addNonExistingReferencedObject(oid); + return null; // we will NOT record an error here + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); + } + }; + resolveNames(delta, nameResolver, prismContext); } - auditService.audit(record, task); } } From b8cb6a8971b5ea7f1031bb63a4546fb1951835d8 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Thu, 16 Apr 2020 18:56:45 +0200 Subject: [PATCH 08/27] Fix ReportUtils.isMetadata method Actually, phantom changes of modifyApproverRef and modifyApprovalComment are not so bad in themselves. They only should not be shown in the audit report. So we fixed the code used for displaying deltas in reports. This resolves MID-6213. --- .../midpoint/report/impl/ReportUtils.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportUtils.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportUtils.java index 0b5986f3c27..96660a7d638 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportUtils.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportUtils.java @@ -561,16 +561,16 @@ private static String printItemDeltaOldValues(ItemPath itemPath, Collection valu } private static boolean isMetadata(ItemDeltaType itemDelta) { - List values = itemDelta.getValue(); - for (Object v : values) { - if (v instanceof MetadataType) { - return true; - } else if (v instanceof RawType) { - return isMetadata(itemDelta.getPath()); + if (isMetadata(itemDelta.getPath())) { + return true; + } else { + for (Object v : itemDelta.getValue()) { + if (v instanceof MetadataType) { + return true; + } } + return false; } - - return false; } private static boolean isMetadata(ItemPathType itemPath) { From 09829ff36619128a47d8eb7133480ba8d1a61f46 Mon Sep 17 00:00:00 2001 From: lskublik Date: Fri, 17 Apr 2020 17:55:12 +0200 Subject: [PATCH 09/27] adding schrodinger tests for next LABs of demo 101 --- .../SchrodingerComponentInitListener.java | 11 +- .../schrodinger/AbstractSchrodingerTest.java | 23 +- .../schrodinger/labs/AbstractLabTest.java | 15 + ...M3ResourcesAttributesAndMappingsTest.java} | 22 +- ...es.java => M4ProvisioningToResources.java} | 8 +- ...ava => M5AccountsAssignmentsAndRoles.java} | 11 +- ...=> M6ConfiguringMultipleAccountTypes.java} | 26 +- .../labs/M7SynchronizationFlavours.java | 238 ++++++++ .../labs/M8ExtendingMidPointXMLSchema.java | 134 +++++ .../labs/M9OrganizationalStructure.java | 167 ++++++ .../labs/SynchronizationFlavours.java | 49 -- .../schrodinger/page/TaskPageTest.java | 24 +- .../scenarios/SynchronizationTests.java | 3 +- .../scenarios/UserAccountTests.java | 26 +- .../archetypes/archetype-org-company.xml | 37 ++ .../archetypes/archetype-org-functional.xml | 46 ++ .../archetypes/archetype-org-group-list.xml | 37 ++ .../archetypes/archetype-org-group.xml | 36 ++ .../labs/objects/org/org-example.xml | 473 +++++++++++++++ .../labs/objects/org/org-secret-ops.xml | 98 ++++ .../localhost-csvfile-3-ldap-8-1.xml | 537 ++++++++++++++++++ .../resources/localhost-hr-noextension.xml | 351 ++++++++++++ .../labs/objects/resources/localhost-hr.xml | 411 ++++++++++++++ .../test/resources/labs/sources/csv-1-7-3.csv | 22 + .../labs/sources/source-7-4-part-1.csv | 16 + .../labs/sources/source-7-4-part-2.csv | 16 + .../labs/sources/source-7-4-part-3.csv | 16 + .../labs/sources/source-7-4-part-4.csv | 16 + .../test/resources/labs/sources/source.csv | 15 + .../schrodingertest/testng-integration.xml | 11 +- .../midpoint/schrodinger/MidPoint.java | 2 + .../schrodinger/component/AssignmentsTab.java | 18 +- .../AssignmentHolderObjectListPage.java | 7 + .../AssignmentHolderObjectListTable.java | 4 +- .../schrodinger/component/common/Paging.java | 8 +- .../component/common/PrismForm.java | 14 + .../component/common/table/Table.java | 4 +- .../common/table/TableWithPageRedirect.java | 25 + .../modal/FocusSetAssignmentsModal.java | 8 +- .../modal/ObjectBrowserModalTable.java | 6 + .../component/org/MemberPanel.java | 77 +++ .../component/org/OrgHierarchyPanel.java | 101 ++++ .../schrodinger/component/org/OrgRootTab.java | 39 ++ .../component/org/OrgTreeNodeDropDown.java | 32 ++ .../resource/ResourceAccountsTab.java | 26 +- .../resource/ResourceShadowTable.java | 24 +- ...=> ResourceShadowTableHeaderDropDown.java} | 13 +- .../resource/ResourcesPageTable.java | 6 + .../table/DirectIndirectAssignmentTable.java | 3 +- .../table/TableHeaderDropDownMenu.java | 23 + .../task/OperationStatisticsTab.java | 27 + .../component/task/TasksPageTable.java | 12 +- .../component/user/ProjectionsDropDown.java | 3 +- .../component/user/UsersPageTable.java | 6 +- .../component/user/UsersTableDropDown.java | 3 +- .../midpoint/schrodinger/page/FocusPage.java | 15 - .../page/archetype/ListArchetypesPage.java | 7 + .../page/cases/CasesListTable.java | 7 + .../page/cases/ChildrenCaseTable.java | 6 + .../schrodinger/page/org/OrgTreePage.java | 21 + .../schrodinger/page/task/ListTasksPage.java | 4 +- .../schrodinger/page/task/TaskPage.java | 20 +- .../schrodinger/page/user/ListUsersPage.java | 4 +- 63 files changed, 3258 insertions(+), 212 deletions(-) rename testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/{ResourcesAttributesAndMappingsTest.java => M3ResourcesAttributesAndMappingsTest.java} (90%) rename testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/{ProvisioningToResources.java => M4ProvisioningToResources.java} (97%) rename testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/{AccountsAssignmentsAndRoles.java => M5AccountsAssignmentsAndRoles.java} (96%) rename testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/{ConfiguringMultipleAccountTypes.java => M6ConfiguringMultipleAccountTypes.java} (81%) create mode 100644 testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java create mode 100644 testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java create mode 100644 testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java delete mode 100644 testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/SynchronizationFlavours.java create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-company.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-functional.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group-list.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/org/org-example.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/org/org-secret-ops.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-csvfile-3-ldap-8-1.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr-noextension.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr.xml create mode 100644 testing/schrodingertest/src/test/resources/labs/sources/csv-1-7-3.csv create mode 100644 testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-1.csv create mode 100644 testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-2.csv create mode 100644 testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-3.csv create mode 100644 testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-4.csv create mode 100644 testing/schrodingertest/src/test/resources/labs/sources/source.csv create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgRootTab.java create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java rename tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/{ResourceShadowTableCog.java => ResourceShadowTableHeaderDropDown.java} (86%) create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/TableHeaderDropDownMenu.java create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/OperationStatisticsTab.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/SchrodingerComponentInitListener.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/SchrodingerComponentInitListener.java index 56f10ef0a73..bae4700c3e2 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/SchrodingerComponentInitListener.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/SchrodingerComponentInitListener.java @@ -11,6 +11,9 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.gui.api.prism.ItemWrapper; +import com.evolveum.midpoint.gui.impl.prism.*; + import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.reflect.FieldUtils; import org.apache.wicket.AttributeModifier; @@ -21,8 +24,6 @@ import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.Response; -import com.evolveum.midpoint.gui.impl.prism.PrismPropertyPanel; -import com.evolveum.midpoint.gui.impl.prism.PrismPropertyWrapper; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.web.component.prism.PrismHeaderPanel; @@ -76,9 +77,9 @@ public void afterRender(Component component) { } private void handleLocalization(Component component) { - if (component instanceof PrismPropertyPanel) { - PrismPropertyPanel ppp = (PrismPropertyPanel) component; - PrismPropertyWrapper iw = (PrismPropertyWrapper) ppp.getModel().getObject(); + if (component instanceof PrismPropertyPanel || component instanceof PrismReferencePanel) { + ItemPanel ppp = (ItemPanel) component; + ItemWrapper iw = (ItemWrapper) ppp.getModel().getObject(); String key = iw.getDisplayName(); QName qname = iw.getItemName(); diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java index 89609315e04..fb1ab47b74d 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java @@ -18,6 +18,7 @@ import com.evolveum.midpoint.schrodinger.component.AssignmentsTab; +import com.evolveum.midpoint.schrodinger.component.common.FeedbackBox; import com.evolveum.midpoint.schrodinger.component.common.table.AbstractTableWithPrismView; import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; @@ -52,7 +53,7 @@ @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @ActiveProfiles("default") @SpringBootTest(classes = MidPointSpringApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@TestPropertySource(properties = { "server.port=8180", "midpoint.schrodinger=true" })//, "webdriverLocation=234234234" }) +@TestPropertySource(properties = { "server.port=8180", "midpoint.schrodinger=true" }) @Listeners({ BrowserPerClass.class }) public abstract class AbstractSchrodingerTest extends AbstractIntegrationTest { @@ -155,7 +156,7 @@ public void afterClass() { .clickYes(); } - protected void importObject(File source, Boolean overrideExistingObject) { + protected void importObject(File source, boolean overrideExistingObject, boolean ignoreWarning) { ImportObjectPage importPage = basicPage.importObject(); if (overrideExistingObject) { @@ -163,17 +164,21 @@ protected void importObject(File source, Boolean overrideExistingObject) { .checkOverwriteExistingObject(); } - Assert.assertTrue( - importPage - .getObjectsFromFile() - .chooseFile(source) + FeedbackBox feedback = importPage + .getObjectsFromFile() + .chooseFile(source) .clickImportFileButton() - .feedback() - .isSuccess()); + .feedback(); + + Assert.assertTrue(feedback.isSuccess() || (ignoreWarning && feedback.isWarning())); + } + + protected void importObject(File source, boolean overrideExistingObject) { + importObject(source, overrideExistingObject, false); } protected void importObject(File source) { - importObject(source, false); + importObject(source, false, false); } protected String fetchMidpointHome() { diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/AbstractLabTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/AbstractLabTest.java index 88ac2a83ba6..6a3cd6bf98a 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/AbstractLabTest.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/AbstractLabTest.java @@ -44,20 +44,33 @@ public class AbstractLabTest extends AbstractSchrodingerTest { protected static final File CSV_1_SIMPLE_RESOURCE_FILE = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-1-document-access-simple.xml"); protected static final File CSV_1_RESOURCE_FILE = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-1-document-access.xml"); protected static final File CSV_1_SOURCE_FILE = new File(LAB_SOURCES_DIRECTORY + "csv-1.csv"); + protected static final File CSV_1_SOURCE_FILE_7_3 = new File(LAB_SOURCES_DIRECTORY + "csv-1-7-3.csv"); protected static final File NUMERIC_PIN_FIRST_NONZERO_POLICY_FILE = new File(LAB_OBJECTS_DIRECTORY + "valuePolicies/numeric-pin-first-nonzero-policy.xml"); protected static final File CSV_2_RESOURCE_FILE = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-2-canteen.xml"); protected static final File CSV_2_SOURCE_FILE = new File(LAB_SOURCES_DIRECTORY + "csv-2.csv"); protected static final File CSV_3_RESOURCE_FILE = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-3-ldap.xml"); protected static final File CSV_3_SOURCE_FILE = new File(LAB_SOURCES_DIRECTORY + "csv-3.csv"); + protected static final File HR_NO_EXTENSION_RESOURCE_FILE = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-hr-noextension.xml"); + protected static final File HR_SOURCE_FILE = new File(LAB_SOURCES_DIRECTORY + "source.csv"); + protected static final File HR_SOURCE_FILE_7_4_PART_1 = new File(LAB_SOURCES_DIRECTORY + "source-7-4-part-1.csv"); + protected static final File HR_SOURCE_FILE_7_4_PART_2 = new File(LAB_SOURCES_DIRECTORY + "source-7-4-part-2.csv"); + protected static final File HR_SOURCE_FILE_7_4_PART_3 = new File(LAB_SOURCES_DIRECTORY + "source-7-4-part-3.csv"); + protected static final File HR_SOURCE_FILE_7_4_PART_4 = new File(LAB_SOURCES_DIRECTORY + "source-7-4-part-4.csv"); + protected static final String DIRECTORY_CURRENT_TEST = "labTests"; protected static final String EXTENSION_SCHEMA_NAME = "extension-example.xsd"; protected static final String CSV_1_FILE_SOURCE_NAME = "csv-1.csv"; protected static final String CSV_1_RESOURCE_NAME = "CSV-1 (Document Access)"; + protected static final String CSV_1_RESOURCE_OID = "10000000-9999-9999-0000-a000ff000002"; protected static final String CSV_2_FILE_SOURCE_NAME = "csv-2.csv"; protected static final String CSV_2_RESOURCE_NAME = "CSV-2 (Canteen Ordering System)"; + protected static final String CSV_2_RESOURCE_OID = "10000000-9999-9999-0000-a000ff000003"; protected static final String CSV_3_FILE_SOURCE_NAME = "csv-3.csv"; protected static final String CSV_3_RESOURCE_NAME = "CSV-3 (LDAP)"; + protected static final String CSV_3_RESOURCE_OID = "10000000-9999-9999-0000-a000ff000004"; + protected static final String HR_FILE_SOURCE_NAME = "source.csv"; + protected static final String HR_RESOURCE_NAME = "ExAmPLE, Inc. HR Source"; protected static final String PASSWORD_ATTRIBUTE_RESOURCE_KEY = "User password attribute name"; protected static final String UNIQUE_ATTRIBUTE_RESOURCE_KEY = "Unique attribute name"; @@ -65,12 +78,14 @@ public class AbstractLabTest extends AbstractSchrodingerTest { protected static final String CSV_1_UNIQUE_ATTRIBUTE_NAME = "login"; protected static final String CSV_1_PASSWORD_ATTRIBUTE_NAME = "password"; protected static final String CSV_1_ACCOUNT_OBJECT_CLASS_LINK = "AccountObjectClass (Default Account)"; + protected static final String ARCHETYPE_EMPLOYEE_PLURAL_LABEL = "Employees"; protected static final List CSV_1_RESOURCE_ATTRIBUTES = Arrays.asList("login", "lname", "groups", "enumber", "phone", "dep", "fname", "dis"); protected static File csv1TargetFile; protected static File csv2TargetFile; protected static File csv3TargetFile; + protected static File hrTargetFile; @AfterClass @Override diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ResourcesAttributesAndMappingsTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M3ResourcesAttributesAndMappingsTest.java similarity index 90% rename from testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ResourcesAttributesAndMappingsTest.java rename to testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M3ResourcesAttributesAndMappingsTest.java index 9dabbcaa688..17260f585b0 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ResourcesAttributesAndMappingsTest.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M3ResourcesAttributesAndMappingsTest.java @@ -41,27 +41,7 @@ * @author skublik */ -public class ResourcesAttributesAndMappingsTest extends AbstractLabTest { - -// @BeforeClass(alwaysRun = true, dependsOnMethods = { "springTestContextBeforeTestClass" }) -// @Override -// protected void springTestContextPrepareTestInstance() throws Exception { -// -// String home = System.getProperty("midpoint.home"); -// File schemaDir = new File(home, "schema"); -// -// if (!schemaDir.mkdir()) { -// if (schemaDir.exists()) { -// FileUtils.cleanDirectory(schemaDir); -// } else { -// throw new IOException("Creation of directory \"" + schemaDir.getAbsolutePath() + "\" unsuccessful"); -// } -// } -// File schemaFile = new File(schemaDir, EXTENSION_SCHEMA_NAME); -// FileUtils.copyFile(EXTENSION_SCHEMA_FILE, schemaFile); -// -// super.springTestContextPrepareTestInstance(); -// } +public class M3ResourcesAttributesAndMappingsTest extends AbstractLabTest { @BeforeClass(alwaysRun = true, dependsOnMethods = { "springTestContextPrepareTestInstance" }) @Override diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ProvisioningToResources.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M4ProvisioningToResources.java similarity index 97% rename from testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ProvisioningToResources.java rename to testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M4ProvisioningToResources.java index 0dffc9aa115..ad46cff6900 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ProvisioningToResources.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M4ProvisioningToResources.java @@ -14,7 +14,6 @@ import com.evolveum.midpoint.schrodinger.component.common.PrismForm; import com.evolveum.midpoint.schrodinger.component.common.PrismFormWithActionButtons; import com.evolveum.midpoint.schrodinger.component.common.table.AbstractTableWithPrismView; -import com.evolveum.midpoint.schrodinger.page.configuration.AboutPage; import com.evolveum.midpoint.schrodinger.page.resource.AccountPage; import com.evolveum.midpoint.schrodinger.page.user.UserPage; @@ -27,7 +26,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; -import org.testng.annotations.AfterClass; import org.testng.annotations.Test; import javax.xml.namespace.QName; @@ -37,9 +35,9 @@ * @author skublik */ -public class ProvisioningToResources extends AbstractLabTest { +public class M4ProvisioningToResources extends AbstractLabTest { - private static final Logger LOG = LoggerFactory.getLogger(ProvisioningToResources.class); + private static final Logger LOG = LoggerFactory.getLogger(M4ProvisioningToResources.class); private static final File CSV_1_RESOURCE_FILE_4_2 = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-1-document-access-4-2.xml"); private static final File CSV_3_RESOURCE_FILE_4_2 = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-3-ldap-4-2.xml"); @@ -104,7 +102,7 @@ public void test0401BasicProvisioningToMultipleResources() { .selectAll() .and() .table() - .clickActionDropDown() + .clickHeaderActionDropDown() .clickEnable() .clickYes(); diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/AccountsAssignmentsAndRoles.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M5AccountsAssignmentsAndRoles.java similarity index 96% rename from testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/AccountsAssignmentsAndRoles.java rename to testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M5AccountsAssignmentsAndRoles.java index 05060940fc8..6e52b639921 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/AccountsAssignmentsAndRoles.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M5AccountsAssignmentsAndRoles.java @@ -7,7 +7,6 @@ package com.evolveum.midpoint.testing.schrodinger.labs; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import com.codeborne.selenide.Selenide; @@ -18,7 +17,6 @@ import com.evolveum.midpoint.schrodinger.component.AssignmentsTab; import com.evolveum.midpoint.schrodinger.component.InducementsTab; import com.evolveum.midpoint.schrodinger.component.common.PrismForm; -import com.evolveum.midpoint.schrodinger.component.common.table.AbstractTableWithPrismView; import com.evolveum.midpoint.schrodinger.component.table.DirectIndirectAssignmentTable; import com.evolveum.midpoint.schrodinger.page.AbstractRolePage; import com.evolveum.midpoint.schrodinger.page.archetype.ArchetypePage; @@ -36,8 +34,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import javax.xml.namespace.QName; @@ -46,9 +42,9 @@ * @author skublik */ -public class AccountsAssignmentsAndRoles extends AbstractLabTest { +public class M5AccountsAssignmentsAndRoles extends AbstractLabTest { - private static final Logger LOG = LoggerFactory.getLogger(AccountsAssignmentsAndRoles.class); + private static final Logger LOG = LoggerFactory.getLogger(M5AccountsAssignmentsAndRoles.class); private static final File INCOGNITO_ROLE_FILE = new File(LAB_OBJECTS_DIRECTORY + "roles/role-incognito.xml"); private static final File INTERNAL_EMPLOYEE_ROLE_FILE = new File(LAB_OBJECTS_DIRECTORY + "roles/role-internal-employee.xml"); @@ -61,7 +57,6 @@ public class AccountsAssignmentsAndRoles extends AbstractLabTest { private static final File ARCHETYPE_EMPLOYEE_FILE = new File(LAB_OBJECTS_DIRECTORY + "archetypes/archetype-employee.xml"); private static final String ARCHETYPE_EMPLOYEE_NAME = "Employee"; private static final String ARCHETYPE_EMPLOYEE_LABEL = "Employee"; - private static final String ARCHETYPE_EMPLOYEE_PLURAL_LABEL = "Employees"; private static final File ARCHETYPE_EXTERNAL_FILE = new File(LAB_OBJECTS_DIRECTORY + "archetypes/archetype-external.xml"); private static final File SYSTEM_CONFIGURATION_FILE_5_7 = new File(LAB_OBJECTS_DIRECTORY + "systemConfiguration/system-configuration-5-7.xml"); @@ -254,7 +249,7 @@ public void test0507ArchetypesIntroduction() { FormLoginPage login = midPoint.formLogin(); login.login(getUsername(), getPassword()); - basicPage.listUsers().newUser(ARCHETYPE_EMPLOYEE_LABEL) + basicPage.listUsers().newUser("New "+ARCHETYPE_EMPLOYEE_LABEL.toLowerCase()) .selectTabBasic() .form() .addAttributeValue(UserType.F_NAME, "janeway") diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ConfiguringMultipleAccountTypes.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M6ConfiguringMultipleAccountTypes.java similarity index 81% rename from testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ConfiguringMultipleAccountTypes.java rename to testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M6ConfiguringMultipleAccountTypes.java index c6e924370d0..2f7218ec91b 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/ConfiguringMultipleAccountTypes.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M6ConfiguringMultipleAccountTypes.java @@ -6,11 +6,8 @@ */ package com.evolveum.midpoint.testing.schrodinger.labs; -import com.codeborne.selenide.Selenide; - import com.evolveum.midpoint.schrodinger.component.ProjectionsTab; import com.evolveum.midpoint.schrodinger.component.common.table.AbstractTableWithPrismView; -import com.evolveum.midpoint.schrodinger.page.configuration.AboutPage; import com.evolveum.midpoint.schrodinger.page.user.UserPage; import com.evolveum.midpoint.schrodinger.util.Utils; import com.evolveum.midpoint.testing.schrodinger.scenarios.ScenariosCommons; @@ -18,21 +15,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import javax.validation.constraints.AssertTrue; import java.io.File; -import java.io.IOException; /** * @author skublik */ -public class ConfiguringMultipleAccountTypes extends AbstractLabTest { +public class M6ConfiguringMultipleAccountTypes extends AbstractLabTest { - private static final Logger LOG = LoggerFactory.getLogger(ConfiguringMultipleAccountTypes.class); + private static final Logger LOG = LoggerFactory.getLogger(M6ConfiguringMultipleAccountTypes.class); private static final File CSV_1_RESOURCE_FILE_6_1 = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-1-document-access-6-1.xml"); private static final File CSV_3_RESOURCE_FILE_6_1 = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-3-ldap-6-1.xml"); @@ -41,21 +34,6 @@ public class ConfiguringMultipleAccountTypes extends AbstractLabTest { private static final String CSV1_TESTER_ROLE_NAME = "CSV-1 Tester"; private static final String CSV3_ADMIN_ROLE_NAME = "CSV-3 Admin"; - @AfterClass - @Override - public void afterClass() { - super.afterClass(); - - midPoint.formLogin().loginWithReloadLoginPage(username, password); - - LOG.info("After: Login name " + username + " pass " + password); - - AboutPage aboutPage = basicPage.aboutPage(); - aboutPage - .clickSwitchToFactoryDefaults() - .clickYes(); - } - @Test public void test0601UsingAccountIntentsForProvisioning() { diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java new file mode 100644 index 00000000000..ed5e96a01ec --- /dev/null +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java @@ -0,0 +1,238 @@ +/* + * 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.testing.schrodinger.labs; + +import com.codeborne.selenide.Selenide; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.resource.ResourceAccountsTab; +import com.evolveum.midpoint.schrodinger.page.resource.ViewResourcePage; +import com.evolveum.midpoint.schrodinger.page.task.TaskPage; +import com.evolveum.midpoint.schrodinger.page.user.UserPage; +import com.evolveum.midpoint.testing.schrodinger.scenarios.ScenariosCommons; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; + +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; + +/** + * @author skublik + */ + +public class M7SynchronizationFlavours extends AbstractLabTest{ + + private static final Logger LOG = LoggerFactory.getLogger(M7SynchronizationFlavours.class); + + @Test + public void test0701RunningImportFromResource() throws IOException { + hrTargetFile = new File(csvTargetDir, HR_FILE_SOURCE_NAME); + FileUtils.copyFile(HR_SOURCE_FILE, hrTargetFile); + + importObject(HR_NO_EXTENSION_RESOURCE_FILE,true); + changeResourceAttribute(HR_RESOURCE_NAME, ScenariosCommons.CSV_RESOURCE_ATTR_FILE_PATH, hrTargetFile.getAbsolutePath(), true); + + ResourceAccountsTab accountTab = basicPage.listResources() + .table() + .clickByName(HR_RESOURCE_NAME) + .clickAccountsTab() + .clickSearchInResource(); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + accountTab.table() + .selectCheckboxByName("001212") + .clickHeaderActionDropDown() + .clickImport() + .and() + .and() + .feedback() + .isSuccess(); + + UserPage owner = accountTab.table() + .clickOnOwnerByName("X001212"); + + Assert.assertTrue(owner.selectTabBasic() + .form() + .compareInputAttributeValue("name", "X001212")); + + basicPage.listResources() + .table() + .clickByName(HR_RESOURCE_NAME) + .clickAccountsTab() + .importTask() + .clickCreateNew() + .selectTabBasic() + .form() + .addAttributeValue("name","Initial import from HR") + .and() + .and() + .clickSaveAndRun() + .feedback() + .isInfo(); + + Assert.assertEquals(basicPage.listTasks() + .table() + .clickByName("Initial import from HR") + .selectTabOperationStatistics() + .getSuccessfullyProcessed(), 14); + Assert.assertEquals(basicPage.listUsers(ARCHETYPE_EMPLOYEE_PLURAL_LABEL).getCountOfObjects(), 15); + } + + @Test(dependsOnMethods = {"test0701RunningImportFromResource"}) + public void test0702RunningAccountReconciliation() { + Selenide.sleep(MidPoint.TIMEOUT_MEDIUM_6_S); + createReconTask("CSV-1 Reconciliation", CSV_1_RESOURCE_NAME); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + deselectDryRun("CSV-1 Reconciliation"); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + Assert.assertTrue(containsProjection("X001212", CSV_1_RESOURCE_OID, "jsmith")); + + createReconTask("CSV-2 Reconciliation", CSV_2_RESOURCE_NAME); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + deselectDryRun("CSV-2 Reconciliation"); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + Assert.assertTrue(containsProjection("X001212", CSV_2_RESOURCE_OID, "jsmith")); + + createReconTask("CSV-3 Reconciliation", CSV_3_RESOURCE_NAME); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + deselectDryRun("CSV-3 Reconciliation"); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + Assert.assertTrue(containsProjection("X001212", CSV_3_RESOURCE_OID, "cn=John Smith,ou=ExAmPLE,dc=example,dc=com")); + } + + @Test(dependsOnMethods = {"test0702RunningAccountReconciliation"}) + public void test0703RunningAttributeReconciliation() throws IOException { + FileUtils.copyFile(CSV_1_SOURCE_FILE_7_3, csv1TargetFile); + + basicPage.listTasks() + .table() + .clickByName("CSV-1 Reconciliation") + .clickRunNow(); + + Assert.assertTrue( + showShadow(CSV_1_RESOURCE_NAME, "Login", "jkirk") + .form() + .compareInputAttributeValues("groups", "Internal Employees", + "Essential Documents")); + + } + + @Test(dependsOnMethods = {"test0703RunningAttributeReconciliation"}) + public void test0704RunningLiveSync() throws IOException { + Selenide.sleep(MidPoint.TIMEOUT_MEDIUM_6_S); + TaskPage task = basicPage.newTask(); + task.setHandlerUriForNewTask("Live synchronization task"); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + task.selectTabBasic() + .form() + .addAttributeValue("objectclass", "AccountObjectClass") + .addAttributeValue(TaskType.F_NAME, "HR Synchronization") + .selectOption("recurrence","Recurring") + .selectOption("binding","Tight") + .editRefValue("objectRef") + .selectType("Resource") + .table() + .clickByName(HR_RESOURCE_NAME) + .and() + .and() + .selectScheduleTab() + .form() + .addAttributeValue("interval", "5") + .and() + .and() + .clickSaveAndRun() + .feedback() + .isInfo(); + + FileUtils.copyFile(HR_SOURCE_FILE_7_4_PART_1, hrTargetFile); + Selenide.sleep(20000); + Assert.assertTrue(showUser("X000999") + .selectTabBasic() + .form() + .compareInputAttributeValue("givenName", "Arnold")); + Assert.assertTrue(showUser("X000999") + .selectTabBasic() + .form() + .compareInputAttributeValue("familyName", "Rimmer")); + Assert.assertTrue(showUser("X000999") + .selectTabBasic() + .form() + .compareSelectAttributeValue("administrativeStatus", "Enabled")); + + FileUtils.copyFile(HR_SOURCE_FILE_7_4_PART_2, hrTargetFile); + Selenide.sleep(20000); + Assert.assertTrue(showUser("X000999") + .selectTabBasic() + .form() + .compareInputAttributeValue("givenName", "Arnold J.")); + + FileUtils.copyFile(HR_SOURCE_FILE_7_4_PART_3, hrTargetFile); + Selenide.sleep(20000); + Assert.assertTrue(showUser("X000999") + .selectTabBasic() + .form() + .compareSelectAttributeValue("administrativeStatus", "Disabled")); + + FileUtils.copyFile(HR_SOURCE_FILE_7_4_PART_4, hrTargetFile); + Selenide.sleep(20000); + Assert.assertTrue(showUser("X000999") + .selectTabBasic() + .form() + .compareSelectAttributeValue("administrativeStatus", "Enabled")); + + } + + private boolean containsProjection(String user, String resourceOid, String accountName) { + return showUser(user).selectTabProjections() + .table() + .search() + .byItem("Resource") + .inputRefOid(resourceOid) + .updateSearch() + .and() + .containsText(accountName); + } + + private void createReconTask(String reconTaskName, String resource){ + TaskPage task = basicPage.newTask(); + task.setHandlerUriForNewTask("Reconciliation task"); + Selenide.sleep(MidPoint.TIMEOUT_SHORT_4_S); + task.selectTabBasic() + .form() + .addAttributeValue("objectclass", "AccountObjectClass") + .selectOption("dryRun", "True") + .addAttributeValue(TaskType.F_NAME, reconTaskName) + .editRefValue("objectRef") + .selectType("Resource") + .table() + .clickByName(resource) + .and() + .and() + .clickSaveAndRun() + .feedback() + .isInfo(); + } + + private void deselectDryRun(String taskName) { + basicPage.listTasks() + .table() + .clickByName(taskName) + .selectTabBasic() + .form() + .selectOption("dryRun", "Undefined") + .and() + .and() + .clickSaveAndRun() + .feedback() + .isInfo(); + } +} diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java new file mode 100644 index 00000000000..70709d51823 --- /dev/null +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java @@ -0,0 +1,134 @@ +/* + * 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.testing.schrodinger.labs; + +import com.codeborne.selenide.Selenide; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.AssignmentHolderBasicTab; +import com.evolveum.midpoint.schrodinger.component.common.PrismForm; +import com.evolveum.midpoint.schrodinger.component.resource.ResourceAccountsTab; +import com.evolveum.midpoint.schrodinger.page.resource.AccountPage; +import com.evolveum.midpoint.schrodinger.page.resource.ViewResourcePage; +import com.evolveum.midpoint.schrodinger.page.user.UserPage; + +import com.evolveum.midpoint.testing.schrodinger.scenarios.ScenariosCommons; + +import org.apache.commons.io.FileUtils; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; + +/** + * @author skublik + */ + +public class M8ExtendingMidPointXMLSchema extends AbstractLabTest { + + private static final File HR_RESOURCE_FILE_8_1 = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-hr.xml"); + private static final File CSV_3_RESOURCE_FILE_8_1 = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-3-ldap-8-1.xml"); + + @BeforeClass(alwaysRun = true, dependsOnMethods = { "springTestContextPrepareTestInstance" }) + @Override + public void beforeClass() throws IOException { + super.beforeClass(); + } + + @BeforeClass(alwaysRun = true, dependsOnMethods = { "springTestContextBeforeTestClass" }) + @Override + protected void springTestContextPrepareTestInstance() throws Exception { + String home = System.getProperty("midpoint.home"); + File schemaDir = new File(home, "schema"); + + if (!schemaDir.mkdir()) { + if (schemaDir.exists()) { + FileUtils.cleanDirectory(schemaDir); + } else { + throw new IOException("Creation of directory \"" + schemaDir.getAbsolutePath() + "\" unsuccessful"); + } + } + File schemaFile = new File(schemaDir, EXTENSION_SCHEMA_NAME); + FileUtils.copyFile(EXTENSION_SCHEMA_FILE, schemaFile); + + super.springTestContextPrepareTestInstance(); + } + + @Test + public void test0801ExtendingMidPointXMLSchema() { + PrismForm> form = basicPage.newUser() + .selectTabBasic() + .form(); + + form.findProperty("ouNumber"); + form.findProperty("ouPath"); + form.findProperty("isManager"); + form.findProperty("empStatus"); + + importObject(HR_RESOURCE_FILE_8_1,true); + changeResourceAttribute(HR_RESOURCE_NAME, ScenariosCommons.CSV_RESOURCE_ATTR_FILE_PATH, hrTargetFile.getAbsolutePath(), true); + + importObject(CSV_3_RESOURCE_FILE_8_1,true); + changeResourceAttribute(CSV_3_RESOURCE_NAME, ScenariosCommons.CSV_RESOURCE_ATTR_FILE_PATH, csv3TargetFile.getAbsolutePath(), true); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + ResourceAccountsTab accountTab = basicPage.listResources() + .table() + .clickByName(HR_RESOURCE_NAME) + .clickAccountsTab() + .clickSearchInResource(); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + accountTab.table() + .selectCheckboxByName("001212") + .clickHeaderActionDropDown() + .clickImport() + .and() + .and() + .feedback() + .isSuccess(); + + form = accountTab.table() + .clickOnOwnerByName("X001212") + .selectTabBasic() + .form(); + + Assert.assertTrue(form.compareInputAttributeValue("ouPath", "0300")); + Assert.assertTrue(form.compareSelectAttributeValue("isManager", "True")); + Assert.assertTrue(form.compareInputAttributeValue("empStatus", "A")); + + form.and() + .and() + .selectTabAssignments() + .clickAddAssignemnt() + .table() + .search() + .byName() + .inputValue("Internal Employee") + .updateSearch() + .and() + .selectCheckboxByName("Internal Employee") + .and() + .clickAdd() + .and() + .clickSave() + .feedback() + .isSuccess(); + + AccountPage shadow = showShadow(CSV_1_RESOURCE_NAME, "Login", "jsmith"); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + PrismForm accountForm = shadow.form(); + Selenide.sleep(1000); + Assert.assertTrue(accountForm.compareInputAttributeValue("dep", "Human Resources")); + + showShadow(CSV_2_RESOURCE_NAME, "Login", "jsmith"); + Assert.assertTrue(accountForm.compareInputAttributeValue("department", "Human Resources")); + + Assert.assertTrue(existShadow(CSV_3_RESOURCE_NAME, "Distinguished Name", "cn=John Smith,ou=0300,ou=ExAmPLE,dc=example,dc=com")); + } +} diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java new file mode 100644 index 00000000000..4ccaceb2107 --- /dev/null +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java @@ -0,0 +1,167 @@ +/* + * 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.testing.schrodinger.labs; + +import com.evolveum.midpoint.schrodinger.page.configuration.AboutPage; +import com.evolveum.midpoint.schrodinger.page.login.FormLoginPage; +import com.evolveum.midpoint.schrodinger.page.org.OrgPage; +import com.evolveum.midpoint.schrodinger.page.org.OrgTreePage; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; + +/** + * @author skublik + */ + +public class M9OrganizationalStructure extends AbstractLabTest{ + + private static final Logger LOG = LoggerFactory.getLogger(M9OrganizationalStructure.class); + + private static final File ARCHETYPE_ORG_COMPANY_FILE = new File(LAB_OBJECTS_DIRECTORY + "archetypes/archetype-org-company.xml"); + private static final File ARCHETYPE_ORG_FUNCTIONAL_FILE = new File(LAB_OBJECTS_DIRECTORY + "archetypes/archetype-org-functional.xml"); + private static final File ARCHETYPE_ORG_GROUP_LIST_FILE = new File(LAB_OBJECTS_DIRECTORY + "archetypes/archetype-org-group-list.xml"); + private static final File ARCHETYPE_ORG_GROUP_FILE = new File(LAB_OBJECTS_DIRECTORY + "archetypes/archetype-org-group.xml"); + private static final File ORG_EXAMPLE_FILE = new File(LAB_OBJECTS_DIRECTORY + "org/org-example.xml"); + private static final File ORG_SECRET_OPS_FILE = new File(LAB_OBJECTS_DIRECTORY + "org/org-secret-ops.xml"); + + @AfterClass + @Override + public void afterClass() { + super.afterClass(); + + midPoint.formLogin().loginWithReloadLoginPage(username, password); + + LOG.info("After: Login name " + username + " pass " + password); + + AboutPage aboutPage = basicPage.aboutPage(); + aboutPage + .clickSwitchToFactoryDefaults() + .clickYes(); + } + + @BeforeClass + @Override + public void beforeClass() throws IOException { + super.beforeClass(); + csv1TargetFile = new File("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-1.csv"); + csv2TargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-2.csv"); + csv3TargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-3.csv"); + hrTargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/source.csv"); + } + + @Test + public void test0901ImportStaticOrgStructure() { + importObject(ARCHETYPE_ORG_FUNCTIONAL_FILE, true, true); + importObject(ARCHETYPE_ORG_COMPANY_FILE, true); + importObject(ARCHETYPE_ORG_GROUP_FILE, true); + importObject(ARCHETYPE_ORG_GROUP_LIST_FILE, true); + + basicPage.loggedUser().logoutIfUserIsLogin(); + FormLoginPage login = midPoint.formLogin(); + login.login(getUsername(), getPassword()); + + importObject(ORG_EXAMPLE_FILE, true); + + OrgTreePage orgTree = basicPage.orgStructure(); + Assert.assertTrue(orgTree.selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .containsChildOrg("ExAmPLE, Inc. - Functional Structure", "Executive Division", "Sales Department", + "Human Resources", "Technology Division", "IT Administration Department", "Software Department", "Java Development")); + Assert.assertTrue(orgTree.selectTabWithRootOrg("Groups") + .getOrgHierarchyPanel() + .containsChildOrg("Groups", "Active Employees", "Administrators", "Contractors", "Former Employees", + "Inactive Employees", "Security")); + + importObject(ORG_SECRET_OPS_FILE, true); + Assert.assertTrue(basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .containsChildOrg("Secret Operations", "Transportation and Logistics Department")); + } + + @Test(dependsOnMethods = {"test0901ImportStaticOrgStructure"}) + public void test0902CreateStaticOrgStructure() { + basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .expandOrg("Secret Operations") + .selectOrgInTree("Transportation and Logistics Department") + .and() + .getMemberPanel() + .newMember("Create Organization type member with default relation"); + new OrgPage() + .selectTabBasic() + .form() + .addAttributeValue(OrgType.F_NAME, "0919") + .addAttributeValue(OrgType.F_DISPLAY_NAME, "Warp Speed Research") + .and() + .and() + .clickSave() + .feedback() + .isSuccess(); + + Assert.assertTrue(basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .expandOrg("Secret Operations") + .expandOrg("Transportation and Logistics Department") + .editOrg("Warp Speed Research") + .edit() + .selectTabBasic() + .form() + .compareInputAttributeValue("name", "0919")); + + showUser("kirk").selectTabAssignments() + .clickAddAssignemnt("New Organization type assignment with default relation") + .table() + .paging() + .next() + .and() + .and() + .table() + .selectCheckboxByName("0919") + .and() + .clickAdd() + .and() + .clickSave() + .feedback() + .isSuccess(); + + Assert.assertTrue(basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .expandOrg("Secret Operations") + .expandOrg("Transportation and Logistics Department") + .selectOrgInTree("Warp Speed Research") + .and() + .getMemberPanel() + .selectType("User") + .table() + .containsText("kirk")); + + showUser("kirk").selectTabAssignments() + .table() + .selectCheckboxByName("Warp Speed Research") + .removeByName("Warp Speed Research") + .and() + .and() + .clickSave() + .feedback() + .isSuccess(); + } + +} diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/SynchronizationFlavours.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/SynchronizationFlavours.java deleted file mode 100644 index 62b4a48003e..00000000000 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/SynchronizationFlavours.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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.testing.schrodinger.labs; - -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.io.File; -import java.io.IOException; - -/** - * @author skublik - */ - -public class SynchronizationFlavours extends AbstractLabTest{ - -// @AfterClass -// @Override -// public void afterClass() { -// super.afterClass(); -// -// midPoint.formLogin().loginWithReloadLoginPage(username, password); -// -// LOG.info("After: Login name " + username + " pass " + password); -// -// AboutPage aboutPage = basicPage.aboutPage(); -// aboutPage -// .clickSwitchToFactoryDefaults() -// .clickYes(); -// } - - @BeforeClass - @Override - public void beforeClass() throws IOException { - super.beforeClass(); - csv1TargetFile = new File("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-1.csv"); - csv2TargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-2.csv"); - csv3TargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-3.csv"); - } - - @Test - public void test0701RunningImportFromResource() { - - } -} diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/TaskPageTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/TaskPageTest.java index fd739507610..9d254111aaf 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/TaskPageTest.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/page/TaskPageTest.java @@ -8,6 +8,7 @@ import com.codeborne.selenide.Selenide; +import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.AssignmentHolderBasicTab; import com.evolveum.midpoint.schrodinger.component.common.PrismForm; import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; @@ -31,10 +32,9 @@ public class TaskPageTest extends AbstractSchrodingerTest { public void test001createNewTask() { String name = "NewTest"; - String handler = "Recompute task"; + Selenide.sleep(MidPoint.TIMEOUT_MEDIUM_6_S); TaskPage task = basicPage.newTask(); - task.selectTabBasic().form().addAttributeValue("handlerUri", handler); - Selenide.sleep(4000); + task.setHandlerUriForNewTask("Recompute task"); task.selectTabBasic() .form() .addAttributeValue("name", name) @@ -47,16 +47,16 @@ public void test001createNewTask() { ListTasksPage tasksPage = basicPage.listTasks(); PrismForm> taskForm = tasksPage .table() - .search() - .byName() - .inputValue(name) - .updateSearch() - .and() - .clickByName(name) - .selectTabBasic() - .form(); + .search() + .byName() + .inputValue(name) + .updateSearch() + .and() + .clickByName(name) + .selectTabBasic() + .form(); Assert.assertTrue(taskForm.compareInputAttributeValue("name", name)); - Assert.assertTrue(taskForm.compareInputAttributeValue("handlerUri", handler)); + Assert.assertTrue(taskForm.compareInputAttributeValue("handlerUri", "http://midpoint.evolveum.com/xml/ns/public/model/synchronization/task/recompute/handler-3")); } } diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/SynchronizationTests.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/SynchronizationTests.java index 8669374cfa8..4f3aba2be36 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/SynchronizationTests.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/SynchronizationTests.java @@ -74,7 +74,6 @@ public void setUpResourceAndSynchronizationTask() throws IOException { .form() .addAttributeValue("name","LiveSyncTest") .selectOption("recurrence","Recurring") - .selectOption("executionStatus", "Runnable") .and() .and()) .selectScheduleTab() @@ -266,7 +265,7 @@ public void resourceAccountDeleted(){ .clickSearchInResource() .table() .selectCheckboxByName("raphael") - .clickCog() + .clickHeaderActionDropDown() .clickDelete() .clickYes() .and() diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/UserAccountTests.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/UserAccountTests.java index 461f95553c0..c1fc2b20d05 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/UserAccountTests.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/UserAccountTests.java @@ -124,13 +124,13 @@ public void bulkDisableUsers(){ .selectAll() .and() .table() - .clickActionDropDown() - .clickDisable() - .clickYes() - .and() - .and() - .feedback() - .isSuccess() + .clickHeaderActionDropDown() + .clickDisable() + .clickYes() + .and() + .and() + .feedback() + .isSuccess() ; } @@ -198,12 +198,12 @@ public void bulkDeleteUsers(){ .selectAll() .and() .table() - .clickActionDropDown() - .clickDelete() - .clickYes() + .clickHeaderActionDropDown() + .clickDelete() + .clickYes() + .and() .and() - .and() - .feedback() - .isSuccess() + .feedback() + .isSuccess() ;} } diff --git a/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-company.xml b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-company.xml new file mode 100644 index 00000000000..e927054a410 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-company.xml @@ -0,0 +1,37 @@ + + + + Company + + + + Companies + + fa fa-sitemap + + + + + + + OrgType + + + + + OrgType + + + org:default + + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-functional.xml b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-functional.xml new file mode 100644 index 00000000000..84ad97a399a --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-functional.xml @@ -0,0 +1,46 @@ + + + + Organization + + + + Organizations + + fa fa-building + darkgreen + + + + + + OrgType + + + + + UserType + + org:default + org:manager + + + + + OrgType + + + org:default + + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group-list.xml b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group-list.xml new file mode 100644 index 00000000000..f6309494c69 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group-list.xml @@ -0,0 +1,37 @@ + + + + Group List + + + + Group Lists + + fa fa-sitemap + + + + + + + OrgType + + + + + OrgType + + + org:default + + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group.xml b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group.xml new file mode 100644 index 00000000000..5a69285df9b --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/archetypes/archetype-org-group.xml @@ -0,0 +1,36 @@ + + + + Group + + + + Groups + + fa fa-users + chocolate + + + + + + OrgType + + + + + UserType + org:default + + + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/org/org-example.xml b/testing/schrodingertest/src/test/resources/labs/objects/org/org-example.xml new file mode 100644 index 00000000000..d2ab634ef12 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/org/org-example.xml @@ -0,0 +1,473 @@ + + + + + EXAMPLE_GROUPS + + enabled + enabled + + Groups + Burning House Avenue 1010/A, Hot Rock City + + + + + + + + + + + ACTIVE + + + + + + + + + + + + + + + + + + enabled + enabled + + + + + Active Employees + + + INACTIVE + + + + + + + + + + + + + + + + + + enabled + enabled + + + + + Inactive Employees + + + FORMER + + + + + + + + + + + + + + + + + + enabled + enabled + + + + + Former Employees + + + ADMIN + + + + + + + + + + + + + + + + + + enabled + enabled + + + + + Administrators + + + SECURITY + + + + + + + + + + + + + + + + + + enabled + enabled + + + + + Security + + + CONTRACTORS + + + + + + + + + + + + + + + + + + enabled + + + + + Contractors + + + EXAMPLE_ORG + + enabled + enabled + + ExAmPLE, Inc. - Functional Structure + EXAMPLE_ORG + Burning House Avenue 1010/A, Hot Rock City + + + + + + + + + + + + 0100 + + + + + + + + + + + + + + + + + + + + Executive Division + 0100 + + + 0110 + + + + + + + + + + + + + + + + + + + + Sales Department + 0110 + + + 0200 + + + + + + + + + + + + + + + + + + + + Technology Division + 0200 + + + 0300 + + + + + + + + + + + + + + + + + + + + Human Resources + 0300 + + + 0210 + + + + + + + + + + + + + + + + + + + + Software Department + 0210 + + + 0211 + + + + + + + + + + + + + + + + + + + + Java Development + 0211 + + + 0212 + + + + + + + + + + + + + + + + + + + + IT Administration Department + 0212 + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/org/org-secret-ops.xml b/testing/schrodingertest/src/test/resources/labs/objects/org/org-secret-ops.xml new file mode 100644 index 00000000000..086af8bced4 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/org/org-secret-ops.xml @@ -0,0 +1,98 @@ + + + + + 0900 + Secret Operations + + + + + + + + + + + + + + + + + Secret Operations + 0900 + + + 0910 + Special transportation means + + + + + + + + + + + + + + + + + Transportation and Logistics Department + 0910 + + + 0915 + + + + + + + + + + + + + + + + + Teleportation Team + 0915 + + + 0917 + + + + + + + + + + + + + + + + + Anti-gravity Device Team + 0917 + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-csvfile-3-ldap-8-1.xml b/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-csvfile-3-ldap-8-1.xml new file mode 100644 index 00000000000..358910909c2 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-csvfile-3-ldap-8-1.xml @@ -0,0 +1,537 @@ + + + + + CSV-3 (LDAP) + + + + c:connectorType + com.evolveum.polygon.connector.csv.CsvConnector + + + + + + /opt/training/midpoint-labs/flatfiles/csv-3.csv + utf-8 + ALL + " + , + ; + dn + userPassword + + + + + account + default + Default Account + true + ri:AccountObjectClass + + ri:dn + Distinguished Name + + 0 + + + strong + + extension/ouPath + + + givenName + + + familyName + + + + + + + + ri:employeeNumber + Employee Number + Definition of Employee Number attribute handling. + + + employeeNumber + + + + + ri:givenName + First name + Definition of Firstname attribute handling. + + + givenName + + + + + ri:sn + Last name + Definition of Lastname attribute handling. + + + familyName + + + + + ri:mail + Mail + Definition of Mail attribute handling. + + + givenName + + + familyName + + + + + + + + emailAddress + + + + + ri:MemberOf + Member Of + Definition of MemberOf attribute handling. + + 0 + unbounded + + + + ri:description + Description + + + description + + + + + ri:manager + Manager + false + + strong + + + + + + + ri:telephoneNumber + Telephone Number + Phone number normalized spaces + + + telephoneNumber + + + + + + + + + + + + + http://prism.evolveum.com/xml/ns/public/matching-rule-3#stringIgnoreCase + attributes/ri:dn + cn=administrator,ou=ExAmPLE,dc=example,dc=com + + + + + + + http://prism.evolveum.com/xml/ns/public/matching-rule-3#stringIgnoreCase + attributes/ri:dn + cn=mail-daemon,ou=ExAmPLE,dc=example,dc=com + + + + + + + weak + + $focusExists + + + + + + + + + + + + + + + + + + + + account + admin + + Admin Account + false + + ri:AccountObjectClass + + ri:dn + Distinguished Name + + + 0 + + + + givenName + + + familyName + + + + + + + + + ri:employeeNumber + Employee Number + Definition of Employee Number attribute handling. + + + employeeNumber + + + + + ri:givenName + First name + Definition of Firstname attribute handling. + + + givenName + + + + + + ri:sn + Last name + Definition of Lastname attribute handling. + + + familyName + + + + + ri:MemberOf + Member Of + Definition of MemberOf attribute handling. + + 0 + unbounded + + + + + 10 + + + + + + + + + + http://prism.evolveum.com/xml/ns/public/matching-rule-3#stringIgnoreCase + attributes/ri:dn + cn=admin2,ou=_Administrators_,ou=ExAmPLE,dc=example,dc=com + + + + + + + http://prism.evolveum.com/xml/ns/public/matching-rule-3#stringIgnoreCase + attributes/ri:dn + cn=admin3,ou=_Administrators_,ou=ExAmPLE,dc=example,dc=com + + + + + + + + + + + + + + + + + + + + + + + + + ri:disabled + false + true + + + + sequentialSearch + + + + + + Default account + Normal accounts are NOT in ou=_Administrators container + account + default + true + + + + + + Correlation expression is a search query. + Following search queury will look for users that have "employeeNumber" + equal to the "employeeNumber" attribute of the account. + The condition will ensure that "employeeNumber" is not + empty, otherwise it would match any midPoint user + with empty "employeeNumber" attribute, such as "administrator". + The correlation rule always looks for users, so it will not match + any other object type. + + + c:employeeNumber + + $projection/attributes/ri:employeeNumber + + + + + + + + linked + true + + + deleted + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + + + Admin account + Admin accounts are in ou=_Administrators container + account + admin + true + + + + + + Correlation expression is a search query. + Following search queury will look for users that have "employeeNumber" + equal to the "employeeNumber" attribute of the account. + The condition will ensure that "employeeNumber" is not + empty, otherwise it would match any midPoint user + with empty "employeeNumber" attribute, such as "administrator". + The correlation rule always looks for users, so it will not match + any other object type. + + + c:employeeNumber + + $projection/attributes/ri:employeeNumber + + + + + + + + + linked + true + + + deleted + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + + + + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr-noextension.xml b/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr-noextension.xml new file mode 100644 index 00000000000..5c02fa5e9ac --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr-noextension.xml @@ -0,0 +1,351 @@ + + + + + + ExAmPLE, Inc. HR Source + + + + + c:connectorType + com.evolveum.polygon.connector.csv.CsvConnector + + + + + + + + /opt/training/midpoint-labs/flatfiles/source.csv + utf-8 + ALL + " + , + + name + true + true + + + + + + + + account + default + + Default Account + true + + ri:AccountObjectClass + + ri:name + + + true + false + false + + + + + + + + name + + + + + employeeNumber + + + + strong + + + c:ArchetypeType + + + name + + Employee + + + + + + + assignment + + + + + ri:department + Department name + + + true + false + false + + + + + organizationalUnit + + + + + ri:ouNumber + Department Number + + + true + false + false + + + + + costCenter + + + + + ri:firstName + First name + + + true + false + false + + + + + givenName + + + + + ri:lastName + Last name + + + true + false + false + + + + + familyName + + + + + ri:position + Job Title + + + true + false + false + + + + + title + + + + + ri:employedFrom + Employed From + + 1 + + true + false + false + + + + + + + + + + + + employedFrom + $projection/attributes/ri:employedFrom + + + + + + + + + + employedTo + $projection/attributes/ri:employedTo + + + + + + + + + + + weak + + + + + + + + + + + + false + + + false + + + false + + + + ri:empStatus + A + F + I + false + + + + sequentialSearch + + + + + + Default account + account + default + + true + + + Correlation expression is a search query. + Following search queury will look for users that have "name" + equal to the "name" attribute of the account. Simply speaking, + it will look for match in usernames in the IDM and the resource. + The correlation rule always looks for users, so it will not match + any other object type. + + + employeeNumber + + $projection/attributes/ri:name + + + + + + + linked + true + + + deleted + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + + diff --git a/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr.xml b/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr.xml new file mode 100644 index 00000000000..c4b3bcaa33c --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/objects/resources/localhost-hr.xml @@ -0,0 +1,411 @@ + + + + + + ExAmPLE, Inc. HR Source + + + + + c:connectorType + com.evolveum.polygon.connector.csv.CsvConnector + + + + + + + + /opt/training/midpoint-labs/flatfiles/source.csv + utf-8 + ALL + " + , + + name + true + true + + + + + + + + account + default + + Default Account + true + + ri:AccountObjectClass + + ri:name + + + true + false + false + + + + + + + + name + + + + + employeeNumber + + + + strong + + + c:ArchetypeType + + + name + + Employee + + + + + + + assignment + + + + + ri:isManager + Is Manager + + + true + false + false + + + + + + + + + extension/isManager + + + + + ri:empStatus + Employee Status + + + true + false + false + + + + + extension/empStatus + + + + + ri:department + Department name + + + true + false + false + + + + + organizationalUnit + + + + + ri:ouNumber + Department Number + + + true + false + false + + + + + costCenter + + + + + ri:ouPath + Organizational Hierarchy + + + true + false + false + + + + + extension/ouPath + + + + + ri:firstName + First name + + + true + false + false + + + + + givenName + + + + + ri:lastName + Last name + + + true + false + false + + + + + familyName + + + + + ri:position + Job Title + + + true + false + false + + + + + title + + + + + ri:employedFrom + Employed From + + 1 + + true + false + false + + + + + ri:employedTo + Employed To + + 1 + + true + false + false + + + + + + + + + + + + employedFrom + $projection/attributes/ri:employedFrom + + + + + + + + + + employedTo + $projection/attributes/ri:employedTo + + + + + + + + + + + weak + + + + + + + + + + + + false + + + false + + + false + + + + ri:empStatus + A + F + I + false + + + + sequentialSearch + + + + + + Default account + account + default + + true + + + Correlation expression is a search query. + Following search queury will look for users that have "name" + equal to the "name" attribute of the account. Simply speaking, + it will look for match in usernames in the IDM and the resource. + The correlation rule always looks for users, so it will not match + any other object type. + + + employeeNumber + + $projection/attributes/ri:name + + + + + + + linked + true + + + deleted + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink + + + + unlinked + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#link + + + + unmatched + true + + http://midpoint.evolveum.com/xml/ns/public/model/action-3#addFocus + + + + + + diff --git a/testing/schrodingertest/src/test/resources/labs/sources/csv-1-7-3.csv b/testing/schrodingertest/src/test/resources/labs/sources/csv-1-7-3.csv new file mode 100644 index 00000000000..14c78b00f94 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/sources/csv-1-7-3.csv @@ -0,0 +1,22 @@ +"login","fname","lname","enumber","dep","dis","password","groups","phone" +"hacker","","","","","true","hackedSecretPassword","","" +"admin","Admin","Admin","","","false","secret","","" +"_tjru68","Test","Test","","","true","secret","","" +"_x000090","Mel","Austenberg Test","000090","Executive Division","false","secret","","" +"jsmith","John","SMITH","001212","Human Resources","false","9hYJX","","" +"maustenberg","Mel","AUSTENBERG","000090","Executive Division","false","WM30M","","" +"efeckerwood","Ellen","FECKERWOOD","001049","Executive Division","true","Nr7Ra","","" +"adecker","Andreas","DECKER","000045","Sales Department","false","2UZcf","","" +"rmechal","Rudie","MECHAL","000021","Executive Division","false","FQMMj","","" +"irockerteller","Ivan","ROCKERTELLER","000089","Java Development","false","UrvRe","","" +"emorthanic","Eve","MORTHANIC","000005","Executive Division","false","dLbOe","","" +"jbradley","James","BRADLEY","000035","Technology Division","false","BZxqQ","","" +"kharrison","Kyle","HARRISON","000078","Software Department","false","dgL8k","","" +"adewries","Ann","DE WRIES","000389","Sales Department","false","2j2o0","","" +"ablack","Alice","BLACK","000158","IT Administration Department","false","fm4Fs","","" +"jwicks","John","WICKS","000390","IT Administration Department","false","KZvNz","","" +"adewrieux","Adele","DEWRIEUX","000328","Java Development","false","Tz00U",,"" +"jpicard","Jean-Luc","PICARD","","","false","abc123","","" +"jkirk","Jim Tiberius","KIRK","","","false","abc123","Alpha force;Bravo force;Delta force","123555-1010" +"kjaneway","Kathryn","JANEWAY","","","false","abc123","Teleportation;Time Travel","" +"_kirk","Jim Tiberius","Kirk","","","false","abc123","","123555-1010" diff --git a/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-1.csv b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-1.csv new file mode 100644 index 00000000000..0c1cc067f3f --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-1.csv @@ -0,0 +1,16 @@ +"name","firstName","lastName","position","ouNumber","department","ouPath","employedFrom","employedTo","isManager","empStatus" +"001212","John","Smith","Human Resources Specialist","0300","Human Resources","0300","2000-05-01T08:00:00",,1,"A" +"000090","Mel","Austenberg","General Manager","0100","Executive Division","0100","2008-06-01T08:00:00",,1,"A" +"001049","Ellen","Feckerwood","Assistant","0100","Executive Division","0100","2001-02-01T09:01:00",,0,"I" +"000045","Andreas","Decker","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000021","Rudie","Mechal","Assistant","0100","Executive Division","0100","2010-07-15T08:20:00",,0,"A" +"000089","Ivan","Rockerteller","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000005","Eve","Morthanic","Executive Manager","0100","Executive Division","0100","2010-07-15T08:20:00",,1,"A" +"000035","James","Bradley","Technology Division Manager","0200","Technology Division","0200","2010-07-15T08:20:00",,1,"A" +"000078","Kyle","Harrison","Analyst","0210","Software Department","0200:0210","2010-07-15T08:20:00",,0,"A" +"000002","Richard","Dwayne","General Manager",,"Executive Division","0100","2000-05-01T08:00:00","2012-09-30T17:35:00",0,"F" +"000389","Ann","De Wries","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000158","Alice","Black","IT Administrator","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,0,"A" +"000390","John","Wicks","IT Manager","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,1,"A" +"000328","Adele","Dewrieux","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000999","Arnold","Rimmer","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" diff --git a/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-2.csv b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-2.csv new file mode 100644 index 00000000000..2530a4734ea --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-2.csv @@ -0,0 +1,16 @@ +"name","firstName","lastName","position","ouNumber","department","ouPath","employedFrom","employedTo","isManager","empStatus" +"001212","John","Smith","Human Resources Specialist","0300","Human Resources","0300","2000-05-01T08:00:00",,1,"A" +"000090","Mel","Austenberg","General Manager","0100","Executive Division","0100","2008-06-01T08:00:00",,1,"A" +"001049","Ellen","Feckerwood","Assistant","0100","Executive Division","0100","2001-02-01T09:01:00",,0,"I" +"000045","Andreas","Decker","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000021","Rudie","Mechal","Assistant","0100","Executive Division","0100","2010-07-15T08:20:00",,0,"A" +"000089","Ivan","Rockerteller","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000005","Eve","Morthanic","Executive Manager","0100","Executive Division","0100","2010-07-15T08:20:00",,1,"A" +"000035","James","Bradley","Technology Division Manager","0200","Technology Division","0200","2010-07-15T08:20:00",,1,"A" +"000078","Kyle","Harrison","Analyst","0210","Software Department","0200:0210","2010-07-15T08:20:00",,0,"A" +"000002","Richard","Dwayne","General Manager",,"Executive Division","0100","2000-05-01T08:00:00","2012-09-30T17:35:00",0,"F" +"000389","Ann","De Wries","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000158","Alice","Black","IT Administrator","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,0,"A" +"000390","John","Wicks","IT Manager","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,1,"A" +"000328","Adele","Dewrieux","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000999","Arnold J.","Rimmer","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" diff --git a/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-3.csv b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-3.csv new file mode 100644 index 00000000000..5c3752a4564 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-3.csv @@ -0,0 +1,16 @@ +"name","firstName","lastName","position","ouNumber","department","ouPath","employedFrom","employedTo","isManager","empStatus" +"001212","John","Smith","Human Resources Specialist","0300","Human Resources","0300","2000-05-01T08:00:00",,1,"A" +"000090","Mel","Austenberg","General Manager","0100","Executive Division","0100","2008-06-01T08:00:00",,1,"A" +"001049","Ellen","Feckerwood","Assistant","0100","Executive Division","0100","2001-02-01T09:01:00",,0,"I" +"000045","Andreas","Decker","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000021","Rudie","Mechal","Assistant","0100","Executive Division","0100","2010-07-15T08:20:00",,0,"A" +"000089","Ivan","Rockerteller","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000005","Eve","Morthanic","Executive Manager","0100","Executive Division","0100","2010-07-15T08:20:00",,1,"A" +"000035","James","Bradley","Technology Division Manager","0200","Technology Division","0200","2010-07-15T08:20:00",,1,"A" +"000078","Kyle","Harrison","Analyst","0210","Software Department","0200:0210","2010-07-15T08:20:00",,0,"A" +"000002","Richard","Dwayne","General Manager",,"Executive Division","0100","2000-05-01T08:00:00","2012-09-30T17:35:00",0,"F" +"000389","Ann","De Wries","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000158","Alice","Black","IT Administrator","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,0,"A" +"000390","John","Wicks","IT Manager","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,1,"A" +"000328","Adele","Dewrieux","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000999","Arnold J.","Rimmer","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"F" diff --git a/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-4.csv b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-4.csv new file mode 100644 index 00000000000..2530a4734ea --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/sources/source-7-4-part-4.csv @@ -0,0 +1,16 @@ +"name","firstName","lastName","position","ouNumber","department","ouPath","employedFrom","employedTo","isManager","empStatus" +"001212","John","Smith","Human Resources Specialist","0300","Human Resources","0300","2000-05-01T08:00:00",,1,"A" +"000090","Mel","Austenberg","General Manager","0100","Executive Division","0100","2008-06-01T08:00:00",,1,"A" +"001049","Ellen","Feckerwood","Assistant","0100","Executive Division","0100","2001-02-01T09:01:00",,0,"I" +"000045","Andreas","Decker","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000021","Rudie","Mechal","Assistant","0100","Executive Division","0100","2010-07-15T08:20:00",,0,"A" +"000089","Ivan","Rockerteller","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000005","Eve","Morthanic","Executive Manager","0100","Executive Division","0100","2010-07-15T08:20:00",,1,"A" +"000035","James","Bradley","Technology Division Manager","0200","Technology Division","0200","2010-07-15T08:20:00",,1,"A" +"000078","Kyle","Harrison","Analyst","0210","Software Department","0200:0210","2010-07-15T08:20:00",,0,"A" +"000002","Richard","Dwayne","General Manager",,"Executive Division","0100","2000-05-01T08:00:00","2012-09-30T17:35:00",0,"F" +"000389","Ann","De Wries","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000158","Alice","Black","IT Administrator","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,0,"A" +"000390","John","Wicks","IT Manager","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,1,"A" +"000328","Adele","Dewrieux","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000999","Arnold J.","Rimmer","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" diff --git a/testing/schrodingertest/src/test/resources/labs/sources/source.csv b/testing/schrodingertest/src/test/resources/labs/sources/source.csv new file mode 100644 index 00000000000..ce24b1d02d7 --- /dev/null +++ b/testing/schrodingertest/src/test/resources/labs/sources/source.csv @@ -0,0 +1,15 @@ +"name","firstName","lastName","position","ouNumber","department","ouPath","employedFrom","employedTo","isManager","empStatus" +"001212","John","Smith","Human Resources Specialist","0300","Human Resources","0300","2000-05-01T08:00:00",,1,"A" +"000090","Mel","Austenberg","General Manager","0100","Executive Division","0100","2008-06-01T08:00:00",,1,"A" +"001049","Ellen","Feckerwood","Assistant","0100","Executive Division","0100","2001-02-01T09:01:00",,0,"I" +"000045","Andreas","Decker","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000021","Rudie","Mechal","Assistant","0100","Executive Division","0100","2010-07-15T08:20:00",,0,"A" +"000089","Ivan","Rockerteller","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" +"000005","Eve","Morthanic","Executive Manager","0100","Executive Division","0100","2010-07-15T08:20:00",,1,"A" +"000035","James","Bradley","Technology Division Manager","0200","Technology Division","0200","2010-07-15T08:20:00",,1,"A" +"000078","Kyle","Harrison","Analyst","0210","Software Department","0200:0210","2010-07-15T08:20:00",,0,"A" +"000002","Richard","Dwayne","General Manager",,"Executive Division","0100","2000-05-01T08:00:00","2012-09-30T17:35:00",0,"F" +"000389","Ann","De Wries","Sales Manager","0110","Sales Department","0100:0110","2010-07-15T08:20:00",,0,"A" +"000158","Alice","Black","IT Administrator","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,0,"A" +"000390","John","Wicks","IT Manager","0212","IT Administration Department","0200:0212","2010-07-15T08:20:00",,1,"A" +"000328","Adele","Dewrieux","Application Developer","0211","Java Development","0200:0210:0211","2010-07-15T08:20:00",,0,"A" diff --git a/testing/schrodingertest/testng-integration.xml b/testing/schrodingertest/testng-integration.xml index ea7744f495c..059412d8252 100644 --- a/testing/schrodingertest/testng-integration.xml +++ b/testing/schrodingertest/testng-integration.xml @@ -110,10 +110,13 @@ - - - - + + + + + + + \ No newline at end of file diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/MidPoint.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/MidPoint.java index e70ca97b413..52985eeae7d 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/MidPoint.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/MidPoint.java @@ -21,6 +21,8 @@ public class MidPoint { public static final long TIMEOUT_DEFAULT_2_S = 2000; + public static final long TIMEOUT_SHORT_4_S = 4000; + public static final long TIMEOUT_MEDIUM_6_S = 6000; public static final long TIMEOUT_LONG_1_M = 60000; diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/AssignmentsTab.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/AssignmentsTab.java index 89655be5f04..039008e1c51 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/AssignmentsTab.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/AssignmentsTab.java @@ -69,12 +69,26 @@ public > FocusSetAssignmentsModal clickAddAssigne $(Schrodinger.byElementAttributeValue("i", "class", "fe fe-assignment ")) .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - SelenideElement modalElement = $(Schrodinger.byElementAttributeValue("div", "aria-labelledby", "Select object(s)")) - .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); + SelenideElement modalElement = getNewAssignmentModal(); return new FocusSetAssignmentsModal((A) this, modalElement); } + public > FocusSetAssignmentsModal clickAddAssignemnt(String title) { + $(Schrodinger.byElementAttributeValue("div", "title", title)) + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + + SelenideElement modalElement = getNewAssignmentModal(); + + return new FocusSetAssignmentsModal((A) this, modalElement); + } + + private SelenideElement getNewAssignmentModal() { + return $(Schrodinger.byElementAttributeValue("div", "aria-labelledby", "Select object(s)")) + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); + } + + public boolean assignmentExists(String assignmentName){ SelenideElement assignmentSummaryDisplayName = table() .clickByName(assignmentName) diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java index f11ad3a6e44..d95dc349169 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java @@ -12,6 +12,8 @@ import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.page.BasicPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.By; @@ -41,4 +43,9 @@ protected String getTableAdditionalClass(){ return null; } + public int getCountOfObjects() { + String countString = $(Schrodinger.byDataId("div", "count")).getText(); + return Integer.valueOf(countString.substring(countString.lastIndexOf(" ")+1)); + } + } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListTable.java index 0cf2d70f9bb..d983f3510c1 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListTable.java @@ -11,8 +11,10 @@ import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.ProjectionsTab; import com.evolveum.midpoint.schrodinger.component.common.Search; import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.user.ProjectionsDropDown; import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import org.openqa.selenium.By; @@ -22,7 +24,7 @@ /** * Created by honchar */ -public abstract class AssignmentHolderObjectListTable

extends TableWithPageRedirect

{ +public abstract class AssignmentHolderObjectListTable extends TableWithPageRedirect

{ public AssignmentHolderObjectListTable(P parent, SelenideElement parentElement){ super(parent, parentElement); diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Paging.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Paging.java index 4054e1d7cae..27b0fe5b525 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Paging.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Paging.java @@ -28,25 +28,25 @@ public Paging(T parent, SelenideElement parentElement) { } public Paging first() { - getParentElement().$(Schrodinger.byElementValue("a", "<<")).click(); + getParentElement().$x(".//a[text()='<<']").click(); Selenide.sleep(1000); return this; } public Paging previous() { - getParentElement().$(Schrodinger.byElementValue("a", "<")).click(); + getParentElement().$x(".//a[text()='<']").click(); Selenide.sleep(1000); return this; } public Paging next() { - getParentElement().$(Schrodinger.byElementValue("a", ">")).click(); + getParentElement().$x(".//a[text()='>']").click(); Selenide.sleep(1000); return this; } public Paging last() { - getParentElement().$(Schrodinger.byElementValue("a", ">>")).click(); + getParentElement().$x(".//a[text()='>>']").click(); Selenide.sleep(1000); return this; } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/PrismForm.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/PrismForm.java index 9064243c42b..0cf6d26439c 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/PrismForm.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/PrismForm.java @@ -13,6 +13,7 @@ import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.Component; +import com.evolveum.midpoint.schrodinger.component.modal.ObjectBrowserModal; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import org.openqa.selenium.By; @@ -413,4 +414,17 @@ public PrismForm collapseAllChildrenContainers(String parentContainerHeraderK } return this; } + + public ObjectBrowserModal> editRefValue(String attributeName) { + SelenideElement property = findProperty(attributeName); + property.$x(".//button[@" + Schrodinger.DATA_S_ID + "='edit']") + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + + SelenideElement modalWindow = $(By.className("wicket-modal")) + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); + + ObjectBrowserModal objectBrowserModal = new ObjectBrowserModal<>(this, modalWindow); + + return objectBrowserModal; + } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java index 1cd75077319..ec3acdec217 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java @@ -82,8 +82,8 @@ public Search> search() { return new Search<>(this, searchElement); } - public Paging paging() { - SelenideElement pagingElement = getParentElement().$(By.className("boxed-table-footer-paging")); + public

> Paging

paging() { + SelenideElement pagingElement = getParentElement().$x(".//div[@class='boxed-table-footer-paging']"); return new Paging(this, pagingElement); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/TableWithPageRedirect.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/TableWithPageRedirect.java index 43a296b107a..367e1817749 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/TableWithPageRedirect.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/TableWithPageRedirect.java @@ -6,8 +6,19 @@ */ package com.evolveum.midpoint.schrodinger.component.common.table; +import com.codeborne.selenide.Condition; import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.ProjectionsTab; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; +import com.evolveum.midpoint.schrodinger.component.user.ProjectionsDropDown; import com.evolveum.midpoint.schrodinger.page.BasicPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; /** * Created by matus on 5/2/2018. @@ -21,4 +32,18 @@ public TableWithPageRedirect(T parent, SelenideElement parentElement) { public abstract E clickByName(String name); public abstract TableWithPageRedirect selectCheckboxByName(String name); + + public abstract

> TableHeaderDropDownMenu

clickHeaderActionDropDown(); + + protected SelenideElement clickAndGetHeaderDropDownMenu() { + + $(By.tagName("thead")) + .$(Schrodinger.byDataId("inlineMenuPanel")) + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S) + .click(); + + SelenideElement dropDownMenu = $(Schrodinger.byElementAttributeValue("ul", "class", "dropdown-menu pull-right")); + + return dropDownMenu; + } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java index 5712a4fc96c..7863706117c 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java @@ -54,9 +54,13 @@ public FocusSetAssignmentsModal selectIntent(String option) { } public FocusTableWithChoosableElements> table() { - SelenideElement resourcesBox = $(By.cssSelector(".box.boxed-table")); + SelenideElement resourcesBox = getParentElement().$x(".//div[@class='box boxed-table']"); - return new FocusTableWithChoosableElements<>(this, resourcesBox); + return new FocusTableWithChoosableElements<>(this, resourcesBox){ + + + + }; } public T clickAdd() { diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/ObjectBrowserModalTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/ObjectBrowserModalTable.java index d5f1a52b8c0..0d19888bb26 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/ObjectBrowserModalTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/ObjectBrowserModalTable.java @@ -9,6 +9,7 @@ import com.codeborne.selenide.Condition; import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.common.Search; import com.evolveum.midpoint.schrodinger.component.common.table.Table; import com.evolveum.midpoint.schrodinger.util.Schrodinger; @@ -31,4 +32,9 @@ public T clickByName(String name){ return getParent().getParent(); } + + @Override + public Search> search() { + return (Search>) super.search(); + } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java new file mode 100644 index 00000000000..de0b79c6470 --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java @@ -0,0 +1,77 @@ +/* + * 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.schrodinger.component.org; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.Component; +import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListTable; +import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; +import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; +import com.evolveum.midpoint.schrodinger.page.user.UserPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +import org.openqa.selenium.By; + +import static com.codeborne.selenide.Selenide.$; + +/** + * @author skublik + */ + +public class MemberPanel extends Component { + + public MemberPanel(T parent, SelenideElement parentElement) { + super(parent, parentElement); + } + + public UserPage newMember() { + SelenideElement mainButton = $(By.xpath("//button[@type='button'][@title='Create member ']")); + String expanded = mainButton.getAttribute("aria-haspopup"); + if (Boolean.getBoolean(expanded)) { + newMember("Create member "); + } else { + mainButton.click(); + } + return null; //TODO implement return popup + } + + public AssignmentHolderDetailsPage newMember(String title) { + SelenideElement mainButton = $(By.xpath("//button[@type='button'][@title='Create member ']")); + if (!Boolean.getBoolean(mainButton.getAttribute("aria-expanded"))) { + mainButton.click(); + mainButton.waitWhile(Condition.attribute("aria-expanded", "false"), MidPoint.TIMEOUT_MEDIUM_6_S); + } + $(Schrodinger.byElementAttributeValue("div", "title", title)) + .waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + return new AssignmentHolderDetailsPage(){}; + } + + public MemberPanel selectType(String type) { + getParentElement().$x(".//select[@name='type:propertyLabel:row:selectWrapper:select']") + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).selectOption(type); + return this; + } + + public AssignmentHolderObjectListTable, AssignmentHolderDetailsPage> table() { + SelenideElement table = getParentElement().$x(".//div[@" + Schrodinger.DATA_S_ID + "='table']"); + return new AssignmentHolderObjectListTable, AssignmentHolderDetailsPage>(this, table) { + @Override + public AssignmentHolderDetailsPage getObjectDetailsPage() { + return new AssignmentHolderDetailsPage() {}; + } + + @Override + public

>> TableHeaderDropDownMenu

clickHeaderActionDropDown() { + return null; + } + }; + } +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java new file mode 100644 index 00000000000..c7798a701e0 --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java @@ -0,0 +1,101 @@ +/* + * 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.schrodinger.component.org; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.Component; +import com.evolveum.midpoint.schrodinger.page.org.OrgPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +import org.openqa.selenium.By; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author skublik + */ + +public class OrgHierarchyPanel extends Component { + public OrgHierarchyPanel(T parent, SelenideElement parentElement) { + super(parent, parentElement); + } + + public OrgHierarchyPanel selectOrgInTree(String orgName) { + boolean exist = getParentElement().$(Schrodinger.byElementValue("span", "class", "tree-label", orgName)).exists(); + if (!exist) { + expandAllIfNeeded(); + } + getParentElement().$(Schrodinger.byElementValue("span", "class", "tree-label", orgName)) + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + return this; + } + + private void expandAllIfNeeded() { + boolean existExpandButton = getParentElement().$(By.cssSelector(".tree-junction-collapsed")).exists(); + if (existExpandButton) { + expandAllOrgs(); + } + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + } + + public OrgHierarchyPanel expandAllOrgs() { + clickOnTreeMenu(); + getParentElement().$(Schrodinger.byDataResourceKey("schrodinger", "TreeTablePanel.expandAll")).parent() + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + return this; + } + + private void clickOnTreeMenu() { + getParentElement().$(Schrodinger.byDataId("div", "treeMenu")).click(); + } + + public boolean containsChildOrg(String parentOrg, String... expectedChild){ + expandAllIfNeeded(); + SelenideElement parentNode = getParentOrgNode(parentOrg); + SelenideElement subtree = parentNode.$x(".//div[@"+Schrodinger.DATA_S_ID+"='subtree']"); + ElementsCollection childsLabels = subtree.$$x(".//span[@"+Schrodinger.DATA_S_ID+"='label']"); + List childs = new ArrayList(); + for (SelenideElement childLabel : childsLabels) { + childs.add(childLabel.getText()); + } + return childs.containsAll(Arrays.asList(expectedChild)); + } + + private SelenideElement getParentOrgNode (String parentOrg) { + selectOrgInTree(parentOrg); + return getParentElement().$(By.cssSelector(".tree-node.success")).parent(); + } + + public OrgHierarchyPanel expandOrg(String orgName) { + SelenideElement parentNode = getParentOrgNode(orgName); + SelenideElement node = parentNode.$x(".//div[@"+Schrodinger.DATA_S_ID+"='node']"); + SelenideElement expandButton = node.$x(".//a[@" + Schrodinger.DATA_S_ID + "='junction']"); + if (expandButton.has(Condition.cssClass("tree-junction-collapsed"))) { + expandButton.waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + expandButton.waitWhile(Condition.cssClass("tree-junction-collapsed"), MidPoint.TIMEOUT_DEFAULT_2_S); + } + return this; + } + + public OrgTreeNodeDropDown editOrg(String orgName) { + SelenideElement parentNode = getParentOrgNode(orgName); + SelenideElement node = parentNode.$x(".//div[@"+Schrodinger.DATA_S_ID+"='node']"); + SelenideElement menuButton = node.$x(".//span[@" + Schrodinger.DATA_S_ID + "='menu']"); + menuButton.waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + SelenideElement menu = menuButton.$x(".//ul[@" + Schrodinger.DATA_S_ID + "='dropDownMenu']").waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); + + return new OrgTreeNodeDropDown(this, menu); + } +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgRootTab.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgRootTab.java new file mode 100644 index 00000000000..aba7abff506 --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgRootTab.java @@ -0,0 +1,39 @@ +/* + * 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.schrodinger.component.org; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.Component; +import com.evolveum.midpoint.schrodinger.page.org.OrgTreePage; +import com.evolveum.midpoint.schrodinger.page.user.UserPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +import org.openqa.selenium.By; + +/** + * @author skublik + */ + +public class OrgRootTab extends Component { + + public OrgRootTab(OrgTreePage parent, SelenideElement parentElement) { + super(parent, parentElement); + } + + public OrgHierarchyPanel getOrgHierarchyPanel() { + SelenideElement treePanel = getParentElement().$(Schrodinger.byDataId("div", "treePanel")); + return new OrgHierarchyPanel<>(this, treePanel); + } + + public MemberPanel getMemberPanel() { + SelenideElement memberPanel = getParentElement().$(Schrodinger.byDataId("div", "memberPanel")); + return new MemberPanel<>(this, memberPanel); + } +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java new file mode 100644 index 00000000000..1c4879b873d --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java @@ -0,0 +1,32 @@ +/* + * 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.schrodinger.component.org; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.common.DropDown; +import com.evolveum.midpoint.schrodinger.page.org.OrgPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +/** + * @author skublik + */ + +public class OrgTreeNodeDropDown extends DropDown { + + public OrgTreeNodeDropDown(T parent, SelenideElement parentElement) { + super(parent, parentElement); + } + + public OrgPage edit(){ + getParentElement().$x(".//schrodinger[@"+ Schrodinger.DATA_S_RESOURCE_KEY +"='TreeTablePanel.edit']").parent() + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + return new OrgPage(); + } +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java index 4df674fc2a3..aefa2b20f89 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java @@ -11,6 +11,8 @@ import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.Component; +import com.evolveum.midpoint.schrodinger.component.ProjectionsTab; +import com.evolveum.midpoint.schrodinger.component.user.ProjectionsDropDown; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import org.openqa.selenium.By; import org.openqa.selenium.Keys; @@ -27,20 +29,20 @@ public ResourceAccountsTab(T parent, SelenideElement parentElement) { } public ResourceTaskQuickAccessDropDown> importTask() { - $(Schrodinger.byElementAttributeValue("label", "data-s-id", "label", "Import")) - .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + SelenideElement importDiv = $(Schrodinger.byDataId("div", "import")); + importDiv.waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - SelenideElement dropDownElement = $(Schrodinger.byElementAttributeValue("ul", "role", "menu")) + SelenideElement dropDownElement = importDiv.lastChild().lastChild() .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); return new ResourceTaskQuickAccessDropDown<>(this, dropDownElement); } public ResourceTaskQuickAccessDropDown> reconciliationTask() { - $(Schrodinger.byElementAttributeValue("label", "data-s-id", "label", "Reconciliation")) - .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + SelenideElement reconcileDiv = $(Schrodinger.byDataId("div", "reconciliation")); + reconcileDiv.waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - SelenideElement dropDownElement = $(Schrodinger.byElementAttributeValue("ul", "role", "menu")) + SelenideElement dropDownElement = reconcileDiv.lastChild().lastChild() .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); return new ResourceTaskQuickAccessDropDown<>(this, dropDownElement); @@ -95,4 +97,16 @@ public void setIntent(String intent) { .setValue(intent).sendKeys(Keys.ENTER); } + public ProjectionsDropDown> clickHeaderActionDropDown() { + + $(By.tagName("thead")) + .$(Schrodinger.byDataId("inlineMenuPanel")) + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S) + .click(); + + SelenideElement dropDownMenu = $(Schrodinger.byElementAttributeValue("ul", "class", "dropdown-menu pull-right")); + + return new ProjectionsDropDown>(this, dropDownMenu); + } + } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java index a13a99bd2da..b3f11c9a516 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java @@ -9,15 +9,13 @@ import com.codeborne.selenide.Condition; import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; -import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListTable; import com.evolveum.midpoint.schrodinger.component.common.Search; import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; -import com.evolveum.midpoint.schrodinger.page.BasicPage; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.page.resource.AccountPage; +import com.evolveum.midpoint.schrodinger.page.user.UserPage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; -import org.openqa.selenium.By; - import static com.codeborne.selenide.Selenide.$; /** @@ -39,20 +37,30 @@ public AccountPage clickByName(String name) { @Override public ResourceShadowTable selectCheckboxByName(String name) { - $(Schrodinger.byAncestorFollowingSiblingDescendantOrSelfElementEnclosedValue("input", "type", "checkbox", "data-s-id", "3", name)) - .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + SelenideElement check = $(Schrodinger.byAncestorFollowingSiblingDescendantOrSelfElementEnclosedValue("input", "type", "checkbox", "data-s-id", "3", name)); + check.waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + check.waitUntil(Condition.selected, MidPoint.TIMEOUT_MEDIUM_6_S); + return this; } - public ResourceShadowTableCog> clickCog() { + public UserPage clickOnOwnerByName(String name) { + getParentElement().$(Schrodinger.byElementValue("span", "data-s-id", "label", name)) + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + + return new UserPage(); + } + + @Override + public ResourceShadowTableHeaderDropDown> clickHeaderActionDropDown() { $(Schrodinger.byElementAttributeValue("button", "data-toggle", "dropdown")) .waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); SelenideElement cog = $(Schrodinger.byElementAttributeValue("ul","role","menu")) .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); - return new ResourceShadowTableCog<>(this, cog); + return new ResourceShadowTableHeaderDropDown<>(this, cog); } @Override diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTableCog.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTableHeaderDropDown.java similarity index 86% rename from tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTableCog.java rename to tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTableHeaderDropDown.java index af7eead03cf..1bfcc31a9dc 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTableCog.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTableHeaderDropDown.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.modal.ConfirmationModal; import com.evolveum.midpoint.schrodinger.component.common.DropDown; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import static com.codeborne.selenide.Selenide.$; @@ -18,21 +19,21 @@ /** * Created by matus on 5/25/2018. */ -public class ResourceShadowTableCog extends DropDown { - public ResourceShadowTableCog(T parent, SelenideElement parentElement) { +public class ResourceShadowTableHeaderDropDown extends TableHeaderDropDownMenu { + public ResourceShadowTableHeaderDropDown(T parent, SelenideElement parentElement) { super(parent, parentElement); } public T clickEnable() { - $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.enableAccounts")) + $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.enableAccount")) .parent().waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); return this.getParent(); } public T clickDisable() { - $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.disableAccounts")) + $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.disableAccount")) .parent().waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); return this.getParent(); @@ -49,14 +50,14 @@ public ConfirmationModal clickDelete() { } public T clickImport() { - $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.importAccounts")) + $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.importAccount")) .parent().waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); return this.getParent(); } public T clickRemoveOwner() { - $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.removeOwners")) + $(Schrodinger.byDataResourceKey("pageContentAccounts.menu.removeOwner")) .parent().waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); return this.getParent(); diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourcesPageTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourcesPageTable.java index b1e79d6229a..319917705fa 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourcesPageTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourcesPageTable.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.common.Search; import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.page.resource.ViewResourcePage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import org.openqa.selenium.By; @@ -28,6 +29,11 @@ public TableWithPageRedirect selectCheckboxByName(String name) { return this; } + @Override + public TableHeaderDropDownMenu clickHeaderActionDropDown() { + return null; + } + @Override public ViewResourcePage clickByName(String name) { getParentElement().$(Schrodinger.byElementValue("span", "data-s-id", "label", name)) diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/DirectIndirectAssignmentTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/DirectIndirectAssignmentTable.java index f7ac85df959..83845149f92 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/DirectIndirectAssignmentTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/DirectIndirectAssignmentTable.java @@ -9,6 +9,7 @@ import com.codeborne.selenide.ElementsCollection; import com.codeborne.selenide.SelenideElement; +import com.evolveum.midpoint.schrodinger.component.Component; import com.evolveum.midpoint.schrodinger.component.common.DropDown; import com.evolveum.midpoint.schrodinger.util.Schrodinger; @@ -20,7 +21,7 @@ * @author skublik */ -public class DirectIndirectAssignmentTable extends DropDown { +public class DirectIndirectAssignmentTable extends Component { public DirectIndirectAssignmentTable(T parent, SelenideElement parentElement) { super(parent, parentElement); diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/TableHeaderDropDownMenu.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/TableHeaderDropDownMenu.java new file mode 100644 index 00000000000..19f0fdf3887 --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/table/TableHeaderDropDownMenu.java @@ -0,0 +1,23 @@ +/* + * 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.schrodinger.component.table; + +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.component.common.DropDown; + +/** + * @author skublik + */ + +public class TableHeaderDropDownMenu extends DropDown { + + public TableHeaderDropDownMenu(T parent, SelenideElement parentElement) { + super(parent, parentElement); + } + +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/OperationStatisticsTab.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/OperationStatisticsTab.java new file mode 100644 index 00000000000..6e5b69892f2 --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/OperationStatisticsTab.java @@ -0,0 +1,27 @@ +/* + * 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.schrodinger.component.task; + +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.component.Component; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +/** + * @author skublik + */ + +public class OperationStatisticsTab extends Component { + + public OperationStatisticsTab(T parent, SelenideElement parentElement) { + super(parent, parentElement); + } + + public int getSuccessfullyProcessed() { + return Integer.valueOf(getParentElement().$(Schrodinger.byDataId("span", "objectsProcessedSuccess")).getText()); + } +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/TasksPageTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/TasksPageTable.java index 9d82e8aeb26..99da520a71e 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/TasksPageTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/task/TasksPageTable.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListTable; import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.page.task.ListTasksPage; import com.evolveum.midpoint.schrodinger.page.task.TaskPage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; @@ -26,9 +27,9 @@ public TasksPageTable(ListTasksPage parent, SelenideElement parentElement) { @Override public TaskPage clickByName(String name) { - getParentElement().$(Schrodinger.byElementValue("span", "data-s-id", "label", name)) - .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - + SelenideElement label = getParentElement().$(Schrodinger.byElementValue("span", "data-s-id", "label", name)); + label.waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + label.waitWhile(Condition.exist, MidPoint.TIMEOUT_MEDIUM_6_S); return new TaskPage(); } @@ -40,6 +41,11 @@ public TableWithPageRedirect selectCheckboxByName(String name) { return null; } + @Override + public TableHeaderDropDownMenu clickHeaderActionDropDown() { + return null; + } + @Override public TaskPage getObjectDetailsPage(){ return new TaskPage(); diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/ProjectionsDropDown.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/ProjectionsDropDown.java index 9ec371c0f59..3d348c088fa 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/ProjectionsDropDown.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/ProjectionsDropDown.java @@ -12,6 +12,7 @@ import com.evolveum.midpoint.schrodinger.component.ProjectionsTab; import com.evolveum.midpoint.schrodinger.component.common.DropDown; import com.evolveum.midpoint.schrodinger.component.modal.ConfirmationModal; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; @@ -20,7 +21,7 @@ /** * Created by matus on 5/2/2018. */ -public class ProjectionsDropDown extends DropDown { +public class ProjectionsDropDown extends TableHeaderDropDownMenu { public ProjectionsDropDown(T parent, SelenideElement parentElement) { super(parent, parentElement); diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersPageTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersPageTable.java index a13aebaa3eb..ec0fe6e04f3 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersPageTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersPageTable.java @@ -16,6 +16,7 @@ import com.evolveum.midpoint.schrodinger.component.modal.ConfirmationModal; import com.evolveum.midpoint.schrodinger.component.common.Search; import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.page.user.ListUsersPage; import com.evolveum.midpoint.schrodinger.page.user.UserPage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; @@ -32,7 +33,8 @@ public UsersPageTable(ListUsersPage parent, SelenideElement parentElement) { super(parent, parentElement); } - public UsersTableDropDown clickActionDropDown() { + @Override + public UsersTableDropDown clickHeaderActionDropDown() { $(Schrodinger.bySelfOrAncestorElementAttributeValue("button", "data-toggle", "dropdown", "class", "sortableLabel")) .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); @@ -40,7 +42,7 @@ public UsersTableDropDown clickActionDropDown() { SelenideElement dropDown = $(Schrodinger.byDataId("ul", "dropDownMenu")) .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); - return new UsersTableDropDown<>(this, dropDown); + return new UsersTableDropDown(this, dropDown); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersTableDropDown.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersTableDropDown.java index 238569798f6..8c7b23fda79 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersTableDropDown.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/user/UsersTableDropDown.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.modal.ConfirmationModal; import com.evolveum.midpoint.schrodinger.component.common.DropDown; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import static com.codeborne.selenide.Selenide.$; @@ -18,7 +19,7 @@ /** * Created by matus on 5/10/2018. */ -public class UsersTableDropDown extends DropDown { +public class UsersTableDropDown extends TableHeaderDropDownMenu { public UsersTableDropDown(T parent, SelenideElement parentElement) { super(parent, parentElement); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/FocusPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/FocusPage.java index 250a64dc670..2f58b2d36bd 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/FocusPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/FocusPage.java @@ -95,19 +95,4 @@ public boolean isActivationState(String state) { return "".equals(summaryPanel.getText()); } } - - @Override - public AssignmentHolderBasicTab selectTabBasic(){ - SelenideElement element = getTabPanel().clickTab("pageAdminFocus.basic") - .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); - - return new AssignmentHolderBasicTab((F) this, element); - } - - @Override - public AssignmentsTab selectTabAssignments(){ - SelenideElement element = getTabPanel().clickTab("pageAdminFocus.assignments"); - - return new AssignmentsTab((F) this, element); - } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/archetype/ListArchetypesPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/archetype/ListArchetypesPage.java index 6bf26deb09b..d0979a309ae 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/archetype/ListArchetypesPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/archetype/ListArchetypesPage.java @@ -8,6 +8,8 @@ import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListPage; import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListTable; +import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.component.user.UsersPageTable; import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; import com.evolveum.midpoint.schrodinger.page.BasicPage; @@ -20,6 +22,11 @@ public class ListArchetypesPage extends AssignmentHolderObjectListPage table() { return new AssignmentHolderObjectListTable(this, getTableBoxElement()) { + @Override + public

> TableHeaderDropDownMenu

clickHeaderActionDropDown() { + return null; + } + @Override public ArchetypePage getObjectDetailsPage() { return new ArchetypePage(); diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/CasesListTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/CasesListTable.java index ff8d1b5f404..c31022e737f 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/CasesListTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/CasesListTable.java @@ -10,6 +10,8 @@ import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListTable; +import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import static com.codeborne.selenide.Selenide.$; @@ -23,6 +25,11 @@ public CasesListTable(CasesPage parent, SelenideElement parentElement){ super(parent, parentElement); } + @Override + public TableHeaderDropDownMenu clickHeaderActionDropDown() { + return null; + } + @Override public CasePage getObjectDetailsPage(){ $(Schrodinger.byDataId("mainPanel")) diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/ChildrenCaseTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/ChildrenCaseTable.java index 30042e1ae08..34013a75ed7 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/ChildrenCaseTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/cases/ChildrenCaseTable.java @@ -10,6 +10,7 @@ import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; import com.evolveum.midpoint.schrodinger.page.BasicPage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; import org.openqa.selenium.By; @@ -36,6 +37,11 @@ public ChildrenCaseTable selectCheckboxByName(String name) { return this; } + @Override + public TableHeaderDropDownMenu clickHeaderActionDropDown() { + return null; + } + public CasePage clickByPartialName(String name) { getParentElement() .$(Schrodinger.byDataId("tableContainer")) diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/org/OrgTreePage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/org/OrgTreePage.java index dbd5be54502..ce992e796b3 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/org/OrgTreePage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/org/OrgTreePage.java @@ -6,12 +6,33 @@ */ package com.evolveum.midpoint.schrodinger.page.org; +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.common.TabPanel; +import com.evolveum.midpoint.schrodinger.component.org.OrgRootTab; +import com.evolveum.midpoint.schrodinger.component.user.UserTasksTab; import com.evolveum.midpoint.schrodinger.page.BasicPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +import static com.codeborne.selenide.Selenide.$; /** * Created by Viliam Repan (lazyman). */ public class OrgTreePage extends BasicPage { + public OrgRootTab selectTabWithRootOrg(String rootOrgName) { + SelenideElement element = getTabPanel().clickTabWithName(rootOrgName); + + return new OrgRootTab(this, element); + } + + private TabPanel getTabPanel() { + SelenideElement tabPanelElement = $(Schrodinger.byDataId("div", "tabs")) + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); + return new TabPanel<>(this, tabPanelElement); + } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/ListTasksPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/ListTasksPage.java index 464bb1e09bc..3b023683912 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/ListTasksPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/ListTasksPage.java @@ -20,9 +20,7 @@ public class ListTasksPage extends AssignmentHolderObjectListPage { public TasksPageTable table() { - SelenideElement box = $(Schrodinger.byDataId("div", "taskTable")); - - return new TasksPageTable(this, box); + return new TasksPageTable(this, getTableBoxElement()); } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/TaskPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/TaskPage.java index 3f2aff91558..3eb68cbc82f 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/TaskPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/task/TaskPage.java @@ -9,10 +9,13 @@ import static com.codeborne.selenide.Selenide.$; import com.codeborne.selenide.Condition; +import com.codeborne.selenide.ElementsCollection; +import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.component.AssignmentHolderBasicTab; import com.evolveum.midpoint.schrodinger.component.AssignmentsTab; +import com.evolveum.midpoint.schrodinger.component.task.OperationStatisticsTab; import com.evolveum.midpoint.schrodinger.component.task.TaskBasicTab; import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; @@ -63,7 +66,7 @@ public TaskPage resumeStopRefreshing() { } public TaskPage clickRunNow() { - $(Schrodinger.byDataResourceKey("a", "pageTaskEdit.button.runNow")).waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + $(Schrodinger.byDataResourceKey("span", "pageTaskEdit.button.runNow")).waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); return this; } @@ -96,4 +99,19 @@ public AssignmentHolderBasicTab selectScheduleTab(){ return new AssignmentHolderBasicTab(this, element); } + + public OperationStatisticsTab selectTabOperationStatistics() { + SelenideElement element = getTabPanel().clickTab("pageTask.operationStats.title") + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); + + return new OperationStatisticsTab(this, element); + } + + public TaskPage setHandlerUriForNewTask(String handler) { + SelenideElement handlerElement = $(Schrodinger.byDataResourceKey("a", "TaskHandlerSelectorPanel.selector.header")); + selectTabBasic().form().addAttributeValue("handlerUri", handler.substring(0, (handler.length() - 1))); + $(Schrodinger.byElementAttributeValue("li", "textvalue", handler)).waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + handlerElement.waitWhile(Condition.exist, MidPoint.TIMEOUT_MEDIUM_6_S); + return this; + } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java index 00ef7fbc151..80149e24516 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java @@ -39,7 +39,7 @@ public UserPage newUser() { SelenideElement mainButton = $(By.xpath("//button[@type='button'][@" + Schrodinger.DATA_S_ID + "='mainButton']")); String expanded = mainButton.getAttribute("aria-haspopup"); if (Boolean.getBoolean(expanded)) { - return newUser("user"); + return newUser("New user"); } mainButton.click(); return new UserPage(); @@ -50,7 +50,7 @@ public UserPage newUser(String title) { if (!Boolean.getBoolean(mainButton.getAttribute("aria-expanded"))) { mainButton.click(); } - $(Schrodinger.byElementAttributeValue("div", "title", "New " + title.toLowerCase())) + $(Schrodinger.byElementAttributeValue("div", "title", title)) .waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); return new UserPage(); } From 017c743fd0c45c5082bdb21c55c8c78b623b5a0b Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Fri, 17 Apr 2020 18:26:55 +0200 Subject: [PATCH 10/27] change archetype improvements: - changed style and position of the message warning user that the changes are applied immediately - when change assignment requested, modify only archetype assignments, don't apply other changes made in form - a little bit of code cleanup. --- .../gui/api/component/AssignmentPopup.html | 3 +- .../gui/api/component/AssignmentPopup.java | 35 ++--- .../api/component/result/MessagePanel.html | 25 +++ .../api/component/result/MessagePanel.java | 111 ++++++++++++++ .../result/OperationResultPanel.java | 143 ++++++------------ .../result/OperationResultPopupPanel.java | 2 +- .../component/message/FeedbackListView.java | 2 +- .../page/admin/PageAdminObjectDetails.java | 86 +++++++---- .../TestConnectionMessagesPanel.java | 2 +- .../page/admin/server/TaskResultTabPanel.java | 3 +- 10 files changed, 253 insertions(+), 159 deletions(-) create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html index 5b81f636cc7..fe98c96c213 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html @@ -6,10 +6,9 @@ --> +

- -

diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java index a4d92cbfa38..8670f75c317 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java @@ -7,26 +7,23 @@ package com.evolveum.midpoint.gui.api.component; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.xml.namespace.QName; -import com.evolveum.midpoint.gui.api.component.tabs.PanelTab; -import com.evolveum.midpoint.web.component.util.SelectableBean; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.extensions.markup.html.tabs.ITab; import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.model.IModel; import org.apache.wicket.model.StringResourceModel; +import com.evolveum.midpoint.gui.api.component.result.MessagePanel; import com.evolveum.midpoint.gui.api.component.tabs.CountablePanelTab; +import com.evolveum.midpoint.gui.api.component.tabs.PanelTab; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; @@ -36,14 +33,9 @@ import com.evolveum.midpoint.web.component.TabbedPanel; import com.evolveum.midpoint.web.component.dialog.Popupable; import com.evolveum.midpoint.web.component.util.EnableBehaviour; -import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; +import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ServiceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; /** * Created by honchar. @@ -77,10 +69,10 @@ protected void onInitialize(){ tabPanel.setOutputMarkupPlaceholderTag(true); form.add(tabPanel); - Label warningMessage = new Label(ID_WARNING_MESSAGE, this :: getWarningMessageModel); + MessagePanel warningMessage = new MessagePanel(ID_WARNING_MESSAGE, MessagePanel.MessagePanelType.WARN, getWarningMessageModel()); warningMessage.setOutputMarkupId(true); warningMessage.add(new VisibleBehaviour(() -> getWarningMessageModel() != null)); - form.add(warningMessage); + add(warningMessage); AjaxButton cancelButton = new AjaxButton(ID_CANCEL_BUTTON, createStringResource("userBrowserDialog.button.cancelButton")) { @@ -109,18 +101,18 @@ public void onClick(AjaxRequestTarget target) { if (assignmentPanel == null){ return; } - - (((AbstractAssignmentPopupTabPanel) assignmentPanel).getSelectedAssignmentsMap()).forEach((k, v) -> - selectedAssignmentsMap.putIfAbsent((String)k, (AssignmentType) v)); - - + if (AbstractAssignmentPopupTabPanel.class.isAssignableFrom(assignmentPanel.getClass())) { + Map map = (((AbstractAssignmentPopupTabPanel) assignmentPanel).getSelectedAssignmentsMap()); + map.forEach(selectedAssignmentsMap::putIfAbsent); + } }); List assignments = new ArrayList<>(selectedAssignmentsMap.values()); + getPageBase().hideMainPopup(target); addPerformed(target, assignments); } }; addButton.add(AttributeAppender.append("title", getAddButtonTitleModel())); - addButton.add(new EnableBehaviour(() -> isAssignButtonEnabled())); + addButton.add(new EnableBehaviour(this::isAssignButtonEnabled)); addButton.setOutputMarkupId(true); form.add(addButton); } @@ -401,7 +393,7 @@ protected boolean isEntitlementAssignment(){ } private int getTabPanelSelectedCount(WebMarkupContainer panel){ - if (panel != null && panel instanceof AbstractAssignmentPopupTabPanel){ + if (panel instanceof AbstractAssignmentPopupTabPanel){ return ((AbstractAssignmentPopupTabPanel) panel).getSelectedObjectsList().size(); } return 0; @@ -428,7 +420,6 @@ private TabbedPanel getTabbedPanel(){ } protected void addPerformed(AjaxRequestTarget target, List newAssignmentsList) { - getPageBase().hideMainPopup(target); } private IModel getAddButtonTitleModel(){ diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html new file mode 100644 index 00000000000..7aa9763b109 --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html @@ -0,0 +1,25 @@ + + + + +

+ + + diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java new file mode 100644 index 00000000000..91a20a4459b --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020 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.gui.api.component.result; + +import java.io.Serializable; + +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.PropertyModel; + +import com.evolveum.midpoint.gui.api.component.BasePanel; + +public class MessagePanel extends BasePanel { + + private static final String ID_MESSAGE = "message"; + + public enum MessagePanelType {INFO, WARN, SUCCESS, ERROR} + + private MessagePanelType type; + + public MessagePanel(String id, MessagePanelType type, IModel model) { + super(id, model); + this.type = type; + + } + + @Override + protected void onInitialize() { + super.onInitialize(); + initLayout(); + } + + public void initLayout() { + + WebMarkupContainer detailsBox = new WebMarkupContainer("detailsBox"); + detailsBox.setOutputMarkupId(true); + detailsBox.add(AttributeModifier.append("class", createHeaderCss())); + add(detailsBox); + + initHeader(detailsBox); + + } + + private IModel createHeaderCss() { + + return (IModel) () -> { + switch (type) { + case INFO: + return " box-info"; + case SUCCESS: + return " box-success"; + case ERROR: + return " box-danger"; + case WARN: // TODO: + default: + return " box-warning"; + } + }; + } + + private void initHeader(WebMarkupContainer box) { + WebMarkupContainer iconType = new WebMarkupContainer("iconType"); + iconType.setOutputMarkupId(true); + iconType.add(new AttributeAppender("class", (IModel) () -> { + + switch (type) { + case INFO: + return " fa-info"; + case SUCCESS: + return " fa-check"; + case ERROR: + return " fa-ban"; + case WARN: + default: + return " fa-warning"; + } + })); + + box.add(iconType); + + Label message = new Label(ID_MESSAGE, getModel()); + box.add(message); + + AjaxLink close = new AjaxLink("close") { + + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + close(target); + + } + }; + + box.add(close); + } + + public void close(AjaxRequestTarget target){ + this.setVisible(false); + target.add(this); + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java index 4d8f5595469..6f65b27b04b 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java @@ -15,18 +15,13 @@ import java.util.List; import java.util.Locale; -import com.evolveum.midpoint.common.LocalizationService; -import com.evolveum.midpoint.gui.api.page.PageBase; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; -import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.feedback.FeedbackMessage; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.DownloadLink; @@ -37,8 +32,10 @@ import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.StringResourceModel; +import com.evolveum.midpoint.common.LocalizationService; import com.evolveum.midpoint.gui.api.component.BasePanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; +import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; @@ -70,13 +67,17 @@ public class OperationResultPanel extends BasePanel implements Popupab private static final Trace LOGGER = TraceManager.getTrace(OperationResultPanel.class); - public OperationResultPanel(String id, IModel model, Page parentPage) { + public OperationResultPanel(String id, IModel model) { super(id, model); + } - initLayout(parentPage); + @Override + protected void onInitialize() { + super.onInitialize(); + initLayout(); } - public void initLayout(Page parentPage) { + public void initLayout() { WebMarkupContainer detailsBox = new WebMarkupContainer(ID_DETAILS_BOX); detailsBox.setOutputMarkupId(true); @@ -84,7 +85,7 @@ public void initLayout(Page parentPage) { add(detailsBox); initHeader(detailsBox); - initDetails(detailsBox, parentPage); + initDetails(detailsBox); } private void initHeader(WebMarkupContainer box) { @@ -248,17 +249,11 @@ public void onClick(AjaxRequestTarget target) { public File getObject() { String home = getPageBase().getMidpointConfiguration().getMidpointHome(); File f = new File(home, "result"); - DataOutputStream dos = null; - try { - dos = new DataOutputStream(new FileOutputStream(f)); - + try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(f))){ dos.writeBytes(OperationResultPanel.this.getModel().getObject().getXml()); } catch (IOException e) { LOGGER.error("Could not download result: {}", e.getMessage(), e); - } finally { - IOUtils.closeQuietly(dos); } - return f; } @@ -273,43 +268,39 @@ public void close(AjaxRequestTarget target) { } private Label createMessage() { - Label message = new Label(ID_MESSAGE_LABEL, new IModel() { - - @Override - public String getObject() { - OpResult result = OperationResultPanel.this.getModel().getObject(); + Label message = new Label(ID_MESSAGE_LABEL, (IModel) () -> { + OpResult result = OperationResultPanel.this.getModel().getObject(); - PageBase page = getPageBase(); + PageBase page = getPageBase(); - String msg = null; - if (result.getUserFriendlyMessage() != null) { + String msg = null; + if (result.getUserFriendlyMessage() != null) { - //TODO: unify with WebModelServiceUtil.translateMessage() - LocalizationService service = page.getLocalizationService(); - Locale locale = page.getSession().getLocale(); + //TODO: unify with WebModelServiceUtil.translateMessage() + LocalizationService service = page.getLocalizationService(); + Locale locale = page.getSession().getLocale(); - msg = service.translate(result.getUserFriendlyMessage(), locale); - } - - if (StringUtils.isNotBlank(msg)) { - return msg; - } + msg = service.translate(result.getUserFriendlyMessage(), locale); + } - msg = result.getMessage(); - if (StringUtils.isNotBlank(msg)) { - return msg; - } + if (StringUtils.isNotBlank(msg)) { + return msg; + } - String resourceKey = OPERATION_RESOURCE_KEY_PREFIX + result.getOperation(); - return page.getString(resourceKey, null, resourceKey); + msg = result.getMessage(); + if (StringUtils.isNotBlank(msg)) { + return msg; } + + String resourceKey = OPERATION_RESOURCE_KEY_PREFIX + result.getOperation(); + return page.getString(resourceKey, null, resourceKey); }); message.setOutputMarkupId(true); return message; } - private void initDetails(WebMarkupContainer box, Page parentPage) { + private void initDetails(WebMarkupContainer box) { final WebMarkupContainer details = new WebMarkupContainer("details", getModel()); details.setOutputMarkupId(true); @@ -337,7 +328,7 @@ protected String load() { details.add(operationPanel); Label operationLabel = new Label("operationLabel", - parentPage.getString("FeedbackAlertMessageDetails.operation")); + createStringResource("FeedbackAlertMessageDetails.operation")); operationLabel.setOutputMarkupId(true); operationPanel.add(operationLabel); @@ -355,7 +346,7 @@ protected Object load() { operation.setOutputMarkupId(true); operationPanel.add(operation); - Label count = new Label("countLabel", parentPage.getString("FeedbackAlertMessageDetails.count")); + Label count = new Label("countLabel", createStringResource("FeedbackAlertMessageDetails.count")); count.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -385,7 +376,7 @@ public boolean isVisible() { operationPanel.add(message); - Label messageLabel = new Label("messageLabel", parentPage.getString("FeedbackAlertMessageDetails.message")); + Label messageLabel = new Label("messageLabel", createStringResource("FeedbackAlertMessageDetails.message")); messageLabel.setOutputMarkupId(true); messageLabel.add(new VisibleEnableBehaviour() { @@ -399,14 +390,14 @@ public boolean isVisible() { operationPanel.add(messageLabel); - initParams(operationPanel, getModel(), parentPage); - initContexts(operationPanel, getModel(), parentPage); - initError(operationPanel, getModel(), parentPage); + initParams(operationPanel, getModel()); + initContexts(operationPanel, getModel()); + initError(operationPanel, getModel()); } - private void initParams(WebMarkupContainer operationContent, final IModel model, Page parentPage) { + private void initParams(WebMarkupContainer operationContent, final IModel model) { - Label paramsLabel = new Label("paramsLabel", parentPage.getString("FeedbackAlertMessageDetails.params")); + Label paramsLabel = new Label("paramsLabel", createStringResource("FeedbackAlertMessageDetails.params")); paramsLabel.setOutputMarkupId(true); paramsLabel.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -445,7 +436,7 @@ public boolean isVisible() { @Override protected void populateItem(final ListItem item) { - Panel subresult = new OperationResultPanel("subresult", item.getModel(), getPage()); + Panel subresult = new OperationResultPanel("subresult", item.getModel()); subresult.setOutputMarkupId(true); item.add(subresult); } @@ -464,9 +455,9 @@ public boolean isVisible() { } - private void initContexts(WebMarkupContainer operationContent, final IModel model, Page parentPage) { + private void initContexts(WebMarkupContainer operationContent, final IModel model) { - Label contextsLabel = new Label("contextsLabel", parentPage.getString("FeedbackAlertMessageDetails.contexts")); + Label contextsLabel = new Label("contextsLabel", createStringResource("FeedbackAlertMessageDetails.contexts")); contextsLabel.setOutputMarkupId(true); contextsLabel.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -500,8 +491,8 @@ public boolean isVisible() { operationContent.add(contexts); } - private void initError(WebMarkupContainer operationPanel, final IModel model, Page parentPage) { - Label errorLabel = new Label("errorLabel", parentPage.getString("FeedbackAlertMessageDetails.error")); + private void initError(WebMarkupContainer operationPanel, final IModel model) { + Label errorLabel = new Label("errorLabel", createStringResource("FeedbackAlertMessageDetails.error")); errorLabel.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -693,52 +684,6 @@ private String getLabelCss(final IModel model) { } } - private String getIconCss(final IModel model) { - OpResult result = model.getObject(); - - if (result == null || result.getStatus() == null) { - return "fa-warning text-warning"; - } - - switch (result.getStatus()) { - case IN_PROGRESS: - case NOT_APPLICABLE: - return "fa-info-circle text-info"; - case SUCCESS: - return "fa-check-circle-o text-success"; - case FATAL_ERROR: - - return "fa-times-circle-o text-danger"; - case UNKNOWN: - case PARTIAL_ERROR: - case HANDLED_ERROR: // TODO: - case WARNING: - default: - return "fa-warning text-warning"; - } - } - - static String createMessageTooltip(final IModel model) { - FeedbackMessage message = model.getObject(); - switch (message.getLevel()) { - case FeedbackMessage.INFO: - return "info"; - case FeedbackMessage.SUCCESS: - return "success"; - case FeedbackMessage.ERROR: - return "partialError"; - case FeedbackMessage.FATAL: - return "fatalError"; - case FeedbackMessage.UNDEFINED: - return "undefined"; - case FeedbackMessage.DEBUG: - return "debug"; - case FeedbackMessage.WARNING: - default: - return "warn"; - } - } - @Override public int getWidth() { return 900; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java index 477f8f10c1a..cbf85fd2fc0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java @@ -32,7 +32,7 @@ protected void onInitialize(){ super.onInitialize(); OperationResultPanel operationResultPanel = new OperationResultPanel(ID_OPERATION_RESULTS_PANEL, - Model.of(OpResult.getOpResult(getPageBase(), getModelObject())), getPageBase()); + Model.of(OpResult.getOpResult(getPageBase(), getModelObject()))); operationResultPanel.setOutputMarkupId(true); add(operationResultPanel); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java index f43d3438c49..148c1bbcdae 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java @@ -40,7 +40,7 @@ protected void populateItem(final ListItem item) { if (message.getMessage() instanceof OpResult) { final OpResult opResult = (OpResult) message.getMessage(); - OperationResultPanel panel = new OperationResultPanel("message", Model.of(opResult), getPage()) { + OperationResultPanel panel = new OperationResultPanel("message", Model.of(opResult)) { private static final long serialVersionUID = 1L; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java index 4c33bf4775b..1e2a14bab76 100755 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.web.page.admin; import java.util.*; +import java.util.stream.Collectors; import javax.xml.namespace.QName; @@ -15,6 +16,7 @@ import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; import com.evolveum.midpoint.gui.api.util.WebPrismUtil; import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.prism.PrismContainer; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; @@ -462,39 +464,40 @@ private void changeArchetypeButtonClicked(AjaxRequestTarget target){ private static final long serialVersionUID = 1L; @Override - protected void addPerformed(AjaxRequestTarget target, List newAssignmentsList) { - super.addPerformed(target, newAssignmentsList); + protected void addPerformed(AjaxRequestTarget target, List newAssignmentsList) { + OperationResult result = new OperationResult(OPERATION_EXECUTE_ARCHETYPE_CHANGES); + if (newAssignmentsList.size() > 1) { + result.recordWarning(getString("PageAdminObjectDetails.change.archetype.more.than.one.selected")); + showResult(result); + target.add(PageAdminObjectDetails.this.getFeedbackPanel()); + return; + } + + AssignmentType oldArchetypAssignment = getOldArchetypeAssignment(result); + if (oldArchetypAssignment == null) { + showResult(result); + target.add(PageAdminObjectDetails.this.getFeedbackPanel()); + return; + } + try { - PrismContainerWrapper assignmentsWrapper = getObjectWrapper().findContainer(FocusType.F_ASSIGNMENT); - ((List) newAssignmentsList).forEach(assignment -> { - PrismContainerValue newAssignment = assignmentsWrapper.getItem().createNewValue(); - assignmentsWrapper.getValues().forEach(assignmentValue -> { - if (assignmentValue.getRealValue().getTargetRef() != null - && assignmentValue.getRealValue().getTargetRef().getType() != null - && QNameUtil.match(assignmentValue.getRealValue().getTargetRef().getType(), ArchetypeType.COMPLEX_TYPE)){ - assignmentValue.setStatus(ValueStatus.DELETED); - } - }); - AssignmentType assignmentType = newAssignment.asContainerable(); - assignmentType.setTargetRef(assignment.getTargetRef()); - WebPrismUtil.createNewValueWrapper(assignmentsWrapper, newAssignment, PageAdminObjectDetails.this, target); - OperationResult result = new OperationResult(OPERATION_EXECUTE_ARCHETYPE_CHANGES); - Task task = createSimpleTask(OPERATION_EXECUTE_ARCHETYPE_CHANGES); - try { - ObjectDelta archetypeDelta = getObjectWrapper().getObjectDelta(); - if (!archetypeDelta.isEmpty()) { - archetypeDelta.revive(getPrismContext()); - getModelService().executeChanges(MiscUtil.createCollection(archetypeDelta), null, task, result); - result.computeStatus(); - } - } catch (Exception e) { - LOGGER.error("Cannot save archetype assignment changes: {}", e.getMessage()); - } - showResult(result); - }); - } catch (SchemaException e) { - LOGGER.error("Cannot find assignment wrapper: {}", e.getMessage()); + ObjectDelta delta = getPrismContext().deltaFor(getCompileTimeClass()) + .item(AssignmentHolderType.F_ASSIGNMENT) + .delete(oldArchetypAssignment.clone()) + .asObjectDelta(getObjectWrapper().getOid()); + delta.addModificationAddContainer(AssignmentHolderType.F_ASSIGNMENT, newAssignmentsList.iterator().next()); + + Task task = createSimpleTask(OPERATION_EXECUTE_ARCHETYPE_CHANGES); + getModelService().executeChanges(MiscUtil.createCollection(delta), null, task, result); + + + } catch (Exception e) { + LOGGER.error("Cannot find assignment wrapper: {}", e.getMessage(), e); + result.recordFatalError(getString("PageAdminObjectDetails.change.archetype.failed", e.getMessage()), e); + } + result.computeStatusIfUnknown(); + showResult(result); target.add(PageAdminObjectDetails.this.getFeedbackPanel()); refresh(target); } @@ -581,6 +584,27 @@ protected IModel getWarningMessageModel(){ } + private AssignmentType getOldArchetypeAssignment(OperationResult result) { + PrismContainer assignmentContainer = getObjectWrapper().getObjectOld().findContainer(AssignmentHolderType.F_ASSIGNMENT); + if (assignmentContainer == null) { + //should not happen either + result.recordWarning(getString("PageAdminObjectDetails.archetype.change.not.supported")); + return null; + } + + List oldAssignments = assignmentContainer.getRealValues().stream().filter(a -> WebComponentUtil.isArchetypeAssignment(a)).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(oldAssignments)) { + result.recordWarning(getString("PageAdminObjectDetails.archetype.change.not.supported")); + return null; + } + + if (oldAssignments.size() > 1) { + result.recordFatalError(getString("PageAdminObjectDetails.archetype.change.no.single.archetype")); + return null; + } + return oldAssignments.iterator().next(); + } + private List getArchetypeOidsListToAssign(){ List archetypeOidsList = getFilteredArchetypeOidsList(); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java index 9ba5ba5a628..7ac1e05d872 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java @@ -145,7 +145,7 @@ protected void populateItem(ListItem item) { public void initResultsPanel(RepeatingView resultView, List opresults, Page parentPage) { for (OpResult result : opresults) { - OperationResultPanel resultPanel = new OperationResultPanel(resultView.newChildId(), new Model<>(result), parentPage); + OperationResultPanel resultPanel = new OperationResultPanel(resultView.newChildId(), new Model<>(result)); resultPanel.setOutputMarkupId(true); resultView.add(resultPanel); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java index 2400531f627..866e1006c70 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java @@ -84,8 +84,7 @@ public void onClick(Optional optionalTarget) { OperationResult opResult = OperationResult.createOperationResult(taskType.getResult()); OperationResultPanel body = new OperationResultPanel( getPageBase().getMainPopupBodyId(), - new Model<>(OpResult.getOpResult(getPageBase(), opResult)), - getPageBase()); + new Model<>(OpResult.getOpResult(getPageBase(), opResult))); body.setOutputMarkupId(true); getPageBase().showMainPopup(body, target); } From 43e85a9b9ddd907ac799d8e9247cef06d2fae48f Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Fri, 17 Apr 2020 18:57:48 +0200 Subject: [PATCH 11/27] avoid phantom changes while deleting task statistics and result --- .../web/page/admin/server/PageTask.java | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) 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 e1c46eb4dc9..1608c373593 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 @@ -1,10 +1,7 @@ package com.evolveum.midpoint.web.page.admin.server; import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; - -import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel; +import java.util.*; import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -29,7 +26,9 @@ 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.PrismValue; import com.evolveum.midpoint.prism.Referencable; +import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; @@ -54,8 +53,8 @@ import com.evolveum.midpoint.web.component.AjaxDownloadBehaviorFromStream; import com.evolveum.midpoint.web.component.AjaxIconButton; import com.evolveum.midpoint.web.component.ObjectSummaryPanel; +import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel; import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectMainPanel; -import com.evolveum.midpoint.web.component.prism.ValueStatus; import com.evolveum.midpoint.web.component.refresh.Refreshable; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; @@ -66,8 +65,6 @@ import com.evolveum.midpoint.web.util.TaskOperationUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import static com.evolveum.midpoint.web.component.data.column.ColumnUtils.createStringResource; - @PageDescriptor( urls = { @Url(mountUrl = "/admin/task", matchUrlForSecurity = "/admin/task") @@ -365,11 +362,11 @@ public StringResourceModel getTitle() { @Override public void yesPerformed(AjaxRequestTarget target) { try { - deleteItem(TaskType.F_OPERATION_STATS); - } catch (SchemaException e){ - LOGGER.error("Cannot clear task results: {}", e.getMessage()); + deleteItem(target, TaskType.F_OPERATION_STATS); + } catch (Exception e) { + LOGGER.error("Cannot delete task operation statistics, {}", e.getMessage(), e); + getSession().error(PageTask.this.getString("PageTask.cleanup.operationStatistics.failed")); } - saveTaskChanges(target); } }; showMainPopup(dialog, target); @@ -380,6 +377,26 @@ public void yesPerformed(AjaxRequestTarget target) { repeatingView.add(cleanupPerformance); } + private void deleteItem(AjaxRequestTarget target, ItemName... itemName) throws SchemaException { + List items = Arrays.asList(itemName); + + Collection> itemDeltas = new ArrayList<>(); + for (ItemName item : items) { + ItemDelta delta = createDeleteItemDelta(item); + if (delta == null) { + LOGGER.trace("Nothing to delete for {}", item); + continue; + } + itemDeltas.add(delta); + } + + ObjectDelta taskDelta = getPrismContext().deltaFor(TaskType.class) + .asObjectDelta(getTask().getOid()); + taskDelta.addModifications(itemDeltas); + + saveTaskChanges(target, taskDelta); + } + private void createCleanupResultsButton(RepeatingView repeatingView) { AjaxCompositedIconButton cleanupResults = new AjaxCompositedIconButton(repeatingView.newChildId(), getTaskCleanupCompositedIcon(GuiStyleConstants.CLASS_ICON_TASK_RESULTS), createStringResource("operationalButtonsPanel.cleanupResults")) { @@ -397,12 +414,11 @@ public StringResourceModel getTitle() { @Override public void yesPerformed(AjaxRequestTarget target) { try { - deleteItem(TaskType.F_RESULT); - deleteItem(TaskType.F_RESULT_STATUS); - } catch (SchemaException e){ + deleteItem(target, TaskType.F_RESULT, TaskType.F_RESULT_STATUS); + } catch (Exception e){ LOGGER.error("Cannot clear task results: {}", e.getMessage()); + getSession().error(PageTask.this.getString("PageTask.cleanup.result.failed")); } - saveTaskChanges(target); } }; showMainPopup(dialog, target); @@ -413,32 +429,26 @@ public void yesPerformed(AjaxRequestTarget target) { repeatingView.add(cleanupResults); } - private void deleteItem(ItemName itemName) throws SchemaException { + private ItemDelta createDeleteItemDelta(ItemName itemName) throws SchemaException { ItemWrapper item = getObjectWrapper().findItem(itemName, ItemWrapper.class); if (item == null) { - return; + return null; } PrismValueWrapper itemValue = item.getValue(); if (itemValue == null) { - return; + return null; } - itemValue.setStatus(ValueStatus.DELETED); + PrismValue oldValue = itemValue.getOldValue().clone(); - } + return getPrismContext().deltaFor(TaskType.class) + .item(itemName) + .delete(oldValue) + .asItemDelta(); - private void saveTaskChanges(AjaxRequestTarget target) { - try { - ObjectDelta taskDelta = getObjectWrapper().getObjectDelta(); - saveTaskChanges(target, taskDelta); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Cannot get task delta.", e); - getSession().error("Cannot save changes, there were problems with computing changes: " + e.getMessage()); - target.add(getFeedbackPanel()); - } } - + private void saveTaskChanges(AjaxRequestTarget target, ObjectDelta taskDelta){ if (taskDelta.isEmpty()) { getSession().warn("Nothing to save, no changes were made."); From c4069714e8ccc1feb02cb2d2b73390cc9cbbf989 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Fri, 17 Apr 2020 19:09:57 +0200 Subject: [PATCH 12/27] Fix visibility of approval buttons for attorney The attorney feature was fixed recently but with the exception of Approve/Reject/Forward buttons visibility. This is implemented in this commit, along with some tests for basic back-end attorney approvals. Also: - eliminating needless execution task run when no work items were approved - created convenience methods runUnderPowerOfAttorney and runUnderPowerOfAttorneyChecked in ModelInteractionService and similar method in WebComponentUtil This resolves MID-6225. --- .../gui/api/util/WebComponentUtil.java | 8 + .../admin/cases/CaseWorkItemActionsPanel.java | 31 +- .../page/admin/cases/PageCaseWorkItem.java | 7 +- .../admin/workflow/WorkItemDetailsPanel.java | 31 +- .../MidPointGuiAuthorizationEvaluator.java | 4 +- .../midpoint/prism/util/PrismAsserts.java | 16 + .../schema/util/CaseWorkItemUtil.java | 6 +- .../midpoint/test/util/MidPointAsserts.java | 27 ++ .../model/api/ModelInteractionService.java | 15 +- .../ModelInteractionServiceImpl.java | 23 +- .../model/intest/sync/TestImportRecon.java | 2 +- .../test/AbstractModelIntegrationTest.java | 15 + .../impl/engine/EngineInvocationContext.java | 8 +- .../CaseOperationExecutionTaskHandler.java | 82 +---- .../wf/impl/execution/ExecutionHelper.java | 70 +++- .../wf/impl/execution/LensContextHelper.java | 112 +++++++ .../wf/impl/processors/ChangeProcessor.java | 3 +- .../primary/PrimaryChangeProcessor.java | 36 +-- .../midpoint/wf/impl/AbstractWfTest.java | 11 + .../wf/impl/other/TestMiscellaneous.java | 306 +++++++++++++----- .../miscellaneous/role-accountant.xml | 24 ++ .../miscellaneous/role-vault-access.xml | 11 + .../miscellaneous/user-gizmoduck.xml | 16 + .../miscellaneous/user-launchpad.xml | 12 + .../resources/miscellaneous/user-scrooge.xml | 16 + .../test/AbstractIntegrationTest.java | 5 +- .../midpoint/test/DummyTestResource.java | 3 +- .../evolveum/midpoint/test/TestResource.java | 9 +- .../test/asserter/AbstractAsserter.java | 12 + .../midpoint/test/asserter/CaseAsserter.java | 108 ++++++- .../test/asserter/CaseEventAsserter.java | 64 ++++ .../test/asserter/CaseEventFinder.java | 105 ++++++ .../test/asserter/CaseEventsAsserter.java | 89 +++++ .../midpoint/test/asserter/CaseFinder.java | 99 ++++++ .../test/asserter/CaseWorkItemAsserter.java | 88 +++++ .../test/asserter/CaseWorkItemFinder.java | 91 ++++++ .../test/asserter/CaseWorkItemsAsserter.java | 82 +++++ .../midpoint/test/asserter/FocusAsserter.java | 14 +- .../test/asserter/SubcasesAsserter.java | 84 +++++ .../asserter/prism/PrismObjectAsserter.java | 57 +++- .../enforcer/api/SecurityEnforcer.java | 10 +- .../enforcer/impl/SecurityEnforcerImpl.java | 4 +- 42 files changed, 1534 insertions(+), 282 deletions(-) create mode 100644 model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/LensContextHelper.java create mode 100644 model/workflow-impl/src/test/resources/miscellaneous/role-accountant.xml create mode 100644 model/workflow-impl/src/test/resources/miscellaneous/role-vault-access.xml create mode 100644 model/workflow-impl/src/test/resources/miscellaneous/user-gizmoduck.xml create mode 100644 model/workflow-impl/src/test/resources/miscellaneous/user-launchpad.xml create mode 100644 model/workflow-impl/src/test/resources/miscellaneous/user-scrooge.xml create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventAsserter.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventFinder.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventsAsserter.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseFinder.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemAsserter.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemFinder.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemsAsserter.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/SubcasesAsserter.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java index 8f10504981a..0227dff7dcc 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/util/WebComponentUtil.java @@ -4134,6 +4134,14 @@ public static void dropPowerOfAttorneyIfRequested(OperationResult result, PrismO } } + public static T runUnderPowerOfAttorneyIfNeeded(CheckedProducer producer, PrismObject powerDonor, + PageBase pageBase, Task task, OperationResult result) throws CommonException { + if (powerDonor != null) { + return pageBase.getModelInteractionService().runUnderPowerOfAttorneyChecked(producer, powerDonor, task, result); + } else { + return producer.get(); + } + } @NotNull public static List computeChangesCategorizationList(ChangesByState changesByState, ObjectReferenceType objectRef, diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java index 5e836384733..60c6bb6f809 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/CaseWorkItemActionsPanel.java @@ -84,7 +84,7 @@ public void onClick(AjaxRequestTarget ajaxRequestTarget) { } }; - workItemApproveButton.add(new VisibleBehaviour(() -> isApproveRejectButtonVisible())); + workItemApproveButton.add(new VisibleBehaviour(this::isApproveRejectButtonVisible)); workItemApproveButton.setOutputMarkupId(true); actionButtonsContainer.add(workItemApproveButton); @@ -100,7 +100,7 @@ public void onClick(AjaxRequestTarget ajaxRequestTarget) { } }; workItemRejectButton.setOutputMarkupId(true); - workItemRejectButton.add(new VisibleBehaviour(() -> isApproveRejectButtonVisible())); + workItemRejectButton.add(new VisibleBehaviour(this::isApproveRejectButtonVisible)); actionButtonsContainer.add(workItemRejectButton); AjaxButton workItemForwardButton = new AjaxButton(ID_WORK_ITEM_FORWARD_BUTTON, @@ -113,7 +113,7 @@ public void onClick(AjaxRequestTarget ajaxRequestTarget) { } }; workItemForwardButton.setOutputMarkupId(true); - workItemForwardButton.add(new VisibleBehaviour(() -> isForwardButtonVisible())); + workItemForwardButton.add(new VisibleBehaviour(this::isForwardButtonVisible)); actionButtonsContainer.add(workItemForwardButton); AjaxButton workItemClaimButton = new AjaxButton(ID_WORK_ITEM_CLAIM_BUTTON, @@ -240,30 +240,37 @@ public String getObject() { } private boolean isApproveRejectButtonVisible() { - boolean isAuthorized = false; + if (CaseWorkItemUtil.isCaseWorkItemClosed(getModelObject()) || + CaseWorkItemUtil.isWorkItemClaimable(getModelObject())) { + return false; // checking separately to avoid needless authorization checking + } try { OperationResult result = new OperationResult(OPERATION_CHECK_SUBMIT_ACTION_AUTHORIZATION); Task task = getPageBase().createSimpleTask(OPERATION_CHECK_SUBMIT_ACTION_AUTHORIZATION); - isAuthorized = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(), task, result); + return WebComponentUtil.runUnderPowerOfAttorneyIfNeeded(() -> + getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(), task, result), + getPowerDonor(), getPageBase(), task, result); } catch (Exception ex) { LOGGER.error("Cannot check current user authorization to submit work item: {}", ex.getLocalizedMessage(), ex); + return false; } - return CaseWorkItemUtil.isCaseWorkItemNotClosed(getModelObject()) && - !CaseWorkItemUtil.isWorkItemClaimable(getModelObject()) && isAuthorized; - } private boolean isForwardButtonVisible() { - boolean isAuthorized = false; + if (CaseWorkItemUtil.isCaseWorkItemClosed(getModelObject()) || + CaseWorkItemUtil.isWorkItemClaimable(getModelObject())) { + return false; // checking separately to avoid needless authorization checking + } try { OperationResult result = new OperationResult(OPERATION_CHECK_DELEGATE_AUTHORIZATION); Task task = getPageBase().createSimpleTask(OPERATION_CHECK_DELEGATE_AUTHORIZATION); - isAuthorized = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(), task, result); + return WebComponentUtil.runUnderPowerOfAttorneyIfNeeded(() -> + getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(), task, result), + getPowerDonor(), getPageBase(), task, result); } catch (Exception ex) { LOGGER.error("Cannot check current user authorization to submit work item: {}", ex.getLocalizedMessage(), ex); + return false; } - return CaseWorkItemUtil.isCaseWorkItemNotClosed(getModelObject()) && - !CaseWorkItemUtil.isWorkItemClaimable(getModelObject()) && isAuthorized; } private boolean isClaimButtonVisible() { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java index 54ad9e9c37b..cf92b6d1742 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/PageCaseWorkItem.java @@ -215,7 +215,12 @@ private void initLayout(){ summaryPanel.setOutputMarkupId(true); add(summaryPanel); - WorkItemDetailsPanel workItemDetailsPanel = new WorkItemDetailsPanel(ID_WORK_ITEM_DETAILS, caseWorkItemModel); + WorkItemDetailsPanel workItemDetailsPanel = new WorkItemDetailsPanel(ID_WORK_ITEM_DETAILS, caseWorkItemModel) { + @Override + protected PrismObject getPowerDonor() { + return PageCaseWorkItem.this.getPowerDonor(); + } + }; workItemDetailsPanel.setOutputMarkupId(true); add(workItemDetailsPanel); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java index 53f29106f89..b7f3ca10581 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ApprovalContextUtil; import com.evolveum.midpoint.schema.util.CaseTypeUtil; import com.evolveum.midpoint.schema.util.WorkItemTypeUtil; @@ -34,7 +35,6 @@ import com.evolveum.midpoint.web.component.prism.show.ScenePanel; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; -import com.evolveum.midpoint.web.page.admin.cases.ManualCaseTabPanel; import com.evolveum.midpoint.web.page.admin.cases.PageCaseWorkItem; import com.evolveum.midpoint.web.page.admin.configuration.component.EmptyOnBlurAjaxFormUpdatingBehaviour; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -55,7 +55,7 @@ /** * Created by honchar */ -public class WorkItemDetailsPanel extends BasePanel{ +public class WorkItemDetailsPanel extends BasePanel { private static final long serialVersionUID = 1L; private static final String DOT_CLASS = WorkItemDetailsPanel.class.getName() + "."; @@ -63,9 +63,7 @@ public class WorkItemDetailsPanel extends BasePanel{ private static final String OPERATION_PREPARE_DELTA_VISUALIZATION = DOT_CLASS + "prepareDeltaVisualization"; private static final String OPERATION_LOAD_CUSTOM_FORM = DOT_CLASS + "loadCustomForm"; private static final String OPERATION_LOAD_CASE_FOCUS_OBJECT = DOT_CLASS + "loadCaseFocusObject"; - private static final String OPERATION_CHECK_SUBMIT_AUTHORIZATION = DOT_CLASS + "checkApproveRejectAuthorization"; - private static final String OPERATION_CHECK_DELEGATE_AUTHORIZATION = DOT_CLASS + "checkDelegateAuthorization"; - private static final String OPERATION_CHECK_CLAIM_AUTHORIZATION = DOT_CLASS + "checkClaimAuthorization"; + private static final String OPERATION_CHECK_ACTIONS_AUTHORIZATION = DOT_CLASS + "checkActionsAuthorization"; private static final String ID_DISPLAY_NAME_PANEL = "displayNamePanel"; private static final String ID_REQUESTED_BY = "requestedBy"; @@ -290,22 +288,25 @@ public String getObject() { } - private boolean isAuthorizedForActions(){ - Task checkApproveRejectAuthTask = getPageBase().createSimpleTask(OPERATION_CHECK_SUBMIT_AUTHORIZATION); - Task checkDelegateAuthTask = getPageBase().createSimpleTask(OPERATION_CHECK_DELEGATE_AUTHORIZATION); + private boolean isAuthorizedForActions() { + Task task = getPageBase().createSimpleTask(OPERATION_CHECK_ACTIONS_AUTHORIZATION); + OperationResult result = task.getResult(); try { - boolean isAuthorizedToSubmit = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(), - checkApproveRejectAuthTask, checkApproveRejectAuthTask.getResult()); - boolean isAuthorizedToDelegate = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(), - checkDelegateAuthTask, checkDelegateAuthTask.getResult()); - boolean isAuthorizedToClaim = getPageBase().getWorkflowManager().isCurrentUserAuthorizedToClaim(getModelObject()); - return isAuthorizedToSubmit || isAuthorizedToClaim || isAuthorizedToDelegate; - } catch (Exception ex){ + return WebComponentUtil.runUnderPowerOfAttorneyIfNeeded(() -> + getPageBase().getWorkflowManager().isCurrentUserAuthorizedToSubmit(getModelObject(), task, result) || + getPageBase().getWorkflowManager().isCurrentUserAuthorizedToDelegate(getModelObject(), task, result) || + getPageBase().getWorkflowManager().isCurrentUserAuthorizedToClaim(getModelObject()), + getPowerDonor(), getPageBase(), task, result); + } catch (Exception ex) { LOGGER.error("Unable to check user authorization for workitem actions: {}", ex.getLocalizedMessage()); } return false; } + protected PrismObject getPowerDonor() { + return null; + } + // Expects that we deal with primary changes of the focus (i.e. not of projections) // Beware: returns the full object; regardless of the security settings public ObjectType getCaseFocusObject(CaseType caseType) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointGuiAuthorizationEvaluator.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointGuiAuthorizationEvaluator.java index 0b8b1be97f2..b48a69829a1 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointGuiAuthorizationEvaluator.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/security/MidPointGuiAuthorizationEvaluator.java @@ -323,8 +323,8 @@ public F computeSecurityFilter(M } @Override - public MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal, - String attorneyAuthorizationAction, PrismObject donor, Task task, + public MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal, + String attorneyAuthorizationAction, PrismObject donor, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java index daf8d1b517d..a512f067ddc 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/util/PrismAsserts.java @@ -120,6 +120,13 @@ public static void assertReferenceValues(PrismReference ref, String... oids) { } } + public static void assertReferenceValues(List refs, String... oids) { + assert oids.length == refs.size() : "Wrong number of values in "+refs+"; expected "+oids.length+" but was "+refs.size(); + for (String oid: oids) { + assertReferenceValue(refs, oid); + } + } + public static void assertReferenceValue(PrismReference ref, String oid) { for (PrismReferenceValue val: ref.getValues()) { if (oid.equals(val.getOid())) { @@ -129,6 +136,15 @@ public static void assertReferenceValue(PrismReference ref, String oid) { fail("Oid "+oid+" not found in reference "+ref); } + public static void assertReferenceValue(List refs, String oid) { + for (Referencable ref: refs) { + if (oid.equals(ref.getOid())) { + return; + } + } + fail("Oid "+oid+" not found among references "+refs); + } + public static void assertItems(PrismContainer object, int expectedNumberOfItems) { List values = (List)object.getValues(); if (expectedNumberOfItems == 0) { diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java index 1c2e8c2a6cf..7aa3cb016fa 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java @@ -64,10 +64,14 @@ public static CaseWorkItemType getWorkItem(CaseType aCase, long id) { return null; } - public static boolean isCaseWorkItemNotClosed(CaseWorkItemType workItem){ + public static boolean isCaseWorkItemNotClosed(CaseWorkItemType workItem) { return workItem != null && workItem.getCloseTimestamp() == null; } + public static boolean isCaseWorkItemClosed(CaseWorkItemType workItem) { + return workItem != null && workItem.getCloseTimestamp() != null; + } + public static boolean isWorkItemClaimable(CaseWorkItemType workItem){ return workItem != null && (workItem.getOriginalAssigneeRef() == null || StringUtils.isEmpty(workItem.getOriginalAssigneeRef().getOid())) && !doesAssigneeExist(workItem) && CollectionUtils.isNotEmpty(workItem.getCandidateRef()); diff --git a/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java b/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java index 4f197bcfbf2..8d39288005c 100644 --- a/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java +++ b/infra/test-util/src/main/java/com/evolveum/midpoint/test/util/MidPointAsserts.java @@ -6,6 +6,7 @@ */ package com.evolveum.midpoint.test.util; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.AssertJUnit.*; import java.util.ArrayList; @@ -356,4 +357,30 @@ public static void assertInstanceOf(String message, Object object, Class expe private static PrismContext getPrismContext() { return TestSpringContextHolder.getPrismContext(); } + + public static void assertThatReferenceMatches(ObjectReferenceType ref, String desc, String expectedOid, QName expectedType) { + assertThat(ref).as(desc).isNotNull(); + assertThat(ref.getOid()).as(desc + ".oid").isEqualTo(expectedOid); + assertThatTypeMatches(ref.getType(), desc + ".type", expectedType); + } + + // here because of AssertJ dependency (consider moving) + public static void assertThatTypeMatches(QName actualType, String desc, QName expectedType) { + assertThat(actualType).as(desc) + .matches(t -> QNameUtil.match(t, expectedType), "matches " + expectedType); + } + + // here because of AssertJ dependency (consider moving) + public static void assertUriMatches(String current, String desc, QName expected) { + assertThat(current).as(desc) + .isNotNull() + .matches(s -> QNameUtil.match(QNameUtil.uriToQName(s, true), expected), "is " + expected); + } + + // here because of AssertJ dependency (consider moving) + public static void assertUriMatches(String current, String desc, String expected) { + assertThat(current).as(desc) + .isNotNull() + .matches(s -> QNameUtil.matchUri(s, expected), "is " + expected); + } } diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java index 1605fca7f9d..c081615fc6f 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/ModelInteractionService.java @@ -27,7 +27,10 @@ import com.evolveum.midpoint.security.api.MidPointPrincipal; import com.evolveum.midpoint.security.enforcer.api.ItemSecurityConstraints; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.CheckedProducer; import com.evolveum.midpoint.util.DisplayableValue; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.Producer; import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ExecuteCredentialResetRequestType; @@ -151,7 +154,7 @@ ModelContext previewChanges( RoleSelectionSpecification getAssignableRoleSpecification(PrismObject assignmentHolder, Class targetType, int assignmentOrder, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException; /** - * Returns filter for lookup of donors or power of attorney. The donors are the users that have granted + * Returns filter for lookup of donors of power of attorney. The donors are the users that have granted * the power of attorney to the currently logged-in user. * * TODO: authorization limitations @@ -352,12 +355,20 @@ List getDeputyAssignees(ObjectReferenceType assignee, QName */ ActivationStatusType getAssignmentEffectiveStatus(String lifecycleStatus, ActivationType activationType); - MidPointPrincipal assumePowerOfAttorney(PrismObject donor, Task task, OperationResult result) + MidPointPrincipal assumePowerOfAttorney(PrismObject donor, Task task, OperationResult result) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException; MidPointPrincipal dropPowerOfAttorney(Task task, OperationResult result) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException; + T runUnderPowerOfAttorney(Producer producer, PrismObject donor, Task task, OperationResult result) + throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException; + + default T runUnderPowerOfAttorneyChecked(CheckedProducer producer, PrismObject donor, Task task, OperationResult result) + throws CommonException { + return MiscUtil.runChecked((p) -> runUnderPowerOfAttorney(p, donor, task, result), producer); + } + // Maybe a bit of hack: used to deduplicate processing of localizable message templates @NotNull LocalizableMessageType createLocalizableMessageType(LocalizableMessageTemplateType template, diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java index 9c91af6cf58..8cebd183846 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelInteractionServiceImpl.java @@ -34,6 +34,7 @@ import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; import com.evolveum.midpoint.schema.util.*; import com.evolveum.midpoint.security.enforcer.api.FilterGizmo; +import com.evolveum.midpoint.util.*; import com.evolveum.midpoint.xml.ns._public.common.api_types_3.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.BooleanUtils; @@ -128,13 +129,6 @@ import com.evolveum.midpoint.security.enforcer.api.ObjectSecurityConstraints; import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.DisplayableValue; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.LocalizableMessageBuilder; -import com.evolveum.midpoint.util.MiscUtil; -import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.CommonException; import com.evolveum.midpoint.util.exception.CommunicationException; @@ -1507,7 +1501,7 @@ public ActivationStatusType getAssignmentEffectiveStatus(String lifecycleStatus, } @Override - public MidPointPrincipal assumePowerOfAttorney(PrismObject donor, Task task, OperationResult result) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException { + public MidPointPrincipal assumePowerOfAttorney(PrismObject donor, Task task, OperationResult result) throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException { MidPointPrincipal attorneyPrincipal = securityContextManager.getPrincipal(); MidPointPrincipal donorPrincipal = securityEnforcer.createDonorPrincipal(attorneyPrincipal, ModelAuthorizationAction.ATTORNEY.getUrl(), donor, task, result); @@ -1550,6 +1544,19 @@ public MidPointPrincipal dropPowerOfAttorney(Task task, OperationResult result) return previousPrincipal; } + @Override + public T runUnderPowerOfAttorney(Producer producer, + PrismObject donor, Task task, OperationResult result) + throws SchemaException, SecurityViolationException, ObjectNotFoundException, ExpressionEvaluationException, + CommunicationException, ConfigurationException { + assumePowerOfAttorney(donor, task, result); + try { + return producer.run(); + } finally { + dropPowerOfAttorney(task, result); + } + } + @Override @NotNull public LocalizableMessageType createLocalizableMessageType(LocalizableMessageTemplateType template, diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java index 27079c14d62..f20e0cd2ca0 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java @@ -94,7 +94,7 @@ public class TestImportRecon extends AbstractInitializedModelIntegrationTest { private static final String USER_AUGUSTUS_NAME = "augustus"; - private static class AccountTestResource extends TestResource { + private static class AccountTestResource extends TestResource { private final String name; @SuppressWarnings({ "FieldCanBeLocal", "unused" }) private final String fullName; diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index d1ddc86b3e1..c2105115073 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -5395,6 +5395,15 @@ protected String addAndRecompute(File file, Task task, OperationResult result) t return object.getOid(); } + protected String addAndRecompute(TestResource testResource, Task task, OperationResult result) throws Exception { + PrismObject object = repoAddObjectFromFile(testResource.file, result); + modelService.recompute(object.asObjectable().getClass(), object.getOid(), null, task, result); + PrismObject after = getObject(object.asObjectable().getClass(), object.getOid()); + display("Object: " + testResource.file, after); + testResource.object = after; + return after.getOid(); + } + protected void assertAuditReferenceValue(AuditEventRecord event, String refName, String oid, QName type, String name) { Set values = event.getReferenceValues(refName); assertEquals("Wrong # of reference values of '" + refName + "'", 1, values.size()); @@ -5938,6 +5947,12 @@ protected CaseAsserter assertCase(String oid, String message) throws Objec return asserter; } + protected CaseAsserter assertCase(CaseType aCase, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + CaseAsserter asserter = CaseAsserter.forCase(aCase.asPrismObject(), message); + initializeAsserter(asserter); + return asserter; + } + protected CaseAsserter assertCaseAfter(String oid) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { CaseAsserter asserter = assertCase(oid, "after"); asserter.display(); diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java index 324b878ae47..ad25e833fa3 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/engine/EngineInvocationContext.java @@ -25,10 +25,7 @@ import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.impl.engine.helpers.DelayedNotification; @@ -141,7 +138,8 @@ public void addAuditRecord(AuditEventRecord record) { } public void commit(OperationResult parentResult) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException { + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException, + ExpressionEvaluationException, ConfigurationException, CommunicationException { OperationResult result = parentResult.subresult(OP_COMMIT) .setMinor() .build(); diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/CaseOperationExecutionTaskHandler.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/CaseOperationExecutionTaskHandler.java index 52bde4cd729..fd908082711 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/CaseOperationExecutionTaskHandler.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/CaseOperationExecutionTaskHandler.java @@ -7,19 +7,15 @@ package com.evolveum.midpoint.wf.impl.execution; -import com.evolveum.midpoint.model.api.context.ModelProjectionContext; import com.evolveum.midpoint.model.impl.lens.Clockwork; import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.ObjectTreeDeltas; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.CaseTypeUtil; import com.evolveum.midpoint.task.api.*; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.LoggingUtils; @@ -29,7 +25,6 @@ import com.evolveum.midpoint.wf.impl.processors.primary.PcpGeneralHelper; import com.evolveum.midpoint.wf.impl.util.MiscHelper; import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskPartitionDefinitionType; import org.springframework.beans.factory.annotation.Autowired; @@ -63,6 +58,7 @@ public class CaseOperationExecutionTaskHandler implements TaskHandler { @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; + @Autowired private LensContextHelper lensContextHelper; @Override public TaskRunResult run(RunningTask task, TaskPartitionDefinitionType partitionDefinition) { @@ -109,7 +105,7 @@ private void executeLocalChanges(CaseType subcase, RunningTask task, OperationRe if (focusChange != null) { approvalMetadataHelper.addAssignmentApprovalMetadata(focusChange, subcase, task, result); } - mergeDeltasToModelContext(modelContext, singletonList(deltas)); + lensContextHelper.mergeDeltasToModelContext(modelContext, singletonList(deltas)); executeModelContext(modelContext, subcase, task, result); executionHelper.closeCaseInRepository(subcase, result); executionHelper.checkDependentCases(subcase.getParentRef().getOid(), result); @@ -119,78 +115,12 @@ private void executeAllChanges(CaseType rootCase, RunningTask task, OperationRes throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, ExpressionEvaluationException, PolicyViolationException, PreconditionViolationException, ObjectAlreadyExistsException, SecurityViolationException { - LensContext modelContext = collectApprovedDeltasToModelContext(rootCase, task, result); + List subcases = miscHelper.getSubcases(rootCase, result); + LensContext modelContext = lensContextHelper.collectApprovedDeltasToModelContext(rootCase, subcases, task, result); executeModelContext(modelContext, rootCase, task, result); executionHelper.closeCaseInRepository(rootCase, result); } - private LensContext collectApprovedDeltasToModelContext(CaseType rootCase, RunningTask task, OperationResult result) - throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, - ExpressionEvaluationException { - List subcases = miscHelper.getSubcases(rootCase, result); - LensContext rootContext = (LensContext) miscHelper.getModelContext(rootCase, task, result); - List deltasToMerge = new ArrayList<>(); - - for (CaseType subcase : subcases) { - if (!CaseTypeUtil.isClosed(subcase)) { - throw new IllegalStateException("Child case " + subcase + " is not in CLOSED state; its state is " + subcase.getState()); - } - ObjectTreeDeltas deltas = pcpGeneralHelper.retrieveResultingDeltas(subcase); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Child case {} has {} resulting deltas", subcase, deltas != null ? deltas.getDeltaList().size() : 0); - } - if (deltas != null) { - ObjectDelta focusChange = deltas.getFocusChange(); - if (focusChange != null) { - approvalMetadataHelper.addAssignmentApprovalMetadata(focusChange, subcase, task, result); - } - if (focusChange != null && focusChange.isAdd()) { - deltasToMerge.add(0, deltas); // "add" must go first - } else { - deltasToMerge.add(deltas); - } - } - } - mergeDeltasToModelContext(rootContext, deltasToMerge); - return rootContext; - } - - private void mergeDeltasToModelContext(LensContext rootContext, List deltasToMerge) - throws SchemaException { - for (ObjectTreeDeltas deltaToMerge : deltasToMerge) { - LensFocusContext focusContext = rootContext.getFocusContext(); - ObjectDelta focusDelta = deltaToMerge.getFocusChange(); - if (focusDelta != null) { - LOGGER.trace("Adding delta to root model context; delta = {}", focusDelta.debugDumpLazily()); - if (focusContext.getPrimaryDelta() != null && !focusContext.getPrimaryDelta().isEmpty()) { - //noinspection unchecked - focusContext.addPrimaryDelta(focusDelta); - } else { - //noinspection unchecked - focusContext.setPrimaryDelta(focusDelta); - } - } - //noinspection unchecked - Set>> entries = deltaToMerge.getProjectionChangeMapEntries(); - for (Map.Entry> entry : entries) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Adding projection delta to root model context; rsd = {}, delta = {}", entry.getKey(), - entry.getValue().debugDump()); - } - ModelProjectionContext projectionContext = rootContext.findProjectionContext(entry.getKey()); - if (projectionContext == null) { - // TODO more liberal treatment? - throw new IllegalStateException("No projection context for " + entry.getKey()); - } - if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { - projectionContext.addPrimaryDelta(entry.getValue()); - } else { - projectionContext.setPrimaryDelta(entry.getValue()); - } - } - } - } - private void executeModelContext(LensContext modelContext, CaseType aCase, RunningTask task, OperationResult result) throws SchemaException, CommunicationException, ObjectNotFoundException, ObjectAlreadyExistsException, ConfigurationException, PreconditionViolationException, SecurityViolationException, PolicyViolationException, @@ -212,7 +142,7 @@ private void executeModelContext(LensContext modelContext, CaseType aCase, Ru while (projectionIterator.hasNext()) { LensProjectionContext projectionContext = projectionIterator.next(); if (!ObjectDelta.isEmpty(projectionContext.getPrimaryDelta()) || !ObjectDelta.isEmpty(projectionContext.getSyncDelta())) { - continue; // don't remove client requested or externally triggered actions! + continue; // don't remove client requested or externally triggered actions! } if (LOGGER.isTraceEnabled()) { LOGGER.trace("Removing projection context {}", projectionContext.getHumanReadableName()); @@ -223,8 +153,6 @@ private void executeModelContext(LensContext modelContext, CaseType aCase, Ru task.setChannel(modelContext.getChannel()); } clockwork.run(modelContext, task, result); -// task.setModelOperationContext(context.toLensContextType(context.getState() == ModelState.FINAL)); -// task.flushPendingModifications(result); } @Override diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java index c22c7a40ae5..bb4077b5492 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/ExecutionHelper.java @@ -9,6 +9,7 @@ import com.evolveum.midpoint.common.Clock; import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ItemDelta; @@ -20,11 +21,11 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CaseTypeUtil; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.task.api.TaskExecutionStatus; import com.evolveum.midpoint.task.api.TaskManager; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.impl.access.AuthorizationHelper; @@ -72,6 +73,7 @@ public class ExecutionHelper { @Autowired public WorkItemHelper workItemHelper; @Autowired public AuthorizationHelper authorizationHelper; @Autowired private SystemObjectCache systemObjectCache; + @Autowired private LensContextHelper lensContextHelper; private static final String DEFAULT_EXECUTION_GROUP_PREFIX_FOR_SERIALIZATION = "$approval-task-group$:"; private static final long DEFAULT_SERIALIZATION_RETRY_TIME = 10000L; @@ -86,15 +88,6 @@ public void closeCaseInRepository(CaseType aCase, OperationResult result) LOGGER.debug("Marked case {} as closed", aCase); } - public void setCaseStateInRepository(CaseType aCase, String newState, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { - List> modifications = prismContext.deltaFor(CaseType.class) - .item(CaseType.F_STATE).replace(newState) - .asItemDeltas(); - repositoryService.modifyObject(CaseType.class, aCase.getOid(), modifications, result); - LOGGER.debug("Marked case {} as {}", aCase, newState); - } - /** * We need to check * 1) if there are any executable cases that depend on this one @@ -147,7 +140,7 @@ public void checkDependentCases(String rootOid, OperationResult result) } } - public void setExecutionConstraints(Task task, CaseType aCase, OperationResult result) throws SchemaException { + private void setExecutionConstraints(Task task, CaseType aCase, OperationResult result) throws SchemaException { PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); WfConfigurationType wfConfiguration = systemConfiguration != null ? systemConfiguration.asObjectable().getWorkflowConfiguration() : null; WfExecutionTasksConfigurationType tasksConfig = wfConfiguration != null ? wfConfiguration.getExecutionTasks() : null; @@ -225,4 +218,55 @@ private String getGroupSuffix(WfExecutionTasksSerializationScopeType scope, Case throw new AssertionError("Unknown scope: " + scope); } } + + public void submitExecutionTask(CaseType aCase, boolean waiting, OperationResult result) + throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { + + // We must do this before the task is started, because as part of task completion we set state to CLOSED. + // So if we set state to EXECUTING after the task is started, the case might be already closed at that point. + // (If task is fast enough.) + markCaseAsExecuting(aCase, result); + + Task task = taskManager.createTaskInstance("execute"); + task.setName("Execution of " + aCase.getName().getOrig()); + task.setOwner(getExecutionTaskOwner(result)); + task.setObjectRef(ObjectTypeUtil.createObjectRef(aCase, prismContext)); + task.setHandlerUri(CaseOperationExecutionTaskHandler.HANDLER_URI); + if (waiting) { + task.setInitialExecutionStatus(TaskExecutionStatus.WAITING); + } + setExecutionConstraints(task, aCase, result); + taskManager.switchToBackground(task, result); + } + + private void markCaseAsExecuting(CaseType aCase, OperationResult result) + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { + List> modifications = prismContext.deltaFor(CaseType.class) + .item(CaseType.F_STATE).replace(SchemaConstants.CASE_STATE_EXECUTING) + .asItemDeltas(); + repositoryService.modifyObject(CaseType.class, aCase.getOid(), modifications, result); + LOGGER.debug("Marked case {} as {}", aCase, SchemaConstants.CASE_STATE_EXECUTING); + } + + private PrismObject getExecutionTaskOwner(OperationResult result) throws SchemaException, ObjectNotFoundException { + return repositoryService.getObject(UserType.class, SystemObjectsType.USER_ADMINISTRATOR.value(), null, result); + } + + /** + * Checks that there are any deltas to be executed and if so, submits the execution task. + * @param rootCase Operation request case (always!) + * @param subcases List of subcases (must be current). + */ + public void submitExecutionTaskIfNeeded(CaseType rootCase, List subcases, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + ExpressionEvaluationException, ObjectAlreadyExistsException { + assert ObjectTypeUtil.hasArchetype(rootCase, SystemObjectsType.ARCHETYPE_OPERATION_REQUEST.value()); + LensContext lensContext = lensContextHelper.collectApprovedDeltasToModelContext(rootCase, subcases, task, result); + if (lensContext.hasAnyPrimaryChange()) { + submitExecutionTask(rootCase, false, result); + } else { + LOGGER.trace("No primary delta in 'approved' lens context, not submitting the execution task"); + closeCaseInRepository(rootCase, result); + } + } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/LensContextHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/LensContextHelper.java new file mode 100644 index 00000000000..8288a8ec986 --- /dev/null +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/execution/LensContextHelper.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.wf.impl.execution; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.evolveum.midpoint.task.api.Task; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.model.api.context.ModelProjectionContext; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.ObjectTreeDeltas; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.CaseTypeUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.wf.impl.processors.primary.ApprovalMetadataHelper; +import com.evolveum.midpoint.wf.impl.processors.primary.PcpGeneralHelper; +import com.evolveum.midpoint.wf.impl.util.MiscHelper; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; + +/** + * Helps with managing LensContext objects for approved changes execution. + */ +@Component +public class LensContextHelper { + + private static final Trace LOGGER = TraceManager.getTrace(LensContextHelper.class); + + @Autowired private MiscHelper miscHelper; + @Autowired private PcpGeneralHelper pcpGeneralHelper; + @Autowired private ApprovalMetadataHelper approvalMetadataHelper; + + LensContext collectApprovedDeltasToModelContext(CaseType rootCase, List subcases, Task task, OperationResult result) + throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, + ExpressionEvaluationException { + LensContext rootContext = (LensContext) miscHelper.getModelContext(rootCase, task, result); + List deltasToMerge = new ArrayList<>(); + + for (CaseType subcase : subcases) { + if (!CaseTypeUtil.isClosed(subcase)) { + throw new IllegalStateException("Child case " + subcase + " is not in CLOSED state; its state is " + subcase.getState()); + } + ObjectTreeDeltas deltas = pcpGeneralHelper.retrieveResultingDeltas(subcase); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Child case {} has {} resulting deltas", subcase, deltas != null ? deltas.getDeltaList().size() : 0); + } + if (deltas != null) { + ObjectDelta focusChange = deltas.getFocusChange(); + if (focusChange != null) { + approvalMetadataHelper.addAssignmentApprovalMetadata(focusChange, subcase, task, result); + } + if (focusChange != null && focusChange.isAdd()) { + deltasToMerge.add(0, deltas); // "add" must go first + } else { + deltasToMerge.add(deltas); + } + } + } + mergeDeltasToModelContext(rootContext, deltasToMerge); + return rootContext; + } + + void mergeDeltasToModelContext(LensContext rootContext, List deltasToMerge) + throws SchemaException { + for (ObjectTreeDeltas deltaToMerge : deltasToMerge) { + LensFocusContext focusContext = rootContext.getFocusContext(); + ObjectDelta focusDelta = deltaToMerge.getFocusChange(); + if (focusDelta != null) { + LOGGER.trace("Adding delta to root model context; delta = {}", focusDelta.debugDumpLazily()); + if (focusContext.getPrimaryDelta() != null && !focusContext.getPrimaryDelta().isEmpty()) { + //noinspection unchecked + focusContext.addPrimaryDelta(focusDelta); + } else { + //noinspection unchecked + focusContext.setPrimaryDelta(focusDelta); + } + } + //noinspection unchecked + Set>> entries = deltaToMerge.getProjectionChangeMapEntries(); + for (Map.Entry> entry : entries) { + LOGGER.trace("Adding projection delta to root model context; rsd = {}, delta = {}", entry.getKey(), + entry.getValue().debugDumpLazily()); + ModelProjectionContext projectionContext = rootContext.findProjectionContext(entry.getKey()); + if (projectionContext == null) { + // TODO more liberal treatment? + throw new IllegalStateException("No projection context for " + entry.getKey()); + } + if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { + projectionContext.addPrimaryDelta(entry.getValue()); + } else { + projectionContext.setPrimaryDelta(entry.getValue()); + } + } + } + } +} diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java index 3b3eb5b4599..3a6a17d7427 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/ChangeProcessor.java @@ -82,7 +82,8 @@ HookOperationMode processModelInvocation(@NotNull ModelInvocationContext ctx, * @throws SchemaException */ void onProcessEnd(EngineInvocationContext ctx, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException; + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException, + ExpressionEvaluationException, ConfigurationException, CommunicationException; /** * Prepares a process instance-related audit record. diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java index f041c4f697e..17732b29c4a 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/processors/primary/PrimaryChangeProcessor.java @@ -27,9 +27,6 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CaseTypeUtil; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.task.api.TaskExecutionStatus; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.QNameUtil; @@ -41,7 +38,6 @@ import com.evolveum.midpoint.wf.impl.engine.EngineInvocationContext; import com.evolveum.midpoint.wf.impl.engine.WorkflowEngine; import com.evolveum.midpoint.wf.impl.engine.helpers.AuditHelper; -import com.evolveum.midpoint.wf.impl.execution.CaseOperationExecutionTaskHandler; import com.evolveum.midpoint.wf.impl.execution.ExecutionHelper; import com.evolveum.midpoint.wf.impl.processes.common.StageComputeHelper; import com.evolveum.midpoint.wf.impl.processors.*; @@ -323,7 +319,7 @@ private HookOperationMode executeStartInstructions(List ins if (case0 != null) { if (ModelExecuteOptions.isExecuteImmediatelyAfterApproval(ctx.modelContext.getOptions())) { - submitExecutionTask(case0, false, result); + executionHelper.submitExecutionTask(case0, false, result); } else { executionHelper.closeCaseInRepository(case0, result); } @@ -422,7 +418,7 @@ private LensContext contextCopyWithNoDelta(ModelContext context) { */ @Override public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) - throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException { + throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException, PreconditionViolationException, ExpressionEvaluationException, ConfigurationException, CommunicationException { CaseType currentCase = ctx.getCurrentCase(); ObjectTreeDeltas deltas = prepareDeltaOut(currentCase); @@ -463,7 +459,7 @@ public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) } else { waiting = false; } - submitExecutionTask(currentCase, waiting, result); + executionHelper.submitExecutionTask(currentCase, waiting, result); } else { LOGGER.debug("Case {} is rejected (with immediate execution) -- nothing to do here", currentCase); executionHelper.closeCaseInRepository(currentCase, result); @@ -476,7 +472,7 @@ public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) List subcases = miscHelper.getSubcases(rootCase, result); if (subcases.stream().allMatch(CaseTypeUtil::isClosed)) { LOGGER.debug("All subcases of {} are closed, so let's execute the deltas", rootCase); - submitExecutionTask(rootCase, false, result); + executionHelper.submitExecutionTaskIfNeeded(rootCase, subcases, ctx.getTask(), result); } else { LOGGER.debug("Some subcases of {} are not closed yet. Delta execution is therefore postponed.", rootCase); for (CaseType subcase : subcases) { @@ -488,30 +484,6 @@ public void onProcessEnd(EngineInvocationContext ctx, OperationResult result) } } - private void submitExecutionTask(CaseType aCase, boolean waiting, OperationResult result) - throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException { - - // We must do this before the task is started, because as part of task completion we set state to CLOSED. - // So if we set state to EXECUTING after the task is started, the case might be already closed at that point. - // (If task is fast enough.) - executionHelper.setCaseStateInRepository(aCase, SchemaConstants.CASE_STATE_EXECUTING, result); - - Task task = taskManager.createTaskInstance("execute"); - task.setName("Execution of " + aCase.getName().getOrig()); - task.setOwner(getExecutionTaskOwner(result)); - task.setObjectRef(ObjectTypeUtil.createObjectRef(aCase, prismContext)); - task.setHandlerUri(CaseOperationExecutionTaskHandler.HANDLER_URI); - if (waiting) { - task.setInitialExecutionStatus(TaskExecutionStatus.WAITING); - } - executionHelper.setExecutionConstraints(task, aCase, result); - taskManager.switchToBackground(task, result); - } - - private PrismObject getExecutionTaskOwner(OperationResult result) throws SchemaException, ObjectNotFoundException { - return repositoryService.getObject(UserType.class, SystemObjectsType.USER_ADMINISTRATOR.value(), null, result); - } - private ObjectTreeDeltas prepareDeltaOut(CaseType aCase) throws SchemaException { ObjectTreeDeltas deltaIn = generalHelper.retrieveDeltasToApprove(aCase); if (ApprovalUtils.isApprovedFromUri(aCase.getOutcome())) { diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java index 49a0d5d4616..359389a2a65 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java @@ -31,6 +31,7 @@ import com.evolveum.midpoint.test.AbstractIntegrationTest; import com.evolveum.midpoint.test.Checker; import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.test.asserter.CaseAsserter; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.wf.api.WorkflowManager; import com.evolveum.midpoint.wf.impl.access.WorkItemManager; @@ -53,6 +54,7 @@ import java.util.List; import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.AssertJUnit.*; /** @@ -356,4 +358,13 @@ public RelatedCases find(Task task, OperationResult result) throws SchemaExcepti return this; } } + + /** + * Takes case OID from the operation result (via asynchronous identifier). + */ + protected CaseAsserter assertCase(OperationResult result, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + String caseOid = OperationResult.referenceToCaseOid(result.findAsynchronousOperationReference()); + assertThat(caseOid).as("No background case OID").isNotNull(); + return assertCase(caseOid, message); + } } diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestMiscellaneous.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestMiscellaneous.java index 7b98a4328e3..b316cd19942 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestMiscellaneous.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestMiscellaneous.java @@ -15,6 +15,9 @@ import java.util.Collections; import java.util.List; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.test.TestResource; + import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.Test; @@ -40,36 +43,26 @@ public class TestMiscellaneous extends AbstractWfTestPolicy { private static final File TEST_RESOURCE_DIR = new File("src/test/resources/miscellaneous"); - private static final File ROLE_SAILOR_FILE = new File(TEST_RESOURCE_DIR, "role-sailor.xml"); - private static final String ROLE_SAILOR_OID = "3ccc0a00-6a3b-4ae0-94a3-d45fc457f63f"; - - private static final File ROLE_CAPTAIN_FILE = new File(TEST_RESOURCE_DIR, "role-captain.xml"); - private static final String ROLE_CAPTAIN_OID = "15a99cf1-5886-44d4-8aaf-7e1f46ccec36"; - - private static final File USER_SCOTT_FILE = new File(TEST_RESOURCE_DIR, "user-scott.xml"); - private static final String USER_SCOTT_OID = "929c49ed-0100-4068-b8e4-137bd8ebd6b2"; - - private static final File METAROLE_PRIZE_FILE = new File(TEST_RESOURCE_DIR, "metarole-prize.xml"); - - private static final File METAROLE_APPROVE_UNASSIGN_FILE = new File(TEST_RESOURCE_DIR, "metarole-approve-unassign.xml"); + private static final TestResource ROLE_SAILOR = new TestResource<>(TEST_RESOURCE_DIR, "role-sailor.xml", "3ccc0a00-6a3b-4ae0-94a3-d45fc457f63f"); + private static final TestResource ROLE_CAPTAIN = new TestResource<>(TEST_RESOURCE_DIR, "role-captain.xml", "15a99cf1-5886-44d4-8aaf-7e1f46ccec36"); + private static final TestResource USER_SCOTT = new TestResource<>(TEST_RESOURCE_DIR, "user-scott.xml", "929c49ed-0100-4068-b8e4-137bd8ebd6b2"); - private static final File ROLE_GOLD_FILE = new File(TEST_RESOURCE_DIR, "role-gold.xml"); - private static final String ROLE_GOLD_OID = "0b3ad53e-7c1d-41d0-a447-ce94cd25c46a"; + private static final TestResource METAROLE_PRIZE = new TestResource<>(TEST_RESOURCE_DIR, "metarole-prize.xml", "2330f9df-83bc-4270-86fc-27fca2b616a7"); + private static final TestResource METAROLE_APPROVE_UNASSIGN = new TestResource<>(TEST_RESOURCE_DIR, "metarole-approve-unassign.xml", "e5144353-c39d-445c-bf15-c4b80ce75918"); - private static final File ROLE_SILVER_FILE = new File(TEST_RESOURCE_DIR, "role-silver.xml"); - private static final String ROLE_SILVER_OID = "ee5206f8-930a-4c85-bfee-c16e4462df23"; + private static final TestResource ROLE_GOLD = new TestResource<>(TEST_RESOURCE_DIR, "role-gold.xml", "0b3ad53e-7c1d-41d0-a447-ce94cd25c46a"); + private static final TestResource ROLE_SILVER = new TestResource<>(TEST_RESOURCE_DIR, "role-silver.xml", "ee5206f8-930a-4c85-bfee-c16e4462df23"); + private static final TestResource ROLE_BRONZE = new TestResource<>(TEST_RESOURCE_DIR, "role-bronze.xml", "f16f4dd7-2830-4d0a-b6ed-9fbf253dbaf3"); - private static final File ROLE_BRONZE_FILE = new File(TEST_RESOURCE_DIR, "role-bronze.xml"); - //private static final String ROLE_BRONZE_OID = "f16f4dd7-2830-4d0a-b6ed-9fbf253dbaf3"; + private static final TestResource TEMPLATE_ASSIGNING_CAPTAIN = new TestResource<>(TEST_RESOURCE_DIR, "template-assigning-captain.xml", "18ac3da2-f2fa-496a-8e54-789a090ff492"); + private static final TestResource TEMPLATE_ASSIGNING_CAPTAIN_AFTER = new TestResource<>(TEST_RESOURCE_DIR, "template-assigning-captain-after.xml", "ace5d8f0-f54b-4f1b-92c0-8fa104a8fe84"); + private static final TestResource ROLE_ASSIGNING_CAPTAIN = new TestResource<>(TEST_RESOURCE_DIR, "role-assigning-captain.xml", "4bdd7ccc-8c52-41ff-a975-0313ec788507"); - private static final File TEMPLATE_ASSIGNING_CAPTAIN_FILE = new File(TEST_RESOURCE_DIR, "template-assigning-captain.xml"); - private static final String TEMPLATE_ASSIGNING_CAPTAIN_OID = "18ac3da2-f2fa-496a-8e54-789a090ff492"; - - private static final File TEMPLATE_ASSIGNING_CAPTAIN_AFTER_FILE = new File(TEST_RESOURCE_DIR, "template-assigning-captain-after.xml"); - private static final String TEMPLATE_ASSIGNING_CAPTAIN_AFTER_OID = "ace5d8f0-f54b-4f1b-92c0-8fa104a8fe84"; - - private static final File ROLE_ASSIGNING_CAPTAIN_FILE = new File(TEST_RESOURCE_DIR, "role-assigning-captain.xml"); - private static final String ROLE_ASSIGNING_CAPTAIN_OID = "4bdd7ccc-8c52-41ff-a975-0313ec788507"; + private static final TestResource USER_SCROOGE = new TestResource<>(TEST_RESOURCE_DIR, "user-scrooge.xml", "edf53304-2da0-4a7c-82b4-74fe35dcbc6e"); + private static final TestResource USER_GIZMODUCK = new TestResource<>(TEST_RESOURCE_DIR, "user-gizmoduck.xml", "6d0a7fce-b698-4f1d-95ce-14246452add5"); + private static final TestResource USER_LAUNCHPAD = new TestResource<>(TEST_RESOURCE_DIR, "user-launchpad.xml", "00880478-d006-4fc8-9d3a-87b5ec546c40"); + private static final TestResource ROLE_VAULT_ACCESS = new TestResource<>(TEST_RESOURCE_DIR, "role-vault-access.xml", "f6f95936-8714-4c7d-abdf-6cd3e6d2d6cc"); + private static final TestResource ROLE_ACCOUNTANT = new TestResource<>(TEST_RESOURCE_DIR, "role-accountant.xml", "5653fc70-3007-4f62-82dd-a36e0673505b"); @Override protected PrismObject getDefaultActor() { @@ -80,20 +73,26 @@ protected PrismObject getDefaultActor() { public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); - repoAddObjectFromFile(ROLE_SAILOR_FILE, initResult); - repoAddObjectFromFile(ROLE_CAPTAIN_FILE, initResult); + repoAdd(ROLE_SAILOR, initResult); + repoAdd(ROLE_CAPTAIN, initResult); + + repoAdd(METAROLE_PRIZE, initResult); + repoAdd(METAROLE_APPROVE_UNASSIGN, initResult); + repoAdd(ROLE_GOLD, initResult); + repoAdd(ROLE_SILVER, initResult); + repoAdd(ROLE_BRONZE, initResult); - repoAddObjectFromFile(METAROLE_PRIZE_FILE, initResult); - repoAddObjectFromFile(METAROLE_APPROVE_UNASSIGN_FILE, initResult); - repoAddObjectFromFile(ROLE_GOLD_FILE, initResult); - repoAddObjectFromFile(ROLE_SILVER_FILE, initResult); - repoAddObjectFromFile(ROLE_BRONZE_FILE, initResult); + addAndRecompute(USER_SCOTT, initTask, initResult); - addAndRecompute(USER_SCOTT_FILE, initTask, initResult); + repoAdd(TEMPLATE_ASSIGNING_CAPTAIN, initResult); + repoAdd(TEMPLATE_ASSIGNING_CAPTAIN_AFTER, initResult); + repoAdd(ROLE_ASSIGNING_CAPTAIN, initResult); - repoAddObjectFromFile(TEMPLATE_ASSIGNING_CAPTAIN_FILE, initResult); - repoAddObjectFromFile(TEMPLATE_ASSIGNING_CAPTAIN_AFTER_FILE, initResult); - repoAddObjectFromFile(ROLE_ASSIGNING_CAPTAIN_FILE, initResult); + repoAdd(ROLE_VAULT_ACCESS, initResult); + repoAdd(ROLE_ACCOUNTANT, initResult); + addAndRecompute(USER_SCROOGE, initTask, initResult); + addAndRecompute(USER_GIZMODUCK, initTask, initResult); + addAndRecompute(USER_LAUNCHPAD, initTask, initResult); } @Test @@ -111,11 +110,11 @@ public void test100RequesterComment() throws Exception { final String REQUESTER_COMMENT = "req.comment"; businessContext.setComment(REQUESTER_COMMENT); - ObjectDelta userDelta = createAssignmentUserDelta(userJackOid, ROLE_SAILOR_OID, RoleType.COMPLEX_TYPE, null, null, null, true); + ObjectDelta userDelta = createAssignmentUserDelta(userJackOid, ROLE_SAILOR.oid, RoleType.COMPLEX_TYPE, null, null, null, true); Collection> deltas = MiscSchemaUtil.createCollection(userDelta); modelService.executeChanges(deltas, ModelExecuteOptions.createRequestBusinessContext(businessContext), task, result); - assertNotAssignedRole(userJackOid, ROLE_SAILOR_OID, result); + assertNotAssignedRole(userJackOid, ROLE_SAILOR.oid, result); CaseWorkItemType workItem = getWorkItem(task, result); display("Work item", workItem); @@ -139,7 +138,7 @@ public void test100RequesterComment() throws Exception { display("Event 2", event2); assertNotNull("Original assignee is null", event2.getOriginalAssigneeRef()); - assertEquals("Wrong original assignee OID", USER_SCOTT_OID, event2.getOriginalAssigneeRef().getOid()); + assertEquals("Wrong original assignee OID", USER_SCOTT.oid, event2.getOriginalAssigneeRef().getOid()); displayDumpable("audit", dummyAuditService); List records = dummyAuditService.getRecordsOfType(AuditEventType.WORKFLOW_PROCESS_INSTANCE); @@ -153,7 +152,7 @@ public void test100RequesterComment() throws Exception { CaseType parentCase = getCase(aCase.getParentRef().getOid()); waitForCaseClose(parentCase); - AssignmentType assignment = assertAssignedRole(userJackOid, ROLE_SAILOR_OID, result); + AssignmentType assignment = assertAssignedRole(userJackOid, ROLE_SAILOR.oid, result); display("assignment after creation", assignment); MetadataType metadata = assignment.getMetadata(); assertNotNull("Null request timestamp in metadata", metadata.getRequestTimestamp()); @@ -176,13 +175,13 @@ public void test105RequesterCommentImmediate() throws Exception { final String REQUESTER_COMMENT = "req.comment"; businessContext.setComment(REQUESTER_COMMENT); - ObjectDelta userDelta = createAssignmentUserDelta(userJackOid, ROLE_CAPTAIN_OID, RoleType.COMPLEX_TYPE, null, null, null, true); + ObjectDelta userDelta = createAssignmentUserDelta(userJackOid, ROLE_CAPTAIN.oid, RoleType.COMPLEX_TYPE, null, null, null, true); Collection> deltas = MiscSchemaUtil.createCollection(userDelta); ModelExecuteOptions options = ModelExecuteOptions.createRequestBusinessContext(businessContext); options.setExecuteImmediatelyAfterApproval(true); modelService.executeChanges(deltas, options, task, result); - assertNotAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertNotAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); CaseWorkItemType workItem = getWorkItem(task, result); display("Work item", workItem); @@ -206,7 +205,7 @@ public void test105RequesterCommentImmediate() throws Exception { display("Event 2", event2); assertNotNull("Original assignee is null", event2.getOriginalAssigneeRef()); - assertEquals("Wrong original assignee OID", USER_SCOTT_OID, event2.getOriginalAssigneeRef().getOid()); + assertEquals("Wrong original assignee OID", USER_SCOTT.oid, event2.getOriginalAssigneeRef().getOid()); displayDumpable("audit", dummyAuditService); List records = dummyAuditService.getRecordsOfType(AuditEventType.WORKFLOW_PROCESS_INSTANCE); @@ -220,7 +219,7 @@ public void test105RequesterCommentImmediate() throws Exception { CaseType parentCase = getCase(aCase.getParentRef().getOid()); waitForCaseClose(parentCase); - AssignmentType assignment = assertAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + AssignmentType assignment = assertAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); display("assignment after creation", assignment); MetadataType metadata = assignment.getMetadata(); assertNotNull("Null request timestamp in metadata", metadata.getRequestTimestamp()); @@ -239,18 +238,18 @@ public void test110RequestPrunedRole() throws Exception { ModelExecuteOptions options = ModelExecuteOptions .createPartialProcessing(new PartialProcessingOptionsType().approvals(PartialProcessingTypeType.SKIP)); - assignRole(userJackOid, ROLE_GOLD_OID, options, task, result); - assertAssignedRole(getUser(userJackOid), ROLE_GOLD_OID); + assignRole(userJackOid, ROLE_GOLD.oid, options, task, result); + assertAssignedRole(getUser(userJackOid), ROLE_GOLD.oid); // WHEN - assignRole(userJackOid, ROLE_SILVER_OID, task, result); + assignRole(userJackOid, ROLE_SILVER.oid, task, result); // THEN result.computeStatus(); TestUtil.assertInProgress("Operation NOT in progress", result); - assertNotAssignedRole(userJackOid, ROLE_SILVER_OID, result); + assertNotAssignedRole(userJackOid, ROLE_SILVER.oid, result); // complete the work item related to assigning role silver CaseWorkItemType workItem = getWorkItem(task, result); @@ -263,7 +262,7 @@ public void test110RequestPrunedRole() throws Exception { CaseType rootCase = getCase(aCase.getParentRef().getOid()); waitForCaseClose(rootCase); - assertNotAssignedRole(userJackOid, ROLE_GOLD_OID, result); // should be pruned without approval + assertNotAssignedRole(userJackOid, ROLE_GOLD.oid, result); // should be pruned without approval } @Test @@ -274,9 +273,9 @@ public void test200GetRoleByTemplate() throws Exception { OperationResult result = getTestOperationResult(); // GIVEN - setDefaultUserTemplate(TEMPLATE_ASSIGNING_CAPTAIN_OID); + setDefaultUserTemplate(TEMPLATE_ASSIGNING_CAPTAIN.oid); unassignAllRoles(userJackOid); - assertNotAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertNotAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); // WHEN // some innocent change @@ -286,7 +285,7 @@ public void test200GetRoleByTemplate() throws Exception { result.computeStatus(); TestUtil.assertSuccess(result); - assertAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); } @Test @@ -297,9 +296,9 @@ public void test210GetRoleByTemplateAfterAssignments() throws Exception { OperationResult result = getTestOperationResult(); // GIVEN - setDefaultUserTemplate(TEMPLATE_ASSIGNING_CAPTAIN_AFTER_OID); + setDefaultUserTemplate(TEMPLATE_ASSIGNING_CAPTAIN_AFTER.oid); unassignAllRoles(userJackOid); - assertNotAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertNotAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); // WHEN // some innocent change @@ -310,7 +309,7 @@ public void test210GetRoleByTemplateAfterAssignments() throws Exception { result.computeStatus(); TestUtil.assertSuccess(result); - assertAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); } @Test @@ -323,16 +322,16 @@ public void test220GetRoleByFocusMappings() throws Exception { // GIVEN setDefaultUserTemplate(null); unassignAllRoles(userJackOid); - assertNotAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertNotAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); // WHEN - assignRole(userJackOid, ROLE_ASSIGNING_CAPTAIN_OID, task, result); + assignRole(userJackOid, ROLE_ASSIGNING_CAPTAIN.oid, task, result); // THEN result.computeStatus(); TestUtil.assertSuccess(result); - assertAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); } @Test @@ -345,13 +344,13 @@ public void test250SkippingApprovals() throws Exception { // GIVEN setDefaultUserTemplate(null); unassignAllRoles(userJackOid); - assertNotAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertNotAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); // WHEN ObjectDelta delta = prismContext.deltaFor(UserType.class) .item(UserType.F_ASSIGNMENT) - .add(ObjectTypeUtil.createAssignmentTo(ROLE_CAPTAIN_OID, ObjectTypes.ROLE, prismContext)) + .add(ObjectTypeUtil.createAssignmentTo(ROLE_CAPTAIN.oid, ObjectTypes.ROLE, prismContext)) .asObjectDelta(userJackOid); ModelExecuteOptions options = ModelExecuteOptions.createPartialProcessing( new PartialProcessingOptionsType().approvals(PartialProcessingTypeType.SKIP)); @@ -361,7 +360,7 @@ public void test250SkippingApprovals() throws Exception { result.computeStatus(); TestUtil.assertSuccess(result); - assertAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); } /** @@ -376,24 +375,23 @@ public void test300DeleteRequestCase() throws Exception { OperationResult result = getTestOperationResult(); unassignAllRoles(userJackOid); - when(); - // @formatter:off ObjectDelta delta = - prismContext.deltaFor(UserType.class) + deltaFor(UserType.class) .item(UserType.F_ASSIGNMENT) - .add(ObjectTypeUtil.createAssignmentTo(ROLE_CAPTAIN_OID, ObjectTypes.ROLE, prismContext)) + .add(ObjectTypeUtil.createAssignmentTo(ROLE_CAPTAIN.oid, ObjectTypes.ROLE, prismContext)) .asObjectDelta(userJackOid); // @formatter:on executeChanges(delta, null, task, result); - - assertNotAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertNotAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); RelatedCases relatedCases = new RelatedCases().find(task, result); CaseType approvalCase = relatedCases.getApprovalCase(); CaseType requestCase = relatedCases.getRequestCase(); + when(); + deleteObject(CaseType.class, requestCase.getOid(), task, result); then(); @@ -414,29 +412,189 @@ public void test310DeleteRequestCaseRaw() throws Exception { OperationResult result = getTestOperationResult(); unassignAllRoles(userJackOid); - when(); - // @formatter:off ObjectDelta delta = - prismContext.deltaFor(UserType.class) + deltaFor(UserType.class) .item(UserType.F_ASSIGNMENT) - .add(ObjectTypeUtil.createAssignmentTo(ROLE_CAPTAIN_OID, ObjectTypes.ROLE, prismContext)) + .add(ObjectTypeUtil.createAssignmentTo(ROLE_CAPTAIN.oid, ObjectTypes.ROLE, prismContext)) .asObjectDelta(userJackOid); // @formatter:on executeChanges(delta, null, task, result); - - assertNotAssignedRole(userJackOid, ROLE_CAPTAIN_OID, result); + assertNotAssignedRole(userJackOid, ROLE_CAPTAIN.oid, result); RelatedCases relatedCases = new RelatedCases().find(task, result); CaseType approvalCase = relatedCases.getApprovalCase(); CaseType requestCase = relatedCases.getRequestCase(); + when(); + deleteObjectRaw(CaseType.class, requestCase.getOid(), task, result); then(); assertObjectDoesntExist(CaseType.class, requestCase.getOid()); assertObjectExists(CaseType.class, approvalCase.getOid()); + + // just to clean up before downstream tests + deleteObjectRaw(CaseType.class, approvalCase.getOid(), task, result); } + + @Test + public void test350ApproveAsAttorneyAdministrator() throws Exception { + given(); + + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + // @formatter:off + ObjectDelta delta = + deltaFor(UserType.class) + .item(UserType.F_ASSIGNMENT) + .add(ObjectTypeUtil.createAssignmentTo(ROLE_VAULT_ACCESS.oid, ObjectTypes.ROLE, prismContext)) + .asObjectDelta(USER_LAUNCHPAD.oid); + // @formatter:on + + executeChanges(delta, null, task, result); + assertNotAssignedRole(USER_LAUNCHPAD.oid, ROLE_VAULT_ACCESS.oid, result); + + CaseWorkItemType workItem = getWorkItem(task, result); + + when(); + + modelInteractionService.runUnderPowerOfAttorneyChecked(() -> { + AbstractWorkItemOutputType output = new AbstractWorkItemOutputType(prismContext) + .outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT); + workflowService.completeWorkItem(CaseWorkItemUtil.getId(workItem), output, task, result); + return null; + }, USER_SCROOGE.object, task, result); + + then(); + + // @formatter:off + assertNotAssignedRole(USER_LAUNCHPAD.oid, ROLE_VAULT_ACCESS.oid, result); + CaseType approvalCase = assertCase(result, "after") + .display() + .assertOperationRequestArchetype() + .assertObjectRef(USER_LAUNCHPAD.oid, UserType.COMPLEX_TYPE) + .assertClosed() + .subcases() + .assertSubcases(1) + .single() + .getObject().asObjectable(); + + assertCase(approvalCase, "after") + .display() + .assertNameOrig("Assigning role \"vault-access\" to user \"launchpad\"") + .assertApprovalCaseArchetype() + .assertObjectRef(USER_LAUNCHPAD.oid, UserType.COMPLEX_TYPE) + .assertTargetRef(ROLE_VAULT_ACCESS.oid, RoleType.COMPLEX_TYPE) + .assertClosed() + .assertRejected() + .assertStageNumber(1) + .events() + .assertEvents(2) + .ofType(CaseCreationEventType.class) + .assertInitiatorRef(USER_ADMINISTRATOR_OID, UserType.COMPLEX_TYPE) + .end() + .ofType(WorkItemCompletionEventType.class) + .assertOriginalAssigneeRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) + .assertInitiatorRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) + .assertAttorneyRef(USER_ADMINISTRATOR_OID, UserType.COMPLEX_TYPE) + .end() + .end() + .workItems() + .assertWorkItems(1) + .single() + .assertNameOrig("Assigning role \"vault-access\" to user \"launchpad\"") + .assertStageNumber(1) + .assertOriginalAssigneeRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) + .assertPerformerRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) // we should perhaps list attorney here as well + .assertClosed() + .assertRejected(); + // @formatter:on + } + + @Test + public void test360ApproveAsAttorneyGizmoduck() throws Exception { + given(); + + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + // @formatter:off + ObjectDelta delta = + deltaFor(UserType.class) + .item(UserType.F_ASSIGNMENT) + .add(ObjectTypeUtil.createAssignmentTo(ROLE_VAULT_ACCESS.oid, ObjectTypes.ROLE, prismContext)) + .asObjectDelta(USER_LAUNCHPAD.oid); + // @formatter:on + + executeChanges(delta, null, task, result); + assertNotAssignedRole(USER_LAUNCHPAD.oid, ROLE_VAULT_ACCESS.oid, result); + + CaseWorkItemType workItem = getWorkItem(task, result); + + when(); + + login(USER_GIZMODUCK.object); + + modelInteractionService.runUnderPowerOfAttorneyChecked(() -> { + AbstractWorkItemOutputType output = new AbstractWorkItemOutputType(prismContext) + .outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT); + workflowService.completeWorkItem(CaseWorkItemUtil.getId(workItem), output, task, result); + return null; + }, USER_SCROOGE.object, task, result); + + then(); + + login(userAdministrator); // to avoid problems because of insufficient privileges + + // @formatter:off + assertNotAssignedRole(USER_LAUNCHPAD.oid, ROLE_VAULT_ACCESS.oid, result); + CaseType approvalCase = assertCase(result, "after") + .display() + .assertOperationRequestArchetype() + .assertObjectRef(USER_LAUNCHPAD.oid, UserType.COMPLEX_TYPE) + .assertClosed() + .subcases() + .assertSubcases(1) + .single() + .getObject().asObjectable(); + + assertCase(approvalCase, "after") + .display() + .assertNameOrig("Assigning role \"vault-access\" to user \"launchpad\"") + .assertApprovalCaseArchetype() + .assertObjectRef(USER_LAUNCHPAD.oid, UserType.COMPLEX_TYPE) + .assertTargetRef(ROLE_VAULT_ACCESS.oid, RoleType.COMPLEX_TYPE) + .assertClosed() + .assertRejected() + .assertStageNumber(1) + .events() + .assertEvents(2) + .ofType(CaseCreationEventType.class) + .assertInitiatorRef(USER_ADMINISTRATOR_OID, UserType.COMPLEX_TYPE) + .end() + .ofType(WorkItemCompletionEventType.class) + .assertOriginalAssigneeRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) + .assertInitiatorRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) + .assertAttorneyRef(USER_GIZMODUCK.oid, UserType.COMPLEX_TYPE) + .end() + .end() + .workItems() + .assertWorkItems(1) + .single() + .assertNameOrig("Assigning role \"vault-access\" to user \"launchpad\"") + .assertStageNumber(1) + .assertOriginalAssigneeRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) + .assertPerformerRef(USER_SCROOGE.oid, UserType.COMPLEX_TYPE) // we should perhaps list attorney here as well + .assertClosed() + .assertRejected(); + + // @formatter:on + } + } diff --git a/model/workflow-impl/src/test/resources/miscellaneous/role-accountant.xml b/model/workflow-impl/src/test/resources/miscellaneous/role-accountant.xml new file mode 100644 index 00000000000..9b9a190ff4c --- /dev/null +++ b/model/workflow-impl/src/test/resources/miscellaneous/role-accountant.xml @@ -0,0 +1,24 @@ + + + + accountant + + attorney-scrooge-workitems + http://midpoint.evolveum.com/xml/ns/public/security/authorization-model-3#attorney + + UserType + + + name + scrooge + + + + + diff --git a/model/workflow-impl/src/test/resources/miscellaneous/role-vault-access.xml b/model/workflow-impl/src/test/resources/miscellaneous/role-vault-access.xml new file mode 100644 index 00000000000..7c8d65b752f --- /dev/null +++ b/model/workflow-impl/src/test/resources/miscellaneous/role-vault-access.xml @@ -0,0 +1,11 @@ + + + + vault-access + diff --git a/model/workflow-impl/src/test/resources/miscellaneous/user-gizmoduck.xml b/model/workflow-impl/src/test/resources/miscellaneous/user-gizmoduck.xml new file mode 100644 index 00000000000..386e56d7147 --- /dev/null +++ b/model/workflow-impl/src/test/resources/miscellaneous/user-gizmoduck.xml @@ -0,0 +1,16 @@ + + + + + gizmoduck + + + + Fenton Crackshell + diff --git a/model/workflow-impl/src/test/resources/miscellaneous/user-launchpad.xml b/model/workflow-impl/src/test/resources/miscellaneous/user-launchpad.xml new file mode 100644 index 00000000000..50b829f28da --- /dev/null +++ b/model/workflow-impl/src/test/resources/miscellaneous/user-launchpad.xml @@ -0,0 +1,12 @@ + + + + launchpad + Launchpad McQuack + diff --git a/model/workflow-impl/src/test/resources/miscellaneous/user-scrooge.xml b/model/workflow-impl/src/test/resources/miscellaneous/user-scrooge.xml new file mode 100644 index 00000000000..c7c6f514d94 --- /dev/null +++ b/model/workflow-impl/src/test/resources/miscellaneous/user-scrooge.xml @@ -0,0 +1,16 @@ + + + + + scrooge + + + + Scrooge McDuck + diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index 48851114d52..8a7b1a268c4 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -431,7 +431,9 @@ protected PrismObject repoAddObjectFromFile(File file, protected PrismObject repoAdd(TestResource resource, OperationResult parentResult) throws SchemaException, ObjectAlreadyExistsException, IOException, EncryptionException { - return repoAddObjectFromFile(resource.file, parentResult); + PrismObject object = repoAddObjectFromFile(resource.file, parentResult); + resource.object = object; + return object; } protected PrismObject repoAddObjectFromFile( @@ -2729,6 +2731,7 @@ protected void assertRelationDef(List relations, QName q protected void initializeAsserter(AbstractAsserter asserter) { asserter.setPrismContext(prismContext); asserter.setObjectResolver(repoSimpleObjectResolver); + asserter.setRepositoryService(repositoryService); asserter.setProtector(protector); asserter.setClock(clock); } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyTestResource.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyTestResource.java index d81996abcf5..43bd17ce0ed 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyTestResource.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/DummyTestResource.java @@ -8,6 +8,7 @@ package com.evolveum.midpoint.test; import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import java.io.File; @@ -15,7 +16,7 @@ * Representation of Dummy Resource in tests. */ @Experimental -public class DummyTestResource extends TestResource { +public class DummyTestResource extends TestResource { public final String name; public DummyResourceContoller controller; diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java index aaf0a754e97..27060acd216 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/TestResource.java @@ -7,7 +7,9 @@ package com.evolveum.midpoint.test; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import java.io.File; @@ -15,13 +17,18 @@ * Representation of any prism object in tests. */ @Experimental -public class TestResource { +public class TestResource { public final File file; public final String oid; + public PrismObject object; public TestResource(File dir, String fileName, String oid) { this.file = new File(dir, fileName); this.oid = oid; } + + public String getNameOrig() { + return object.getName().getOrig(); + } } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java index 058f0a86b4e..df6386ed07f 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java @@ -6,6 +6,8 @@ */ package com.evolveum.midpoint.test.asserter; +import com.evolveum.midpoint.repo.api.RepositoryService; + import org.testng.AssertJUnit; import com.evolveum.midpoint.common.Clock; @@ -28,6 +30,7 @@ public abstract class AbstractAsserter { private RA returnAsserter; private PrismContext prismContext; private SimpleObjectResolver objectResolver; + private RepositoryService repositoryService; private Protector protector; private Clock clock; @@ -62,6 +65,14 @@ public void setObjectResolver(SimpleObjectResolver objectResolver) { this.objectResolver = objectResolver; } + public RepositoryService getRepositoryService() { + return repositoryService; + } + + public void setRepositoryService(RepositoryService repositoryService) { + this.repositoryService = repositoryService; + } + protected Protector getProtector() { return protector; } @@ -118,6 +129,7 @@ protected PrismObject resolveObject(Class type, Str protected void copySetupTo(AbstractAsserter other) { other.setPrismContext(this.getPrismContext()); other.setObjectResolver(this.getObjectResolver()); + other.setRepositoryService(this.getRepositoryService()); other.setProtector(this.getProtector()); other.setClock(this.getClock()); } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseAsserter.java index f42b8624b39..518efb38c1d 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseAsserter.java @@ -7,13 +7,25 @@ package com.evolveum.midpoint.test.asserter; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.schema.SearchResultList; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.test.asserter.prism.PolyStringAsserter; import com.evolveum.midpoint.test.asserter.prism.PrismObjectAsserter; +import com.evolveum.midpoint.test.util.MidPointAsserts; +import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.SystemObjectsType; + +import javax.xml.namespace.QName; + +import static com.evolveum.midpoint.prism.PrismObject.asObjectableList; +import static org.assertj.core.api.Assertions.assertThat; /** * @author semancik - * */ public class CaseAsserter extends PrismObjectAsserter { @@ -70,6 +82,11 @@ public CaseAsserter assertName(String expectedOrig) { return this; } + @Override + public CaseAsserter assertNameOrig(String expectedOrig) { + return (CaseAsserter) super.assertNameOrig(expectedOrig); + } + @Override public CaseAsserter assertDescription(String expected) { super.assertDescription(expected); @@ -119,6 +136,7 @@ public CaseAsserter display(String message) { @Override public PolyStringAsserter> name() { + //noinspection unchecked return (PolyStringAsserter>)super.name(); } @@ -127,4 +145,92 @@ public CaseAsserter assertNoTrigger() { super.assertNoTrigger(); return this; } + + @Override + public CaseAsserter assertArchetypeRef(String expectedArchetypeOid) { + return (CaseAsserter) super.assertArchetypeRef(expectedArchetypeOid); + } + + @Override + public CaseAsserter assertNoArchetypeRef() { + return (CaseAsserter) super.assertNoArchetypeRef(); + } + + public CaseAsserter assertOperationRequestArchetype() { + return assertArchetypeRef(SystemObjectsType.ARCHETYPE_OPERATION_REQUEST.value()); + } + + public CaseAsserter assertApprovalCaseArchetype() { + return assertArchetypeRef(SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value()); + } + + public CaseAsserter assertClosed() { + return assertState(SchemaConstants.CASE_STATE_CLOSED_QNAME); + } + + private CaseAsserter assertState(QName expected) { + MidPointAsserts.assertUriMatches(getObjectable().getState(), "state", expected); + return this; + } + + public CaseAsserter assertApproved() { + return assertOutcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE); + } + + public CaseAsserter assertRejected() { + return assertOutcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT); + } + + private CaseAsserter assertOutcome(String expected) { + MidPointAsserts.assertUriMatches(getObjectable().getOutcome(), "outcome", expected); + return this; + } + + public SubcasesAsserter subcases() { + OperationResult result = new OperationResult(CaseAsserter.class.getName() + ".subcases"); + ObjectQuery query = getPrismContext().queryFor(CaseType.class) + .item(CaseType.F_PARENT_REF).ref(getOid()) + .build(); + SearchResultList> subcases; + try { + subcases = getRepositoryService().searchObjects(CaseType.class, query, null, result); + } catch (SchemaException e) { + throw new AssertionError(e); + } + SubcasesAsserter asserter = new SubcasesAsserter<>(this, asObjectableList(subcases), getDetails()); + copySetupTo(asserter); + return asserter; + } + + public CaseAsserter assertObjectRef(String oid, QName typeName) { + return assertReference(getObjectable().getObjectRef(), "objectRef", oid, typeName); + } + + public CaseAsserter assertTargetRef(String oid, QName typeName) { + return assertReference(getObjectable().getTargetRef(), "targetRef", oid, typeName); + } + + private CaseAsserter assertReference(ObjectReferenceType ref, String desc, String oid, QName typeName) { + MidPointAsserts.assertThatReferenceMatches(ref, desc, oid, typeName); + return this; + } + + public CaseAsserter assertStageNumber(int expected) { + assertThat(getObjectable().getStageNumber()) + .as("stage number") + .isEqualTo(expected); + return this; + } + + public CaseEventsAsserter events() { + CaseEventsAsserter asserter = new CaseEventsAsserter<>(this, getObjectable().getEvent(), getDetails()); + copySetupTo(asserter); + return asserter; + } + + public CaseWorkItemsAsserter workItems() { + CaseWorkItemsAsserter asserter = new CaseWorkItemsAsserter<>(this, getObjectable().getWorkItem(), getDetails()); + copySetupTo(asserter); + return asserter; + } } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventAsserter.java new file mode 100644 index 00000000000..21f1b38efd1 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventAsserter.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.test.asserter.prism.PrismContainerValueAsserter; +import com.evolveum.midpoint.test.util.MidPointAsserts; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseEventType; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventType; + +import org.jetbrains.annotations.NotNull; + +/** + * Asserts about CaseEventType. + */ +public class CaseEventAsserter extends PrismContainerValueAsserter { + + public CaseEventAsserter(CaseEventType event) { + //noinspection unchecked + super(event.asPrismContainerValue()); + } + + public CaseEventAsserter(CaseEventType event, String details) { + //noinspection unchecked + super(event.asPrismContainerValue(), details); + } + + public CaseEventAsserter(CaseEventType event, RA returnAsserter, String details) { + //noinspection unchecked + super(event.asPrismContainerValue(), returnAsserter, details); + } + + public CaseEventAsserter assertInitiatorRef(String oid, QName typeName) { + MidPointAsserts.assertThatReferenceMatches(getEvent().getInitiatorRef(), "initiatorRef", oid, typeName); + return this; + } + + public CaseEventAsserter assertAttorneyRef(String oid, QName typeName) { + MidPointAsserts.assertThatReferenceMatches(getEvent().getAttorneyRef(), "attorneyRef", oid, typeName); + return this; + } + + public CaseEventAsserter assertOriginalAssigneeRef(String oid, QName typeName) { + CaseEventType event = getEvent(); + assertThat(event).isInstanceOf(WorkItemEventType.class); + ObjectReferenceType originalAssigneeRef = ((WorkItemEventType) event).getOriginalAssigneeRef(); + MidPointAsserts.assertThatReferenceMatches(originalAssigneeRef, "originalAssigneeRef", oid, typeName); + return this; + } + + @NotNull + private CaseEventType getEvent() { + return getPrismValue().asContainerable(); + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventFinder.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventFinder.java new file mode 100644 index 00000000000..4806d297ae6 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventFinder.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import java.util.Objects; + +import org.testng.AssertJUnit; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseEventType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemEventType; + +/** + * + */ +public class CaseEventFinder { + + private final CaseEventsAsserter eventsAsserter; + private Integer stageNumber; + private Long workItemId; + private Class type; + + public CaseEventFinder(CaseEventsAsserter eventsAsserter) { + this.eventsAsserter = eventsAsserter; + } + + public CaseEventFinder stageNumber(Integer stageNumber) { + this.stageNumber = stageNumber; + return this; + } + + public CaseEventFinder workItemId(Long workItemId) { + this.workItemId = workItemId; + return this; + } + + public CaseEventFinder type(Class type) { + this.type = type; + return this; + } + + public CaseEventAsserter> find() { + CaseEventType found = null; + for (CaseEventType event: eventsAsserter.getEvents()) { + if (matches(event)) { + if (found == null) { + found = event; + } else { + fail("Found more than one event that matches search criteria"); + } + } + } + if (found == null) { + throw new AssertionError("Found no event that matches search criteria"); + } else { + return eventsAsserter.forEvent(found); + } + } + + public CaseEventsAsserter assertNone() { + for (CaseEventType event: eventsAsserter.getEvents()) { + if (matches(event)) { + fail("Found event while not expecting it: " + event); + } + } + return eventsAsserter; + } + + public CaseEventsAsserter assertAll() { + for (CaseEventType event: eventsAsserter.getEvents()) { + if (!matches(event)) { + fail("Found event that does not match search criteria: "+event); + } + } + return eventsAsserter; + } + + private boolean matches(CaseEventType event) { + if (stageNumber != null) { + if (!Objects.equals(stageNumber, event.getStageNumber())) { + return false; + } + } + if (workItemId != null) { + if (!(event instanceof WorkItemEventType) || + !Objects.equals(workItemId, ((WorkItemEventType) event).getWorkItemId())) { + return false; + } + } + if (type != null) { + if (!type.isAssignableFrom(event.getClass())) { + return false; + } + } + return true; + } + + protected void fail(String message) { + AssertJUnit.fail(message); + } + +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventsAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventsAsserter.java new file mode 100644 index 00000000000..f8ebed42f92 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseEventsAsserter.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseEventType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import static org.testng.AssertJUnit.assertEquals; + +/** + * Asserts over a set of events. + */ +public class CaseEventsAsserter extends AbstractAsserter> { + + @NotNull private final CaseAsserter caseAsserter; + @NotNull private final List events; + + public CaseEventsAsserter(@NotNull CaseAsserter caseAsserter, @NotNull List events, String details) { + super(caseAsserter, details); + this.caseAsserter = caseAsserter; + this.events = events; + } + + PrismObject getCase() { + return caseAsserter.getObject(); + } + + public @NotNull List getEvents() { + return events; + } + + public CaseEventsAsserter assertEvents(int expected) { + assertEquals("Wrong number of events in " + desc(), expected, getEvents().size()); + return this; + } + + public CaseEventsAsserter assertNone() { + assertEvents(0); + return this; + } + + CaseEventAsserter> forEvent(CaseEventType event) { + CaseEventAsserter> asserter = new CaseEventAsserter<>(event, this, "event in "+ getCase()); + copySetupTo(asserter); + return asserter; + } + + public CaseEventAsserter> single() { + assertEvents(1); + return forEvent(getEvents().get(0)); + } + + @Override + protected String desc() { + return descWithDetails("events in "+ getCase()); + } + + public CaseEventFinder by() { + return new CaseEventFinder<>(this); + } + + public CaseEventAsserter> forStageNumber(Integer stageNumber) { + return by() + .stageNumber(stageNumber) + .find(); + } + + public CaseEventAsserter> forWorkItemId(Long workItemId) { + return by() + .workItemId(workItemId) + .find(); + } + + public CaseEventAsserter> ofType(Class type) { + return by() + .type(type) + .find(); + } + +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseFinder.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseFinder.java new file mode 100644 index 00000000000..dd308bf8305 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseFinder.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import com.evolveum.midpoint.util.QNameUtil; + +import org.testng.AssertJUnit; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; + +import javax.xml.namespace.QName; + +/** + * + */ +public class CaseFinder { + + private final SubcasesAsserter subcasesAsserter; + private String targetOid; + private QName targetType; + + public CaseFinder(SubcasesAsserter subcasesAsserter) { + this.subcasesAsserter = subcasesAsserter; + } + + public CaseFinder targetOid(String targetOid) { + this.targetOid = targetOid; + return this; + } + + public CaseFinder targetType(QName targetType) { + this.targetType = targetType; + return this; + } + + public CaseAsserter> find() { + CaseType found = null; + for (CaseType subcase: subcasesAsserter.getSubcases()) { + if (matches(subcase)) { + if (found == null) { + found = subcase; + } else { + fail("Found more than one subcase that matches search criteria"); + } + } + } + if (found == null) { + throw new AssertionError("Found no subcase that matches search criteria"); + } else { + return subcasesAsserter.forSubcase(found); + } + } + + public SubcasesAsserter assertNone() { + for (CaseType subcase: subcasesAsserter.getSubcases()) { + if (matches(subcase)) { + fail("Found subcase while not expecting it: " + subcase); + } + } + return subcasesAsserter; + } + + public SubcasesAsserter assertAll() { + for (CaseType subcase: subcasesAsserter.getSubcases()) { + PrismObject assignmentTarget = null; + if (!matches(subcase)) { + fail("Found subcase that does not match search criteria: "+subcase); + } + } + return subcasesAsserter; + } + + private boolean matches(CaseType subcase) { + ObjectReferenceType targetRef = subcase.getTargetRef(); + if (targetOid != null) { + if (targetRef == null || !targetOid.equals(targetRef.getOid())) { + return false; + } + } + if (targetType != null) { + if (targetRef == null || !QNameUtil.match(targetType, targetRef.getType())) { + return false; + } + } + return true; + } + + protected void fail(String message) { + AssertJUnit.fail(message); + } + +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemAsserter.java new file mode 100644 index 00000000000..8be42ee2a5b --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemAsserter.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.schema.constants.SchemaConstants; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.test.asserter.prism.PrismContainerValueAsserter; +import com.evolveum.midpoint.test.util.MidPointAsserts; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractWorkItemOutputType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; + +/** + * Asserts about CaseWorkItemType. + */ +public class CaseWorkItemAsserter extends PrismContainerValueAsserter { + + public CaseWorkItemAsserter(CaseWorkItemType workItem) { + //noinspection unchecked + super(workItem.asPrismContainerValue()); + } + + public CaseWorkItemAsserter(CaseWorkItemType workItem, String details) { + //noinspection unchecked + super(workItem.asPrismContainerValue(), details); + } + + public CaseWorkItemAsserter(CaseWorkItemType workItem, RA returnAsserter, String details) { + //noinspection unchecked + super(workItem.asPrismContainerValue(), returnAsserter, details); + } + + @NotNull + private CaseWorkItemType getWorkItem() { + return getPrismValue().asContainerable(); + } + + public CaseWorkItemAsserter assertOriginalAssigneeRef(String oid, QName typeName) { + MidPointAsserts.assertThatReferenceMatches(getWorkItem().getOriginalAssigneeRef(), "originalAssigneeRef", oid, typeName); + return this; + } + + public CaseWorkItemAsserter assertPerformerRef(String oid, QName typeName) { + MidPointAsserts.assertThatReferenceMatches(getWorkItem().getPerformerRef(), "performerRef", oid, typeName); + return this; + } + + public CaseWorkItemAsserter assertAssignees(String... oids) { + PrismAsserts.assertReferenceValues(getWorkItem().getAssigneeRef(), oids); + return this; + } + + public CaseWorkItemAsserter assertStageNumber(Integer expected) { + assertThat(getWorkItem().getStageNumber()).as("stage number").isEqualTo(expected); + return this; + } + + public CaseWorkItemAsserter assertOutcome(String expected) { + AbstractWorkItemOutputType output = getWorkItem().getOutput(); + assertThat(output).as("output").isNotNull(); + MidPointAsserts.assertUriMatches(output.getOutcome(), "outcome", expected); + return this; + } + + public CaseWorkItemAsserter assertNameOrig(String expected) { + assertThat(getWorkItem().getName().getOrig()).as("name.orig").isEqualTo(expected); + return this; + } + + public CaseWorkItemAsserter assertRejected() { + return assertOutcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT); + } + + public CaseWorkItemAsserter assertClosed() { + assertThat(getWorkItem().getCloseTimestamp()).as("closeTimestamp").isNotNull(); + return this; + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemFinder.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemFinder.java new file mode 100644 index 00000000000..a2e53f0d2a9 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemFinder.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import java.util.Objects; + +import org.testng.AssertJUnit; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; + +/** + * + */ +public class CaseWorkItemFinder { + + private final CaseWorkItemsAsserter workItemsAsserter; + private Integer stageNumber; + private Long workItemId; + + public CaseWorkItemFinder(CaseWorkItemsAsserter workItemsAsserter) { + this.workItemsAsserter = workItemsAsserter; + } + + public CaseWorkItemFinder stageNumber(Integer stageNumber) { + this.stageNumber = stageNumber; + return this; + } + + public CaseWorkItemFinder workItemId(Long workItemId) { + this.workItemId = workItemId; + return this; + } + + public CaseWorkItemAsserter> find() { + CaseWorkItemType found = null; + for (CaseWorkItemType workItem: workItemsAsserter.getWorkItems()) { + if (matches(workItem)) { + if (found == null) { + found = workItem; + } else { + fail("Found more than one workItem that matches search criteria"); + } + } + } + if (found == null) { + throw new AssertionError("Found no work item that matches search criteria"); + } else { + return workItemsAsserter.forWorkItem(found); + } + } + + public CaseWorkItemsAsserter assertNone() { + for (CaseWorkItemType workItem: workItemsAsserter.getWorkItems()) { + if (matches(workItem)) { + fail("Found workItem while not expecting it: " + workItem); + } + } + return workItemsAsserter; + } + + public CaseWorkItemsAsserter assertAll() { + for (CaseWorkItemType workItem: workItemsAsserter.getWorkItems()) { + if (!matches(workItem)) { + fail("Found work item that does not match search criteria: "+workItem); + } + } + return workItemsAsserter; + } + + private boolean matches(CaseWorkItemType workItem) { + if (stageNumber != null) { + if (!Objects.equals(stageNumber, workItem.getStageNumber())) { + return false; + } + } + if (workItemId != null) { + if (!Objects.equals(workItemId, workItem.getId())) { + return false; + } + } + return true; + } + + protected void fail(String message) { + AssertJUnit.fail(message); + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemsAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemsAsserter.java new file mode 100644 index 00000000000..6218bc5c2c9 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/CaseWorkItemsAsserter.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import static org.testng.AssertJUnit.assertEquals; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseWorkItemType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; + +/** + * Asserts over a set of work items. + */ +public class CaseWorkItemsAsserter extends AbstractAsserter> { + + @NotNull private final CaseAsserter caseAsserter; + @NotNull private final List workItems; + + public CaseWorkItemsAsserter(@NotNull CaseAsserter caseAsserter, @NotNull List workItems, String details) { + super(caseAsserter, details); + this.caseAsserter = caseAsserter; + this.workItems = workItems; + } + + PrismObject getCase() { + return caseAsserter.getObject(); + } + + public @NotNull List getWorkItems() { + return workItems; + } + + public CaseWorkItemsAsserter assertWorkItems(int expected) { + assertEquals("Wrong number of work items in " + desc(), expected, getWorkItems().size()); + return this; + } + + public CaseWorkItemsAsserter assertNone() { + assertWorkItems(0); + return this; + } + + CaseWorkItemAsserter> forWorkItem(CaseWorkItemType workItem) { + CaseWorkItemAsserter> asserter = new CaseWorkItemAsserter<>(workItem, this, "work item in "+ getCase()); + copySetupTo(asserter); + return asserter; + } + + public CaseWorkItemAsserter> single() { + assertWorkItems(1); + return forWorkItem(getWorkItems().get(0)); + } + + @Override + protected String desc() { + return descWithDetails("work items in "+ getCase()); + } + + public CaseWorkItemFinder by() { + return new CaseWorkItemFinder<>(this); + } + + public CaseWorkItemAsserter> forStageNumber(Integer stageNumber) { + return by() + .stageNumber(stageNumber) + .find(); + } + + public CaseWorkItemAsserter> forWorkItemId(Long workItemId) { + return by() + .workItemId(workItemId) + .find(); + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java index 6a635a4e82b..8edc4b2f121 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java @@ -337,22 +337,12 @@ public FocusAsserter assertNoItem(ItemPath itemPath) { } public FocusAsserter assertArchetypeRef(String expectedArchetypeOid) { - List archetypeRefs = getObject().asObjectable().getArchetypeRef(); - if (archetypeRefs == null || archetypeRefs.isEmpty()) { - fail("No archetypeRefs while archetype "+expectedArchetypeOid+" expected"); - } - if (archetypeRefs.size() > 1) { - fail("Too many archetypes while archetypeRefs "+expectedArchetypeOid+" expected: "+archetypeRefs); - } - assertEquals("Wrong archetypeRef in "+desc(), expectedArchetypeOid, archetypeRefs.get(0).getOid()); + super.assertArchetypeRef(expectedArchetypeOid); return this; } public FocusAsserter assertNoArchetypeRef() { - List archetypeRefs = getObject().asObjectable().getArchetypeRef(); - if (archetypeRefs != null && !archetypeRefs.isEmpty()) { - fail("Found archetypeRefs while not expected any: "+archetypeRefs); - } + super.assertNoArchetypeRef(); return this; } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/SubcasesAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/SubcasesAsserter.java new file mode 100644 index 00000000000..a7829d23fa1 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/SubcasesAsserter.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 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.test.asserter; + +import static org.testng.AssertJUnit.assertEquals; + +import java.util.List; +import javax.xml.namespace.QName; + +import org.jetbrains.annotations.NotNull; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; + +/** + * Asserts over a set of subcases. + * + */ +public class SubcasesAsserter extends AbstractAsserter { + + @NotNull private final CaseAsserter parentCaseAsserter; + @NotNull private final List subcases; + + public SubcasesAsserter(@NotNull CaseAsserter parentCaseAsserter, @NotNull List subcases, String details) { + super(details); + this.parentCaseAsserter = parentCaseAsserter; + this.subcases = subcases; + } + + PrismObject getParentCase() { + return parentCaseAsserter.getObject(); + } + + public @NotNull List getSubcases() { + return subcases; + } + + public SubcasesAsserter assertSubcases(int expected) { + assertEquals("Wrong number of subcases in " + desc(), expected, getSubcases().size()); + return this; + } + + public SubcasesAsserter assertNone() { + assertSubcases(0); + return this; + } + + CaseAsserter> forSubcase(CaseType subcase) { + CaseAsserter> asserter = new CaseAsserter<>(subcase.asPrismObject(), this, "subcase of "+getParentCase()); + copySetupTo(asserter); + return asserter; + } + + public CaseAsserter> single() { + assertSubcases(1); + return forSubcase(getSubcases().get(0)); + } + + @Override + protected String desc() { + return descWithDetails("subcases of "+ getParentCase()); + } + + public CaseFinder by() { + return new CaseFinder<>(this); + } + + public CaseAsserter> forTarget(String targetOid) { + return by() + .targetOid(targetOid) + .find(); + } + + public CaseAsserter> forTarget(String targetOid, QName targetType) { + return by() + .targetOid(targetOid) + .targetType(targetType) + .find(); + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java index 3b5567846f2..189dc70a9fe 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/prism/PrismObjectAsserter.java @@ -6,6 +6,8 @@ */ package com.evolveum.midpoint.test.asserter.prism; +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; @@ -20,6 +22,10 @@ import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.test.asserter.*; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.jetbrains.annotations.NotNull; import org.testng.AssertJUnit; @@ -29,21 +35,8 @@ import com.evolveum.midpoint.prism.util.PrismAsserts; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.test.IntegrationTestTools; -import com.evolveum.midpoint.test.asserter.AbstractAsserter; -import com.evolveum.midpoint.test.asserter.ExtensionAsserter; -import com.evolveum.midpoint.test.asserter.OrgAsserter; -import com.evolveum.midpoint.test.asserter.ParentOrgRefsAsserter; -import com.evolveum.midpoint.test.asserter.RoleAsserter; -import com.evolveum.midpoint.test.asserter.TriggersAsserter; -import com.evolveum.midpoint.test.asserter.UserAsserter; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.TriggerType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; /** * @author semancik @@ -75,6 +68,10 @@ public PrismObject getObject() { return object; } + public O getObjectable() { + return object.asObjectable(); + } + public static PrismObjectAsserter forObject(PrismObject shadow) { return new PrismObjectAsserter<>(shadow); } @@ -126,6 +123,13 @@ public PrismObjectAsserter assertName(String expectedOrig) { return this; } + public PrismObjectAsserter assertNameOrig(String expectedOrig) { + assertThat(getObject().getName().getOrig()) + .as("Name (orig) in " + desc()) + .isEqualTo(expectedOrig); + return this; + } + public PolyStringAsserter> name() { PolyStringAsserter> asserter = new PolyStringAsserter<>(getPolyStringPropertyValue(ObjectType.F_NAME), this, "name in "+desc()); copySetupTo(asserter); @@ -348,4 +352,31 @@ public PrismObject getCachedObject(Class type, S copySetupTo(asserter); return asserter; } + + public PrismObjectAsserter assertArchetypeRef(String expectedArchetypeOid) { + List archetypeRefs = getArchetypeRefs(); + if (archetypeRefs.isEmpty()) { + fail("No archetypeRefs while archetype "+expectedArchetypeOid+" expected"); + } + if (archetypeRefs.size() > 1) { + fail("Too many archetypes while archetypeRefs "+expectedArchetypeOid+" expected: "+archetypeRefs); + } + assertEquals("Wrong archetypeRef in "+desc(), expectedArchetypeOid, archetypeRefs.get(0).getOid()); + return this; + } + + @NotNull + private List getArchetypeRefs() { + O objectable = getObject().asObjectable(); + return objectable instanceof AssignmentHolderType ? + ((AssignmentHolderType) objectable).getArchetypeRef() : emptyList(); + } + + public PrismObjectAsserter assertNoArchetypeRef() { + List archetypeRefs = getArchetypeRefs(); + if (!archetypeRefs.isEmpty()) { + fail("Found archetypeRefs while not expected any: "+archetypeRefs); + } + return this; + } } diff --git a/repo/security-enforcer-api/src/main/java/com/evolveum/midpoint/security/enforcer/api/SecurityEnforcer.java b/repo/security-enforcer-api/src/main/java/com/evolveum/midpoint/security/enforcer/api/SecurityEnforcer.java index dd32c273bcc..3dc0bf79554 100644 --- a/repo/security-enforcer-api/src/main/java/com/evolveum/midpoint/security/enforcer/api/SecurityEnforcer.java +++ b/repo/security-enforcer-api/src/main/java/com/evolveum/midpoint/security/enforcer/api/SecurityEnforcer.java @@ -26,11 +26,7 @@ import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrderConstraintsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; /** * @author Radovan Semancik @@ -86,7 +82,7 @@ void authorize(String operationUrl, /** * Returns a filter that applies to all the objects/targets for which the principal is authorized. * - * E.g. it can return a filter of all assignable roles for a principlal. In that case #assign authorization is used, + * E.g. it can return a filter of all assignable roles for a principal. In that case #assign authorization is used, * and object is the user which should hold the assignment. * * If it returns NoneFilter then no search should be done. The principal is not authorized for this operation at all. @@ -128,7 +124,7 @@ F computeSecurityFilter(MidPoint ItemSecurityConstraints getAllowedRequestAssignmentItems(MidPointPrincipal midPointPrincipal, String operationUrl, PrismObject object, PrismObject target, OwnerResolver ownerResolver, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; - MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal, String attorneyAuthorizationAction, PrismObject donor, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; + MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal, String attorneyAuthorizationAction, PrismObject donor, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException; AccessDecision determineSubitemDecision(ObjectSecurityConstraints securityConstraints, ObjectDelta delta, PrismObject currentObject, String operationUrl, AuthorizationPhaseType phase, ItemPath subitemRootPath); diff --git a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java index 6defad9ee29..a4a86d11c05 100644 --- a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java +++ b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java @@ -2174,13 +2174,13 @@ public ItemSecurityConstraint } @Override - public MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal, String attorneyAuthorizationAction, PrismObject donor, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + public MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal, String attorneyAuthorizationAction, PrismObject donor, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { if (attorneyPrincipal.getAttorney() != null) { throw new UnsupportedOperationException("Transitive attorney is not supported yet"); } AuthorizationLimitationsCollector limitationsCollector = new AuthorizationLimitationsCollector(); - AuthorizationParameters autzParams = AuthorizationParameters.Builder.buildObject(donor); + AuthorizationParameters autzParams = AuthorizationParameters.Builder.buildObject(donor); AccessDecision decision = isAuthorizedInternal(attorneyPrincipal, attorneyAuthorizationAction, null, autzParams, null, limitationsCollector, task, result); if (!decision.equals(AccessDecision.ALLOW)) { failAuthorization(attorneyAuthorizationAction, null, autzParams, result); From e0a27c21d05895ffb6e0096560710466c2dc1881 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Fri, 17 Apr 2020 19:51:47 +0200 Subject: [PATCH 13/27] fixing MID-6209 + cleanup for task list page --- .../web/page/admin/server/PageTasks.java | 48 +++--- .../web/page/admin/server/TaskTablePanel.java | 145 ++++++++++-------- 2 files changed, 104 insertions(+), 89 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java index 3923803431b..c2e1f0ab21f 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java @@ -7,9 +7,26 @@ package com.evolveum.midpoint.web.page.admin.server; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import org.apache.commons.lang.time.DurationFormatUtils; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.GetOperationOptions; @@ -27,25 +44,10 @@ import com.evolveum.midpoint.web.page.admin.PageAdmin; import com.evolveum.midpoint.web.page.admin.server.dto.TaskDtoExecutionStatus; import com.evolveum.midpoint.web.session.UserProfileStorage; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.lang.time.DurationFormatUtils; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.apache.wicket.util.string.StringValue; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskBindingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskRecurrenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; @PageDescriptor( urls = { @@ -110,7 +112,7 @@ protected List, String>> createColumns() { add(tablePanel); } - private Collection, String>> addCustomColumns(List, String>> columns) { + private void addCustomColumns(List, String>> columns) { columns.add(2, new ObjectReferenceColumn>(createStringResource("pageTasks.task.objectRef"), SelectableBeanImpl.F_VALUE+"."+TaskType.F_OBJECT_REF.getLocalPart()){ private static final long serialVersionUID = 1L; @Override @@ -178,7 +180,6 @@ public IModel getDataModel(IModel> rowModel) { return Model.of(createScheduledToRunAgain(rowModel)); } }); - return columns; } private Collection> createOperationOptions() { @@ -190,10 +191,9 @@ private Collection> createOperationOptions( GetOperationOptionsBuilder getOperationOptionsBuilder = getSchemaHelper().getOperationOptionsBuilder(); getOperationOptionsBuilder = getOperationOptionsBuilder.resolveNames(); - Collection> searchOptions = getOperationOptionsBuilder + return getOperationOptionsBuilder .items(propertiesToGet.toArray(new Object[0])).retrieve() .build(); - return searchOptions; } private Date getCurrentRuntime(IModel> taskModel) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java index dc24c6bc99c..02e86ac3635 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java @@ -7,9 +7,29 @@ package com.evolveum.midpoint.web.page.admin.server; +import java.util.*; +import java.util.stream.Collectors; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.jetbrains.annotations.NotNull; + import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; -import com.evolveum.midpoint.gui.api.model.ReadOnlyModel; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.model.api.ModelPublicConstants; import com.evolveum.midpoint.model.api.TaskService; @@ -31,7 +51,10 @@ import com.evolveum.midpoint.web.application.PageDescriptor; import com.evolveum.midpoint.web.application.Url; import com.evolveum.midpoint.web.component.AjaxIconButton; -import com.evolveum.midpoint.web.component.data.column.*; +import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; +import com.evolveum.midpoint.web.component.data.column.EnumPropertyColumn; +import com.evolveum.midpoint.web.component.data.column.IconColumn; +import com.evolveum.midpoint.web.component.data.column.LinkPanel; import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; @@ -42,27 +65,6 @@ import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.WebPage; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.model.PropertyModel; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.jetbrains.annotations.NotNull; - -import javax.xml.datatype.XMLGregorianCalendar; -import java.util.*; -import java.util.stream.Collectors; - @PageDescriptor( urls = { @Url(mountUrl = "/admin/tasks2", matchUrlForSecurity = "/admin/tasks2") @@ -100,7 +102,7 @@ public TaskTablePanel(String id, UserProfileStorage.TableId tableId, Collection< @Override protected void objectDetailsPerformed(AjaxRequestTarget target, TaskType object) { - taskDetailsPerformed(target, object.getOid()); + taskDetailsPerformed(object.getOid()); } @Override @@ -159,10 +161,6 @@ private WebMarkupContainer getFeedbackPanel() { return getPageBase().getFeedbackPanel(); } - private void navigateToNext(Class page, PageParameters parameters) { - getPageBase().navigateToNext(page, parameters); - } - private void synchronizeTasksPerformed(AjaxRequestTarget target) { Task opTask = createSimpleTask(OPERATION_SYNCHRONIZE_TASKS); OperationResult result = opTask.getResult(); @@ -186,10 +184,10 @@ private void synchronizeTasksPerformed(AjaxRequestTarget target) { target.add(getTable()); } - private void taskDetailsPerformed(AjaxRequestTarget target, String oid) { + private void taskDetailsPerformed(String oid) { PageParameters parameters = new PageParameters(); parameters.add(OnePageParameterEncoder.PARAMETER, oid); - navigateToNext(PageTask.class, parameters); + getPageBase().navigateToNext(PageTask.class, parameters); } private List, String>> initTaskColumns() { @@ -227,30 +225,28 @@ protected List, String>> initCustomTaskColumns( columns.add(createTaskExecutionStatusColumn()); - columns.add(createProgressColumn("pageTasks.task.progress")); - columns.add(createErrorsColumn("pageTasks.task.errors")); + columns.add(createProgressColumn()); + columns.add(createErrorsColumn()); columns.add(new IconColumn>(createStringResource("pageTasks.task.status"), TaskType.F_RESULT_STATUS.getLocalPart()) { @Override protected DisplayType getIconDisplayType(final IModel> rowModel) { - String icon = ""; - if (rowModel != null && rowModel.getObject() != null && rowModel.getObject().getValue().getResultStatus() != null) { + String icon; + String title; + + TaskType task = getTask(rowModel, false); + + if (task != null && task.getResultStatus() != null) { icon = OperationResultStatusPresentationProperties - .parseOperationalResultStatus(rowModel.getObject().getValue().getResultStatus()).getIcon() + .parseOperationalResultStatus(task.getResultStatus()).getIcon() + " fa-lg"; + title = createStringResource(task.getResultStatus()).getString(); } else { icon = OperationResultStatusPresentationProperties.UNKNOWN.getIcon() + " fa-lg"; - } - - String title = ""; - TaskType dto = rowModel.getObject().getValue(); - - if (dto != null && dto.getResultStatus() != null) { - title = createStringResource(dto.getResultStatus()).getString(); - } else { title = createStringResource(OperationResultStatusType.UNKNOWN).getString(); } + return WebComponentUtil.createDisplayType(icon, "", title); } }); @@ -269,8 +265,8 @@ protected String translate(Enum en) { }; } - private AbstractExportableColumn, String> createProgressColumn(String titleKey) { - return new AbstractExportableColumn, String>(createStringResource(titleKey)) { + private AbstractExportableColumn, String> createProgressColumn() { + return new AbstractExportableColumn, String>(createStringResource("pageTasks.task.progress")) { @Override public void populateItem(Item>> cellItem, String componentId, final IModel> rowModel) { @@ -284,7 +280,7 @@ public void populateItem(Item>> cellItem public void onClick(AjaxRequestTarget target) { PageParameters pageParams = new PageParameters(); pageParams.add(OnePageParameterEncoder.PARAMETER, rowModel.getObject().getValue().getOid()); - navigateToNext(PageTask.class, pageParams); + getPageBase().navigateToNext(PageTask.class, pageParams); } }); } @@ -298,8 +294,8 @@ public IModel getDataModel(IModel> rowModel) { }; } - private AbstractColumn, String> createErrorsColumn(String titleKey) { - return new AbstractColumn, String>(createStringResource(titleKey)) { + private AbstractColumn, String> createErrorsColumn() { + return new AbstractColumn, String>(createStringResource("pageTasks.task.errors")) { @Override public void populateItem(Item>> cellItem, String componentId, IModel> rowModel) { TaskType task = rowModel.getObject().getValue(); @@ -701,7 +697,7 @@ private void suspendTasksPerformed(AjaxRequestTarget target, IModel plainTasks = selectedTasks.stream().filter(dto -> !isManageableTreeRoot(dto)).collect(Collectors.toList()); - List trees = selectedTasks.stream().filter(dto -> isManageableTreeRoot(dto)).collect(Collectors.toList()); + List trees = selectedTasks.stream().filter(TaskTablePanel::isManageableTreeRoot).collect(Collectors.toList()); boolean suspendedPlain = suspendPlainTasks(plainTasks, result, opTask); boolean suspendedTrees = suspendTrees(trees, result, opTask); result.computeStatus(); @@ -730,7 +726,7 @@ private void resumeTasksPerformed(AjaxRequestTarget target, IModel plainTasks = selectedTasks.stream().filter(dto -> !isManageableTreeRoot(dto)).collect(Collectors.toList()); - List trees = selectedTasks.stream().filter(dto -> isManageableTreeRoot(dto)).collect(Collectors.toList()); + List trees = selectedTasks.stream().filter(TaskTablePanel::isManageableTreeRoot).collect(Collectors.toList()); getTaskService().resumeTasks(getOids(plainTasks), opTask, result); for (TaskType tree : trees) { getTaskService().resumeTaskTree(tree.getOid(), opTask, result); @@ -780,7 +776,6 @@ private List getSelectedTasks(AjaxRequestTarget target, IModel getTaskConfirmationMessageModel(ColumnMenuAction action, String actionName) { - if (action.getRowModel() == null) { - return createStringResource("pageTasks.message.confirmationMessageForMultipleTaskObject", actionName, getSelectedObjects().size()); -// WebComponentUtil.getSelectedData(()).size()); - } else { - String objectName = ((SelectableBean) (action.getRowModel().getObject())).getValue().getName().getOrig(); + private IModel getTaskConfirmationMessageModel(ColumnMenuAction> action, String actionName) { + if (action.getRowModel() != null) { + String objectName = WebComponentUtil.getName(getTask(action.getRowModel(), false)); return createStringResource("pageTasks.message.confirmationMessageForSingleTaskObject", actionName, objectName); } + if (CollectionUtils.isEmpty(getSelectedObjects())) { + getSession().warn(getString("pageTasks.message.confirmationMessageForNoTaskObject", actionName)); + return null; //confirmation popup should not be shown + } + + return createStringResource("pageTasks.message.confirmationMessageForMultipleTaskObject", actionName, getSelectedObjects().size()); } // must be static, otherwise JVM crashes (probably because of some wicket serialization issues) + @SuppressWarnings("unchecked") private static boolean isCoordinator(IModel rowModel, boolean isHeader) { - SelectableBean dto = getDto(rowModel, isHeader); - return dto != null && TaskTypeUtil.isCoordinator(dto.getValue()); + if (isNotTaskModel(rowModel)) { + return false; + } + TaskType task = getTask((IModel>) rowModel, isHeader); + return task != null && TaskTypeUtil.isCoordinator(task); } // must be static, otherwise JVM crashes (probably because of some wicket serialization issues) + @SuppressWarnings("unchecked") private static boolean isManageableTreeRoot(IModel rowModel, boolean isHeader) { - SelectableBean dto = getDto(rowModel, isHeader); - return dto != null && isManageableTreeRoot(dto.getValue()); + if (isNotTaskModel(rowModel)) { + return false; + } + TaskType task = getTask((IModel>) rowModel, isHeader); + return task != null && isManageableTreeRoot(task); + } + + private static boolean isNotTaskModel(IModel rowModel) { + if (rowModel == null) { + return false; + } + return rowModel.getObject() instanceof SelectableBean; } private static boolean isManageableTreeRoot(TaskType taskType) { return TaskTypeUtil.isCoordinator(taskType) || TaskTypeUtil.isPartitionedMaster(taskType); } - private static SelectableBean getDto(IModel rowModel, boolean isHeader) { + private static TaskType getTask(IModel> rowModel, boolean isHeader) { if (rowModel != null && !isHeader) { - Object object = rowModel.getObject(); - if (object instanceof SelectableBean) { - return (SelectableBean) object; + SelectableBean object = rowModel.getObject(); + if (object == null) { + return null; } + + return object.getValue(); } return null; } From 7e3798441147a86c0c4b82882f2da12954e638d2 Mon Sep 17 00:00:00 2001 From: kate Date: Mon, 20 Apr 2020 15:54:14 +0200 Subject: [PATCH 14/27] fixed the task name for member operations --- .../web/page/admin/roles/MemberOperationsHelper.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/MemberOperationsHelper.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/MemberOperationsHelper.java index 7ecd3e0a6f7..39720e4c565 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/MemberOperationsHelper.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/MemberOperationsHelper.java @@ -10,6 +10,12 @@ import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.util.PolyStringUtils; + +import com.evolveum.prism.xml.ns._public.types_3.PolyStringTranslationType; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; + import org.apache.wicket.ajax.AjaxRequestTarget; import com.evolveum.midpoint.gui.api.component.ChooseArchetypeMemberPopup; @@ -396,7 +402,7 @@ private static void createTask(Task operationalTask, QName type, ObjectQuery mem ScheduleType schedule = new ScheduleType(); schedule.setMisfireAction(MisfireActionType.EXECUTE_IMMEDIATELY); operationalTask.makeSingle(schedule); - operationalTask.setName(WebComponentUtil.createPolyFromOrigString(parentResult.getOperation())); + operationalTask.setName(WebComponentUtil.createPolyFromOrigString(pageBase.createStringResource(parentResult.getOperation()).getString())); PrismPropertyDefinition propertyDefQuery = pageBase.getPrismContext().getSchemaRegistry() .findPropertyDefinitionByElementName(SchemaConstants.MODEL_EXTENSION_OBJECT_QUERY); From 80a957c2ef8e58fdeabf230412aa670db4bec8b0 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Mon, 20 Apr 2020 16:51:54 +0200 Subject: [PATCH 15/27] Fix case approval state visualization The semantics of ApprovalSchemaExecutionInformationType was more clearly defined and the code for its handling radically simplified. The stage/executionRecord part was deprecated and eliminated. See also https://wiki.evolveum.com/display/midPoint/How+to+display+approval+case+%28planned+or+real%29+execution. --- ...pprovalProcessExecutionInformationDto.java | 2 +- .../ApprovalStageExecutionInformationDto.java | 125 +- .../workflow/dto/ApproverEngagementDto.java | 12 +- ...pprovalSchemaExecutionInformationUtil.java | 43 + .../midpoint/schema/util/CaseEventUtil.java | 30 + .../schema/util/CaseWorkItemUtil.java | 9 +- .../ns/public/common/common-workflows-3.xsd | 3095 +++++++++-------- .../midpoint/wf/api/WorkflowManager.java | 4 +- ...rovalSchemaExecutionInformationHelper.java | 31 +- .../midpoint/wf/impl/WorkflowManagerImpl.java | 4 +- .../midpoint/wf/impl/AbstractWfTest.java | 39 +- .../assignments/TestAssignmentsAdvanced.java | 7 - .../midpoint/wf/impl/other/TestPreview.java | 298 ++ .../resources/preview/role-lab-manager.xml | 40 + .../src/test/resources/preview/user-alice.xml | 11 + .../preview/user-jane-the-lab-owner.xml | 12 + .../preview/user-kate-the-administrator.xml | 12 + .../preview/user-martin-the-dept-head.xml | 12 + .../resources/preview/user-peter-the-dean.xml | 12 + model/workflow-impl/testng-integration.xml | 1 + .../test/AbstractIntegrationTest.java | 20 +- 21 files changed, 2129 insertions(+), 1690 deletions(-) create mode 100644 infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java create mode 100644 infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java create mode 100644 model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java create mode 100644 model/workflow-impl/src/test/resources/preview/role-lab-manager.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-alice.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java index 60b95f6f409..cf89c9380f1 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java @@ -74,7 +74,7 @@ public static ApprovalProcessExecutionInformationDto createFrom(ApprovalSchemaEx targetName, triggers, running); int startingStageNumber = wholeProcess ? 1 : currentStageNumber+1; boolean reachable = true; - for (int i = startingStageNumber - 1; i < numberOfStages; i++) { + for (int i = startingStageNumber; i <= numberOfStages; i++) { ApprovalStageExecutionInformationDto stage = ApprovalStageExecutionInformationDto.createFrom(info, i, resolver, session, opTask, result); stage.setReachable(reachable); rv.stages.add(stage); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java index 0f65a92cdcc..2adad5e2ed1 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java @@ -9,15 +9,10 @@ import com.evolveum.midpoint.repo.common.ObjectResolver; import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.schema.util.ApprovalContextUtil; -import com.evolveum.midpoint.schema.util.WorkItemId; +import com.evolveum.midpoint.schema.util.*; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.wf.util.ApprovalUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.collections.CollectionUtils; import java.io.Serializable; import java.util.ArrayList; @@ -35,7 +30,6 @@ public class ApprovalStageExecutionInformationDto implements Serializable { private static final long serialVersionUID = 1L; - private static final Trace LOGGER = TraceManager.getTrace(ApprovalStageExecutionInformationDto.class); public static final String F_APPROVER_ENGAGEMENTS = "approverEngagements"; private final int stageNumber; @@ -57,23 +51,25 @@ private ApprovalStageExecutionInformationDto(ApprovalStageDefinitionType definit evaluationStrategy = definition.getEvaluationStrategy(); } - static ApprovalStageExecutionInformationDto createFrom(ApprovalSchemaExecutionInformationType processInfo, int stageIndex, + static ApprovalStageExecutionInformationDto createFrom(ApprovalSchemaExecutionInformationType processInfo, int stageNumber, ObjectResolver resolver, ObjectResolver.Session session, Task opTask, OperationResult result) { - ApprovalStageExecutionInformationType stageInfo = processInfo.getStage().get(stageIndex); + ApprovalStageExecutionInformationType stageInfo = ApprovalSchemaExecutionInformationUtil.getStage(processInfo, stageNumber); + if (stageInfo == null) { + throw new IllegalStateException("No stage execution information in " + processInfo); + } ApprovalStageExecutionInformationDto rv = new ApprovalStageExecutionInformationDto(stageInfo.getDefinition()); - int stageNumber = stageIndex+1; int currentStageNumber = defaultIfNull(processInfo.getCurrentStageNumber(), 0); if (stageNumber <= currentStageNumber) { - addInformationFromRecordedStage(rv, processInfo, stageInfo.getExecutionRecord(), currentStageNumber, resolver, session, opTask, result); + addInformationFromPastOrCurrentStage(rv, processInfo, stageNumber, currentStageNumber, resolver, session, opTask, result); } else { - addInformationFromPreviewedStage(rv, stageInfo.getExecutionPreview(), resolver, session, opTask, result); + addInformationFromFutureStage(rv, stageInfo.getExecutionPreview(), resolver, session, opTask, result); } // computing stage outcome that is to be displayed if (rv.automatedOutcome != null) { rv.outcome = rv.automatedOutcome; } else { if (stageNumber < currentStageNumber) { - rv.outcome = ApprovalLevelOutcomeType.APPROVE; // no stage before current stage could be manually rejected + rv.outcome = ApprovalLevelOutcomeType.APPROVE; // no stage before current stage could be manually rejected } else if (stageNumber == currentStageNumber) { rv.outcome = ApprovalUtils.approvalLevelOutcomeFromUri(ApprovalContextUtil.getOutcome(processInfo)); } else { @@ -87,7 +83,7 @@ static ApprovalStageExecutionInformationDto createFrom(ApprovalSchemaExecutionIn return rv; } - private static void addInformationFromPreviewedStage(ApprovalStageExecutionInformationDto rv, + private static void addInformationFromFutureStage(ApprovalStageExecutionInformationDto rv, ApprovalStageExecutionPreviewType executionPreview, ObjectResolver resolver, ObjectResolver.Session session, Task opTask, OperationResult result) { if (executionPreview.getExpectedAutomatedCompletionReason() != null) { @@ -96,7 +92,7 @@ private static void addInformationFromPreviewedStage(ApprovalStageExecutionInfor } else { for (ObjectReferenceType approver : executionPreview.getExpectedApproverRef()) { resolve(approver, resolver, session, opTask, result); - rv.addApproverEngagement(new ApproverEngagementDto(approver, null)); + rv.addApproverEngagement(new ApproverEngagementDto(approver)); } } rv.errorMessage = executionPreview.getErrorMessage(); @@ -109,43 +105,25 @@ private static void resolve(ObjectReferenceType ref, ObjectResolver resolver, Ob } } - private static void addInformationFromRecordedStage(ApprovalStageExecutionInformationDto rv, - ApprovalSchemaExecutionInformationType processInfo, ApprovalStageExecutionRecordType executionRecord, - int currentStageNumber, ObjectResolver resolver, - ObjectResolver.Session session, Task opTask, OperationResult result) { - for (CaseEventType event : executionRecord.getEvent()) { - if (event instanceof WorkItemEventType) { - WorkItemEventType workItemEvent = (WorkItemEventType) event; - ObjectReferenceType approver; - if (event instanceof WorkItemDelegationEventType){ - List delegateToList = ((WorkItemDelegationEventType)event).getDelegatedTo(); - approver = CollectionUtils.isNotEmpty(delegateToList) ? delegateToList.get(0) : null; - } else { - approver = workItemEvent.getOriginalAssigneeRef(); - } - if (approver == null) { - LOGGER.warn("No original assignee in work item event {} -- ignoring it", workItemEvent); - continue; - } - if (workItemEvent.getExternalWorkItemId() == null) { - LOGGER.warn("No external work item ID in work item event {} -- ignoring it", workItemEvent); - continue; - } - WorkItemId externalWorkItemId = WorkItemId.create(workItemEvent.getExternalWorkItemId()); - ApproverEngagementDto engagement = rv.findApproverEngagement(approver, externalWorkItemId); - if (engagement == null) { - resolve(approver, resolver, session, opTask, result); - engagement = new ApproverEngagementDto(approver, externalWorkItemId); - rv.addApproverEngagement(engagement); - } - if (event instanceof WorkItemCompletionEventType) { - WorkItemCompletionEventType completionEvent = (WorkItemCompletionEventType) event; - engagement.setCompletedAt(completionEvent.getTimestamp()); - resolve(completionEvent.getInitiatorRef(), resolver, session, opTask, result); - engagement.setCompletedBy(completionEvent.getInitiatorRef()); - engagement.setAttorney(completionEvent.getAttorneyRef()); - engagement.setOutput(completionEvent.getOutput()); - } + private static void addInformationFromPastOrCurrentStage(ApprovalStageExecutionInformationDto rv, + ApprovalSchemaExecutionInformationType processInfo, int stageNumber, int currentStageNumber, + ObjectResolver resolver, ObjectResolver.Session session, Task opTask, OperationResult result) { + assert stageNumber <= currentStageNumber; + CaseType aCase = ApprovalSchemaExecutionInformationUtil.getEmbeddedCaseBean(processInfo); + + for (CaseEventType event : CaseEventUtil.getEventsForStage(aCase, stageNumber)) { + if (event instanceof WorkItemCompletionEventType) { + WorkItemCompletionEventType completionEvent = (WorkItemCompletionEventType) event; + ObjectReferenceType initiatorRef = completionEvent.getInitiatorRef(); + ObjectReferenceType attorneyRef = completionEvent.getAttorneyRef(); + resolve(initiatorRef, resolver, session, opTask, result); + resolve(attorneyRef, resolver, session, opTask, result); + ApproverEngagementDto engagement = new ApproverEngagementDto(initiatorRef); + engagement.setCompletedAt(completionEvent.getTimestamp()); + engagement.setCompletedBy(initiatorRef); + engagement.setAttorney(attorneyRef); + engagement.setOutput(completionEvent.getOutput()); + rv.addApproverEngagement(engagement); } else if (event instanceof StageCompletionEventType) { StageCompletionEventType completionEvent = (StageCompletionEventType) event; if (completionEvent.getAutomatedDecisionReason() != null) { @@ -154,23 +132,16 @@ private static void addInformationFromRecordedStage(ApprovalStageExecutionInform } } } - // not needed after "create work item" events will be implemented - for (CaseWorkItemType workItem : executionRecord.getWorkItem()) { - if (workItem.getStageNumber() == null || workItem.getStageNumber() != currentStageNumber){ - continue; - } - ObjectReferenceType approver = CollectionUtils.isNotEmpty(workItem.getAssigneeRef()) ? - workItem.getAssigneeRef().get(0) : workItem.getOriginalAssigneeRef(); - if (approver == null) { - LOGGER.warn("No original assignee in work item {} -- ignoring it", workItem); - continue; - } - WorkItemId externalWorkItemId = WorkItemId.create(processInfo.getCaseRef().getOid(), workItem.getId()); - ApproverEngagementDto engagement = rv.findApproverEngagement(approver, externalWorkItemId); - if (engagement == null) { - resolve(approver, resolver, session, opTask, result); - engagement = new ApproverEngagementDto(approver, externalWorkItemId); - rv.addApproverEngagement(engagement); + + // Obtaining information about open work items + if (stageNumber == currentStageNumber) { + for (CaseWorkItemType workItem : CaseWorkItemUtil.getWorkItemsForStage(aCase, stageNumber)) { + if (CaseWorkItemUtil.isCaseWorkItemNotClosed(workItem)) { + for (ObjectReferenceType assigneeRef : workItem.getAssigneeRef()) { + resolve(assigneeRef, resolver, session, opTask, result); + rv.addApproverEngagement(new ApproverEngagementDto(assigneeRef)); + } + } } } } @@ -179,16 +150,6 @@ private void addApproverEngagement(ApproverEngagementDto engagement) { approverEngagements.add(engagement); } - private ApproverEngagementDto findApproverEngagement(ObjectReferenceType approver, WorkItemId externalWorkItemId) { - for (ApproverEngagementDto engagement : approverEngagements) { - if (ObjectTypeUtil.matchOnOid(engagement.getApproverRef(), approver) - && java.util.Objects.equals(engagement.getExternalWorkItemId(), externalWorkItemId)) { - return engagement; - } - } - return null; - } - public int getStageNumber() { return stageNumber; } @@ -201,10 +162,6 @@ public String getStageDisplayName() { return stageDisplayName; } - public LevelEvaluationStrategyType getEvaluationStrategy() { - return evaluationStrategy; - } - public ApprovalLevelOutcomeType getAutomatedOutcome() { return automatedOutcome; } @@ -238,7 +195,7 @@ public boolean isReachable() { return reachable; } - public void setReachable(boolean reachable) { + void setReachable(boolean reachable) { this.reachable = reachable; } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java index 2fb1483ac5e..f6a30ab0526 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java @@ -7,7 +7,6 @@ package com.evolveum.midpoint.web.page.admin.workflow.dto; -import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractWorkItemOutputType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import org.jetbrains.annotations.NotNull; @@ -18,24 +17,20 @@ /** * GUI-friendly information about an engagement of given approver in a historic, current or future execution of an approval stage. - * - * @author mederly */ public class ApproverEngagementDto implements Serializable { private static final long serialVersionUID = 1L; @NotNull private final ObjectReferenceType approverRef; // with the whole object, if possible - @Nullable private final WorkItemId externalWorkItemId; @Nullable private AbstractWorkItemOutputType output; @Nullable private XMLGregorianCalendar completedAt; @Nullable private ObjectReferenceType completedBy; // the user that really completed the work item originally assigned to that approver @Nullable private ObjectReferenceType attorney; // the attorney (of completedBy) private boolean last; - ApproverEngagementDto(@NotNull ObjectReferenceType approverRef, @Nullable WorkItemId externalWorkItemId) { + ApproverEngagementDto(@NotNull ObjectReferenceType approverRef) { this.approverRef = approverRef; - this.externalWorkItemId = externalWorkItemId; } @NotNull @@ -43,11 +38,6 @@ public ObjectReferenceType getApproverRef() { return approverRef; } - @Nullable - public WorkItemId getExternalWorkItemId() { - return externalWorkItemId; - } - @Nullable public AbstractWorkItemOutputType getOutput() { return output; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java new file mode 100644 index 00000000000..fa3dbe205fc --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.schema.util; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalSchemaExecutionInformationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalStageExecutionInformationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class ApprovalSchemaExecutionInformationUtil { + public static ApprovalStageExecutionInformationType getStage(ApprovalSchemaExecutionInformationType executionInfo, int number) { + return executionInfo.getStage().stream() + .filter(i -> Objects.equals(number, i.getNumber())) + .findAny() + .orElse(null); + } + + @NotNull + public static PrismObject getEmbeddedCase(ApprovalSchemaExecutionInformationType executionInfo) { + if (executionInfo.getCaseRef() == null) { + throw new IllegalStateException("No caseRef in " + executionInfo); + } else if (executionInfo.getCaseRef().getObject() == null) { + throw new IllegalStateException("No caseRef.object in " + executionInfo); + } else { + //noinspection unchecked + return executionInfo.getCaseRef().getObject(); + } + } + + @NotNull + public static CaseType getEmbeddedCaseBean(ApprovalSchemaExecutionInformationType executionInfo) { + return getEmbeddedCase(executionInfo).asObjectable(); + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java new file mode 100644 index 00000000000..26ba5be2087 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.schema.util; + +import java.util.List; +import java.util.stream.Collectors; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +public class CaseEventUtil { + + public static boolean completedByUserAction(WorkItemEventType event) { + WorkItemEventCauseInformationType cause = event.getCause(); + return event.getInitiatorRef() != null && + (cause == null || + cause.getType() == null || + cause.getType() == WorkItemEventCauseTypeType.USER_ACTION); + } + + public static List getEventsForStage(CaseType aCase, int stageNumber) { + return aCase.getEvent().stream() + .filter(e -> java.util.Objects.equals(e.getStageNumber(), stageNumber)) + .collect(Collectors.toList()); + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java index 7aa3cb016fa..35e462c590c 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java @@ -16,7 +16,8 @@ import org.jetbrains.annotations.NotNull; import java.util.List; - +import java.util.Objects; +import java.util.stream.Collectors; /** * @author bpowers @@ -89,5 +90,9 @@ public static boolean doesAssigneeExist(CaseWorkItemType workItem){ return false; } - + public static List getWorkItemsForStage(CaseType aCase, int stageNumber) { + return aCase.getWorkItem().stream() + .filter(wi -> Objects.equals(wi.getStageNumber(), stageNumber)) + .collect(Collectors.toList()); + } } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd index 2b010d16674..69cf8ce75f5 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd @@ -1,1545 +1,1550 @@ - - - - - - - - - TODO - - - - - - - - - - - - - - - Rules for approving something (e.g. assignment of a role to a user). - Specifies the structure of approvers and their relations. - - - - - tns:level - 4.0 - removed - - - - - - - - ApprovalSchemaType.name - - - - - - - ApprovalSchemaType.description - - - - - - - - Levels, or stages, of the approval process. - - - 3.6 - ApprovalSchemaType.stage - - - - - - - - - - - One "level" (or stage) in the approval process. - - - - - tns:order - 4.0 - removed - number - - - tns:automaticallyApproved - 4.0 - removed - automaticallyCompleted - - - - - - - - Number of this approval stage. These should go from 1 to N. - - - 3.6 - ApprovalStageDefinitionType.number - - - - - - - ApprovalStageDefinitionType.name - - - - - - - ApprovalStageDefinitionType.displayName - - - - - - - ApprovalStageDefinitionType.description - - - - - - - - Instruction to approve something, by a user (if this points to a User object) or - by someone from a group of users (if this points to a Org object; representing - all users that belong to that organization). - - - ApprovalStageDefinitionType.approverRef - - - - - - -

- What relation(s) to use when determining approvers? E.g. "approver", "owner", - "securityApprover", and so on. -

-
- - 3.5 - ApprovalStageDefinitionType.approverRelation - -
-
- - - - Dynamically specifies approver(s). If specified, the expression(s) are evaluated and the result - is used as a set of approvers (UserType, OrgType, RoleType, or any combination of them). - May be used with approverRef element(s). - - - ApprovalStageDefinitionType.approverExpression - - - - - - - Must all approvers at this stage approve the thing (allMustApprove), - or first decision is taken as authoritative (firstDecides)? - - - ApprovalStageDefinitionType.evaluationStrategy - - - - - - - What is the outcome (of this stage) if there are no approvers? E.g. there are no users that have - been assigned a role as an approver; or a user has no managers, etc. - - - 3.6 - ApprovalStageDefinitionType.outcomeIfNoApprovers - - - - - - - How should be "groups" (orgs, roles) expanded? Default is "byClaimingWorkItem", i.e. they are not - expanded at all - their members can claim corresponding work items. - - - 3.6 - ApprovalStageDefinitionType.groupExpansion - - - - - - - Form to be displayed e.g. to present or request additional information. - EXPERIMENTAL - - - 3.6 - ApprovalStageDefinitionType.formRef - - - - - - - - Additional information for approver. Will be displayed when work item will be worked on. - - - 3.6 - ApprovalStageDefinitionType.additionalInformation - - - - - - - Expression specifying that this stage should be automatically processed (approved, rejected, skipped). - If the expression returns null, standard processing by human actors is carried out. - - - 3.6 - ApprovalStageDefinitionType.automaticallyCompleted - - - - - - - Duration of work items created at this stage. - TODO other time units, like business days? - - - 3.6 - ApprovalStageDefinitionType.duration - - - - - - - What actions are to be applied to work items when given timer(s) occur. - EXPERIMENTAL - - - 3.6 - ApprovalStageDefinitionType.timedActions - - - -
- -
- - - - - Information on actual or expected execution of an approval schema. - Contains information on the approval schema stages as defined, as executed, and as estimated to be executed. - - - 3.7 - - - - - - - - The case that was or would be created to approve give operation. This object reference should - contain the actual object (TODO). - - - c:CaseType - 4.0 - - - - - - - Current stage, if any. Null usually means that the approval process has not started yet. - - - - - - - Information related to a given stage of the approval process. - - - - - - - - - - - - Information of an actual or expected execution of an approval stage. - - - - 3.7 - - - - - - - Number of this approval stage. May be missing if the stage definition and its number is provided. - If both numbers are present, they must be equal. - - - - - - - Definition of this approval stage. - - - - - - - Approvers that are expected to be part of this stage. - (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) - - - - - - - TODO - - - - - - - - - - - Preview of an execution of an approval stage. - - Contains estimation of what approvers there would be; also - if applicable - any result - that is expected to be automatically determined, and so on. - - - - 3.7 - - - - - - - Approvers that are expected to be part of this stage. - (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) - - - - - - - Expected outcome that would occur automatically, i.e. not based on the decision of human approvers. - E.g. in case of no approvers, or because of automaticallyApproved/automaticallyCompleted feature. - - - - - - - Reason of expected automated completion. - - - - - - - Error message if preview couldn't be computed. (TODO) - - - - - - - - - - - Record of an actual execution of an approval stage. - - - - 3.7 - - - - - - - Events related to this stage. Currently might contain WorkItemEventType and StageCompletionEventType instances. - - - - - - - Currently active work items. - - - - - - - - - - - Result (outcome) of an approval process stage. - - - - - - - - - - Operation was approved at this stage. The approval process will continue at the next stage. - - - - - - - - - - Operation was rejected at this stage. The approval process will stop. - - - - - - - - - - This stage is silently skipped. This is useful for situations where we don't even want to start - an approval process if there are no approvers in it. - - Skipping whole approval process is currently supported only partly: when using approver relations. - For approver expressions, these are always evaluated within context of a workflow process. - - - - - - - - - - - - - Overall output from a work item: outcome (approve/reject/...), comment, additional delta, - and probably other things in the future. - - TODO devise an extension mechanism for this - - - - 3.6 - - - - - - - - - Additional delta(s) resulting from this action. - Typically if the user filled-in some information into - custom form. - - - - - - - - - - - - Result (outcome) of a work item. - - - - - - - - - - TODO - - - - - - - - - - TODO - - - - - - - - - - - - - - How should be "groups" (orgs, roles) expanded? - - - - - - - - - - Groups are not expanded at all - their members can claim corresponding work items. - - - - - - - - - - Groups are expanded on work item creation. One work item is created for each member of given org/role. - - - - - - - - - - - - - Enumeration of approval strategies at a particular stage. - - - - - - - - - - All approvers at a particular stage must approve the operation. - - - - - - - - - - First approver that votes will decide the whole stage (either by approving or by rejecting). - - - - - - - - - - - - - Configuration for workflows - for those parts not stored in the system config file. - - - - - - - - - - This property controls the workflow model hook - i.e. whether each request going - through the model subsystem should be processed by the workflow hook. So, if disabled, - all requests are executed immediately, without being processed by workflows. - - - WfConfigurationType.modelHookEnabled - 100 - - - - - - - How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, - automaticallyApproved items in AbstractRoleType? The default is "ifNoExplicitApprovalPolicyAction", that - means these items are applied only if no explicit approval policy action is encountered. - - - WfConfigurationType.useLegacyApproversSpecification - 120 - - - - - - - Whether to use default approval policy rules. The default is "ifNoApprovalPolicyAction", that means - these rules are applied only if no other approval policy action is encountered. - - - WfConfigurationType.useDefaultApprovalPolicyRules - 110 - - - - - - - Configuration related to tasks in which model operations are executed. - EXPERIMENTAL - - - true - 3.6.1 - WfConfigurationType.executionTasks - 500 - - - - - - - Instructions how to format approvers comments before storing them into metadata. - EXPERIMENTAL - - - true - 3.7.1 - WfConfigurationType.approverCommentsFormatting - - - - - - - WfConfigurationType.primaryChangeProcessor - 510 - - - - - - - WfConfigurationType.generalChangeProcessor - - - - - - - - - - - Instructions how to format approvers/reviewers comments before storing them into metadata. - Normally midPoint stores comments as they were entered by performers. However, each deployment can - tailor these e.g. by including performer name along with the comment. - EXPERIMENTAL - - - true - 3.7.1 - - - - - - - How to construct the comment. For example: performer.fullName + ': ' + output.comment. - - Available variables: - - performer (i.e. reviewer or approver), - - output (of AbstractWorkItemOutputType), - - workItem (of AbstractWorkItemType - only for certification), - - event (of WorkItemCompletionEventType - only for approvals). - - - - - - - Whether to include the particular output in comments. For example: output.comment != null. - Null or empty values are skipped regardless of the condition. - - - - - - - - - - Configuration related to tasks in which model operations are executed. - EXPERIMENTAL - - - true - true - 3.6.1 - - - - - - - Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). - - - 100 - WfExecutionTasksConfigurationType.serialization - - - - - - - TODO - - - 3.7 - 110 - WfExecutionTasksConfigurationType.executionConstraints - - - - - - - - - - - Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). - EXPERIMENTAL - - - true - true - 3.6.1 - - - - - - - Whether this feature is enabled. Default is true if "serialization" element is present; false otherwise. - - - WfExecutionTasksSerializationType.enabled - 100 - - - - - - - Scope of serialization. The default is "object". If multiple scopes are defined, serialization occurs on each one. - - - WfExecutionTasksSerializationType.scope - 110 - - - - - - - Interval after which the execution task is to be rescheduled in case of conflict. Default is 10 seconds. - - - WfExecutionTasksSerializationType.retryAfter - 120 - - - - - - - TODO - - - WfExecutionTasksSerializationType.groupPrefix - 130 - - - - - - - - - - - Scope of execution task serialization. - - - - - - - - - - No two workflow execution tasks from a single operation are allowed to execute at once. - - - - - - - - - - No two workflow execution tasks on a given object are allowed to execute at once. - - - - - - - - - - No two workflow execution tasks related to give target are allowed to execute at once. - Note that the information on target is not always available (e.g. when executing changes - that do not require approval), so this may not be absolutely reliable. - - - - - - - - - - No two workflow execution tasks are allowed to execute at once. - - - - - - - - - - - - - How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, - automaticallyApproved items in AbstractRoleType? - - - - - - - - - - The legacy approvers specification is never used. - - - - - - - - - - The legacy approvers specification is always used. It is used before any other (policy-based) approval actions. - - - - - - - - - - The legacy approvers specification is used if there's no explicit approval policy applicable to a given - target. - - - - - - - - - - - - - Whether to use default approval policy rules. - - - - - - - - - - Default approval policy rules are never used. - - - - - - - - - - Default approval policy rules are used if there are no applicable approval policy actions. - - - - - - - - - - - - - Configuration for workflow change processor. - - - true - - - - - - - WfChangeProcessorConfigurationType.enabled - 100 - - - - - - - Order in which the change processor should be invoked. (Unspecified means "at the end".) - NOT IMPLEMENTED YET. - - - - - - - - - - - Configuration for GeneralChangeProcessor. - - - true - - - - - - - - - - - - - - - A scenario for GeneralChangeProcessor. - - - - - - - Is this scenario enabled? - - - - - - - A human-readable name of the scenario (e.g. "Approving assignments of roles R1001-R1999 to users in XYZ organization"). - - - - - - - A condition controlling whether this scenario applies, i.e. whether a defined approval process should be started. - - - - - - - A name of the approval process. When the above condition is met, this process is started. It has to evaluate the situation, - seek user's (or users') approval(s), modifying the situation if necessary. - - - - - - - The name of the Spring bean used for customizations. It provides e.g. a method for externalizing process state, - a method for providing work item contents, and so on. - - - - - - - - - - Configuration for PrimaryChangeProcessor. - - - - - - - - - - - - PrimaryChangeProcessorConfigurationType.policyRuleBasedAspect - 200 - - - - - - - PrimaryChangeProcessorConfigurationType.addAssociationAspect - 210 - - - - - - - - - - - - Configuration for a primary change processor aspect. - - Some aspects do not require any configuration - for example, role and resource assignment ones. - They take all the approver information directly from the object (role or resource) being assigned. - However, there are some others (namely, role/resource/user/whatever add/modify aspects) that need - the explicit information about approver(s) in order to know where to route the request. - - For the former aspects, the approver information specified here takes precedence over - approver information derived from the objects being used (e.g. role or resource). - More specifically, if any approver information is here, no approver information is - taken from the objects. This could be changed (e.g. by allowing to tune this behavior) - in the future. - - - - - - - - - - Whether the aspect is enabled or not. - If not specified (but if aspect configuration is present), it is assumed to be true. - However, if the whole aspect configuration is absent, only aspects marked as "enabled-by-default" are enabled. - - - PcpAspectConfigurationType.enabled - 100 - - - - - - - Approvers for this aspect. The approver is a person (or group) that approves carrying out - action(s) relevant to this aspect. This reference may point to object of type UserType of OrgType. - - - PcpAspectConfigurationType.approverRef - 110 - - - - - - - Approvers for this aspect. If specified, the expression(s) are evaluated and the result - is used as a set of approvers (UserType, OrgType, or any combination of them). - May be used with approverRef element(s). - - - PcpAspectConfigurationType.approverExpression - 120 - - - - - - - More complex (multi-stage) approval schema. If used, it overrides both - approverRef and approverExpression elements. - - - PcpAspectConfigurationType.approvalSchema - 130 - - - - - - - Name of custom approval process. If used, it overrides - approverRef, approverExpression, and approvalSchema elements. - - For explicitness, only one of approverRef(s)/approverExpression(s), - approvalSchema and approvalProcess should be specified. - - THIS PROPERTY (approvalProcess) IS NOT SUPPORTED YET. - - - - - - - Condition specifying when the item is automatically approved (e.g. "user is - from Board of Directors"). This is an expression that should yield a boolean value. - - - PcpAspectConfigurationType.automaticallyApproved - 140 - - - - - - - Condition specifying if the workflow should be started in the first place. - This is an expression that should yield a boolean value. It gets 'itemToApprove' parameter - that contains item to be approved - it is aspect-specific: might be e.g. an assignment, - an association + resource shadow discriminator, etc. - - The difference between applicabilityCondition and automaticallyApproved is that if the - former yields false, workflow is not even started. If it yields true, workflow is started, - and then 'automaticallyApproved' is evaluated. If it yields false, manual approval is - required. If true, item is automatically approved. - - CURRENTLY IMPLEMENTED ONLY IN AddAssociationAspect. - - - PcpAspectConfigurationType.applicabilityCondition - 150 - - - - - - - - - - - A generic configuration for a wf aspect. - - It is meant for non-standard aspects. (Standard aspects use named properties in - PrimaryChangeProcessorConfigurationType container.) - - - - - - - - - - - - Name of the aspect bean. - - - GenericPcpAspectConfigurationType.name - - - - - - - - - - - - Container for association-to-be-added in the context: resource shadow discriminator. - - - - - - - - - - Association to be added. - - - - - - - To which resource/kind/intent to add it. - - - - - - - - - - - - Describes the approval context, i.e. what has to be approved, the approval schema, and so on. - - - - - - - - - - - - - - - - - - - - - - - - DEPRECATED - We need to decide what to do with this. - - - - - - - - - - - - - - TODO Replace by forms eventually - TEMPORARY - - - - - - - - 3.7 - - - - - - - true - 3.7 - - - - - - - - - - - TODO Replace by forms eventually. - TEMPORARY - - - - - - - 3.7 - - - - - - - true - 3.7 - - - - - - - - - - - Why was this process started? For processes based on policy rules we define it via relevant policy rules. - (For legacy processes we don't provide this kind of information.) - - EXPERIMENTAL - - - - - - - - - - - TODO - - Note that the rule should be triggered. All irrelevant (non-approval) action types should be removed. - - - - - - - - - - - - - TODO - EXPERIMENTAL - - By default (when the base is not specified), positive time intervals are meant "after work item start". - Negative time intervals are meant "before work item deadline". - - - 3.6 - - - - - - - - - - - - - TODO - - - - 3.6 - - - - - - - Time will be taken relative to the deadline. (This is the default for zero or negative values.) - - - - - - - - - - Time will be taken relative to the work item creation timestamp. (This is the default for positive values.) - - - - - - - - - - - - - - Kind of operation. - - - 3.6 - - - - - - - - Complete (approve/reject) operation. (Explicit or automated.) - - - - - - - - - - Delegate operation. (Explicit or automated.) - - - - - - - - - - Escalate operation. (Explicit or automated.) - - - - - - - - - - Claim operation. - - - - - - - - - - Claim operation. - - - - - - - - - - Cancel operation. Work item was cancelled as a result of other action. (E.g. another work item - was completed, resulting in process or stage completion. Or the process was cancelled/deleted - externally.) - - - - - - - - - -
+ + + + + + + + + TODO + + + + + + + + + + + + + + + Rules for approving something (e.g. assignment of a role to a user). + Specifies the structure of approvers and their relations. + + + + + tns:level + 4.0 + removed + + + + + + + + ApprovalSchemaType.name + + + + + + + ApprovalSchemaType.description + + + + + + + + Levels, or stages, of the approval process. + + + 3.6 + ApprovalSchemaType.stage + + + + + + + + + + + One "level" (or stage) in the approval process. + + + + + tns:order + 4.0 + removed + number + + + tns:automaticallyApproved + 4.0 + removed + automaticallyCompleted + + + + + + + + Number of this approval stage. These should go from 1 to N. + + + 3.6 + ApprovalStageDefinitionType.number + + + + + + + ApprovalStageDefinitionType.name + + + + + + + ApprovalStageDefinitionType.displayName + + + + + + + ApprovalStageDefinitionType.description + + + + + + + + Instruction to approve something, by a user (if this points to a User object) or + by someone from a group of users (if this points to a Org object; representing + all users that belong to that organization). + + + ApprovalStageDefinitionType.approverRef + + + + + + +

+ What relation(s) to use when determining approvers? E.g. "approver", "owner", + "securityApprover", and so on. +

+
+ + 3.5 + ApprovalStageDefinitionType.approverRelation + +
+
+ + + + Dynamically specifies approver(s). If specified, the expression(s) are evaluated and the result + is used as a set of approvers (UserType, OrgType, RoleType, or any combination of them). + May be used with approverRef element(s). + + + ApprovalStageDefinitionType.approverExpression + + + + + + + Must all approvers at this stage approve the thing (allMustApprove), + or first decision is taken as authoritative (firstDecides)? + + + ApprovalStageDefinitionType.evaluationStrategy + + + + + + + What is the outcome (of this stage) if there are no approvers? E.g. there are no users that have + been assigned a role as an approver; or a user has no managers, etc. + + + 3.6 + ApprovalStageDefinitionType.outcomeIfNoApprovers + + + + + + + How should be "groups" (orgs, roles) expanded? Default is "byClaimingWorkItem", i.e. they are not + expanded at all - their members can claim corresponding work items. + + + 3.6 + ApprovalStageDefinitionType.groupExpansion + + + + + + + Form to be displayed e.g. to present or request additional information. + EXPERIMENTAL + + + 3.6 + ApprovalStageDefinitionType.formRef + + + + + + + + Additional information for approver. Will be displayed when work item will be worked on. + + + 3.6 + ApprovalStageDefinitionType.additionalInformation + + + + + + + Expression specifying that this stage should be automatically processed (approved, rejected, skipped). + If the expression returns null, standard processing by human actors is carried out. + + + 3.6 + ApprovalStageDefinitionType.automaticallyCompleted + + + + + + + Duration of work items created at this stage. + TODO other time units, like business days? + + + 3.6 + ApprovalStageDefinitionType.duration + + + + + + + What actions are to be applied to work items when given timer(s) occur. + EXPERIMENTAL + + + 3.6 + ApprovalStageDefinitionType.timedActions + + + +
+ +
+ + + + + Information on actual or expected execution of an approval schema. + Contains information on the approval schema stages as defined, as executed, and as estimated to be executed. + + + 3.7 + + + + + + + + The case that was or would be created to approve give operation. This object reference should + contain the actual object (TODO). + + + c:CaseType + 4.0 + + + + + + + Current stage, if any. Null usually means that the approval process has not started yet. + + + + + + + Information related to a given stage of the approval process. + + + + + + + + + + + + + Information of an actual or expected execution of an approval stage. + + + + 3.7 + + + + + + + Number of this approval stage. May be missing if the stage definition and its number is provided. + If both numbers are present, they must be equal. + + + + + + + Definition of this approval stage. + + + + + + + Approvers that are expected to be part of this stage. + (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) + + + + + + + TODO + + + + + + + + + + + Preview of an execution of an approval stage. + + Contains estimation of what approvers there would be; also - if applicable - any result + that is expected to be automatically determined, and so on. + + + + 3.7 + + + + + + + Approvers that are expected to be part of this stage. + (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) + + + + + + + Expected outcome that would occur automatically, i.e. not based on the decision of human approvers. + E.g. in case of no approvers, or because of automaticallyApproved/automaticallyCompleted feature. + + + + + + + Reason of expected automated completion. + + + + + + + Error message if preview couldn't be computed. (TODO) + + + + + + + + + + + Record of an actual execution of an approval stage. + Note that this structure is deprecated. Everything we need can be found in the case object + attached to (enclosing) ApprovalSchemaExecutionInformationType. + + + + 3.7 + true + 4.1 + + + + + + + Events related to this stage. Currently might contain WorkItemEventType and StageCompletionEventType instances. + + + + + + + Currently active work items. + + + + + + + + + + + Result (outcome) of an approval process stage. + + + + + + + + + + Operation was approved at this stage. The approval process will continue at the next stage. + + + + + + + + + + Operation was rejected at this stage. The approval process will stop. + + + + + + + + + + This stage is silently skipped. This is useful for situations where we don't even want to start + an approval process if there are no approvers in it. + + Skipping whole approval process is currently supported only partly: when using approver relations. + For approver expressions, these are always evaluated within context of a workflow process. + + + + + + + + + + + + + Overall output from a work item: outcome (approve/reject/...), comment, additional delta, + and probably other things in the future. + + TODO devise an extension mechanism for this + + + + 3.6 + + + + + + + + + Additional delta(s) resulting from this action. + Typically if the user filled-in some information into + custom form. + + + + + + + + + + + + Result (outcome) of a work item. + + + + + + + + + + TODO + + + + + + + + + + TODO + + + + + + + + + + + + + + How should be "groups" (orgs, roles) expanded? + + + + + + + + + + Groups are not expanded at all - their members can claim corresponding work items. + + + + + + + + + + Groups are expanded on work item creation. One work item is created for each member of given org/role. + + + + + + + + + + + + + Enumeration of approval strategies at a particular stage. + + + + + + + + + + All approvers at a particular stage must approve the operation. + + + + + + + + + + First approver that votes will decide the whole stage (either by approving or by rejecting). + + + + + + + + + + + + + Configuration for workflows - for those parts not stored in the system config file. + + + + + + + + + + This property controls the workflow model hook - i.e. whether each request going + through the model subsystem should be processed by the workflow hook. So, if disabled, + all requests are executed immediately, without being processed by workflows. + + + WfConfigurationType.modelHookEnabled + 100 + + + + + + + How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, + automaticallyApproved items in AbstractRoleType? The default is "ifNoExplicitApprovalPolicyAction", that + means these items are applied only if no explicit approval policy action is encountered. + + + WfConfigurationType.useLegacyApproversSpecification + 120 + + + + + + + Whether to use default approval policy rules. The default is "ifNoApprovalPolicyAction", that means + these rules are applied only if no other approval policy action is encountered. + + + WfConfigurationType.useDefaultApprovalPolicyRules + 110 + + + + + + + Configuration related to tasks in which model operations are executed. + EXPERIMENTAL + + + true + 3.6.1 + WfConfigurationType.executionTasks + 500 + + + + + + + Instructions how to format approvers comments before storing them into metadata. + EXPERIMENTAL + + + true + 3.7.1 + WfConfigurationType.approverCommentsFormatting + + + + + + + WfConfigurationType.primaryChangeProcessor + 510 + + + + + + + WfConfigurationType.generalChangeProcessor + + + + + + + + + + + Instructions how to format approvers/reviewers comments before storing them into metadata. + Normally midPoint stores comments as they were entered by performers. However, each deployment can + tailor these e.g. by including performer name along with the comment. + EXPERIMENTAL + + + true + 3.7.1 + + + + + + + How to construct the comment. For example: performer.fullName + ': ' + output.comment. + + Available variables: + - performer (i.e. reviewer or approver), + - output (of AbstractWorkItemOutputType), + - workItem (of AbstractWorkItemType - only for certification), + - event (of WorkItemCompletionEventType - only for approvals). + + + + + + + Whether to include the particular output in comments. For example: output.comment != null. + Null or empty values are skipped regardless of the condition. + + + + + + + + + + Configuration related to tasks in which model operations are executed. + EXPERIMENTAL + + + true + true + 3.6.1 + + + + + + + Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). + + + 100 + WfExecutionTasksConfigurationType.serialization + + + + + + + TODO + + + 3.7 + 110 + WfExecutionTasksConfigurationType.executionConstraints + + + + + + + + + + + Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). + EXPERIMENTAL + + + true + true + 3.6.1 + + + + + + + Whether this feature is enabled. Default is true if "serialization" element is present; false otherwise. + + + WfExecutionTasksSerializationType.enabled + 100 + + + + + + + Scope of serialization. The default is "object". If multiple scopes are defined, serialization occurs on each one. + + + WfExecutionTasksSerializationType.scope + 110 + + + + + + + Interval after which the execution task is to be rescheduled in case of conflict. Default is 10 seconds. + + + WfExecutionTasksSerializationType.retryAfter + 120 + + + + + + + TODO + + + WfExecutionTasksSerializationType.groupPrefix + 130 + + + + + + + + + + + Scope of execution task serialization. + + + + + + + + + + No two workflow execution tasks from a single operation are allowed to execute at once. + + + + + + + + + + No two workflow execution tasks on a given object are allowed to execute at once. + + + + + + + + + + No two workflow execution tasks related to give target are allowed to execute at once. + Note that the information on target is not always available (e.g. when executing changes + that do not require approval), so this may not be absolutely reliable. + + + + + + + + + + No two workflow execution tasks are allowed to execute at once. + + + + + + + + + + + + + How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, + automaticallyApproved items in AbstractRoleType? + + + + + + + + + + The legacy approvers specification is never used. + + + + + + + + + + The legacy approvers specification is always used. It is used before any other (policy-based) approval actions. + + + + + + + + + + The legacy approvers specification is used if there's no explicit approval policy applicable to a given + target. + + + + + + + + + + + + + Whether to use default approval policy rules. + + + + + + + + + + Default approval policy rules are never used. + + + + + + + + + + Default approval policy rules are used if there are no applicable approval policy actions. + + + + + + + + + + + + + Configuration for workflow change processor. + + + true + + + + + + + WfChangeProcessorConfigurationType.enabled + 100 + + + + + + + Order in which the change processor should be invoked. (Unspecified means "at the end".) + NOT IMPLEMENTED YET. + + + + + + + + + + + Configuration for GeneralChangeProcessor. + + + true + + + + + + + + + + + + + + + A scenario for GeneralChangeProcessor. + + + + + + + Is this scenario enabled? + + + + + + + A human-readable name of the scenario (e.g. "Approving assignments of roles R1001-R1999 to users in XYZ organization"). + + + + + + + A condition controlling whether this scenario applies, i.e. whether a defined approval process should be started. + + + + + + + A name of the approval process. When the above condition is met, this process is started. It has to evaluate the situation, + seek user's (or users') approval(s), modifying the situation if necessary. + + + + + + + The name of the Spring bean used for customizations. It provides e.g. a method for externalizing process state, + a method for providing work item contents, and so on. + + + + + + + + + + Configuration for PrimaryChangeProcessor. + + + + + + + + + + + + PrimaryChangeProcessorConfigurationType.policyRuleBasedAspect + 200 + + + + + + + PrimaryChangeProcessorConfigurationType.addAssociationAspect + 210 + + + + + + + + + + + + Configuration for a primary change processor aspect. + + Some aspects do not require any configuration - for example, role and resource assignment ones. + They take all the approver information directly from the object (role or resource) being assigned. + However, there are some others (namely, role/resource/user/whatever add/modify aspects) that need + the explicit information about approver(s) in order to know where to route the request. + + For the former aspects, the approver information specified here takes precedence over + approver information derived from the objects being used (e.g. role or resource). + More specifically, if any approver information is here, no approver information is + taken from the objects. This could be changed (e.g. by allowing to tune this behavior) + in the future. + + + + + + + + + + Whether the aspect is enabled or not. + If not specified (but if aspect configuration is present), it is assumed to be true. + However, if the whole aspect configuration is absent, only aspects marked as "enabled-by-default" are enabled. + + + PcpAspectConfigurationType.enabled + 100 + + + + + + + Approvers for this aspect. The approver is a person (or group) that approves carrying out + action(s) relevant to this aspect. This reference may point to object of type UserType of OrgType. + + + PcpAspectConfigurationType.approverRef + 110 + + + + + + + Approvers for this aspect. If specified, the expression(s) are evaluated and the result + is used as a set of approvers (UserType, OrgType, or any combination of them). + May be used with approverRef element(s). + + + PcpAspectConfigurationType.approverExpression + 120 + + + + + + + More complex (multi-stage) approval schema. If used, it overrides both + approverRef and approverExpression elements. + + + PcpAspectConfigurationType.approvalSchema + 130 + + + + + + + Name of custom approval process. If used, it overrides + approverRef, approverExpression, and approvalSchema elements. + + For explicitness, only one of approverRef(s)/approverExpression(s), + approvalSchema and approvalProcess should be specified. + + THIS PROPERTY (approvalProcess) IS NOT SUPPORTED YET. + + + + + + + Condition specifying when the item is automatically approved (e.g. "user is + from Board of Directors"). This is an expression that should yield a boolean value. + + + PcpAspectConfigurationType.automaticallyApproved + 140 + + + + + + + Condition specifying if the workflow should be started in the first place. + This is an expression that should yield a boolean value. It gets 'itemToApprove' parameter + that contains item to be approved - it is aspect-specific: might be e.g. an assignment, + an association + resource shadow discriminator, etc. + + The difference between applicabilityCondition and automaticallyApproved is that if the + former yields false, workflow is not even started. If it yields true, workflow is started, + and then 'automaticallyApproved' is evaluated. If it yields false, manual approval is + required. If true, item is automatically approved. + + CURRENTLY IMPLEMENTED ONLY IN AddAssociationAspect. + + + PcpAspectConfigurationType.applicabilityCondition + 150 + + + + + + + + + + + A generic configuration for a wf aspect. + + It is meant for non-standard aspects. (Standard aspects use named properties in + PrimaryChangeProcessorConfigurationType container.) + + + + + + + + + + + + Name of the aspect bean. + + + GenericPcpAspectConfigurationType.name + + + + + + + + + + + + Container for association-to-be-added in the context: resource shadow discriminator. + + + + + + + + + + Association to be added. + + + + + + + To which resource/kind/intent to add it. + + + + + + + + + + + + Describes the approval context, i.e. what has to be approved, the approval schema, and so on. + + + + + + + + + + + + + + + + + + + + + + + + DEPRECATED + We need to decide what to do with this. + + + + + + + + + + + + + + TODO Replace by forms eventually + TEMPORARY + + + + + + + + 3.7 + + + + + + + true + 3.7 + + + + + + + + + + + TODO Replace by forms eventually. + TEMPORARY + + + + + + + 3.7 + + + + + + + true + 3.7 + + + + + + + + + + + Why was this process started? For processes based on policy rules we define it via relevant policy rules. + (For legacy processes we don't provide this kind of information.) + + EXPERIMENTAL + + + + + + + + + + + TODO + + Note that the rule should be triggered. All irrelevant (non-approval) action types should be removed. + + + + + + + + + + + + + TODO + EXPERIMENTAL + + By default (when the base is not specified), positive time intervals are meant "after work item start". + Negative time intervals are meant "before work item deadline". + + + 3.6 + + + + + + + + + + + + + TODO + + + + 3.6 + + + + + + + Time will be taken relative to the deadline. (This is the default for zero or negative values.) + + + + + + + + + + Time will be taken relative to the work item creation timestamp. (This is the default for positive values.) + + + + + + + + + + + + + + Kind of operation. + + + 3.6 + + + + + + + + Complete (approve/reject) operation. (Explicit or automated.) + + + + + + + + + + Delegate operation. (Explicit or automated.) + + + + + + + + + + Escalate operation. (Explicit or automated.) + + + + + + + + + + Claim operation. + + + + + + + + + + Claim operation. + + + + + + + + + + Cancel operation. Work item was cancelled as a result of other action. (E.g. another work item + was completed, resulting in process or stage completion. Or the process was cancelled/deleted + externally.) + + + + + + + + + +
diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java index ae9d1825b7e..eb7dbc36522 100644 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java @@ -96,11 +96,11 @@ ChangesByState getChangesByState(CaseType approvalCase, CaseType rootCase, Model * * Does not need authorization checks before execution; it uses model calls in order to gather any information needed. * - * @param taskOid OID of an approval task that should be analyzed + * @param caseOid OID of an approval case that should be analyzed * @param opTask task under which this operation is carried out * @param parentResult operation result */ - ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String taskOid, Task opTask, OperationResult parentResult) + ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String caseOid, Task opTask, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java index aad55ec9abd..4f48af01c91 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java @@ -95,19 +95,23 @@ private ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInforma return rv; } rv.setCurrentStageNumber(aCase.getStageNumber()); - Integer currentStageNumber = !purePreview ? aCase.getStageNumber() : 0; - if (currentStageNumber == null) { - result.recordFatalError("Information on current stage number in " + aCase + " is missing or not accessible."); - return rv; + int currentStageNumber; + if (purePreview) { + currentStageNumber = 0; + } else { + if (aCase.getStageNumber() != null) { + currentStageNumber = aCase.getStageNumber(); + } else { + result.recordFatalError("Information on current stage number in " + aCase + " is missing or not accessible."); + return rv; + } } List stagesDef = ApprovalContextUtil.sortAndCheckStages(approvalSchema); for (ApprovalStageDefinitionType stageDef : stagesDef) { ApprovalStageExecutionInformationType stageExecution = new ApprovalStageExecutionInformationType(prismContext); stageExecution.setNumber(stageDef.getNumber()); stageExecution.setDefinition(stageDef); - if (stageDef.getNumber() <= currentStageNumber) { - stageExecution.setExecutionRecord(createStageExecutionRecord(aCase, stageDef.getNumber(), currentStageNumber)); - } else { + if (stageDef.getNumber() > currentStageNumber) { stageExecution.setExecutionPreview(createStageExecutionPreview(aCase, opTask.getChannel(), stageDef, opTask, result)); } rv.getStage().add(stageExecution); @@ -136,17 +140,4 @@ private ApprovalStageExecutionPreviewType createStageExecutionPreview(CaseType a } return rv; } - - private ApprovalStageExecutionRecordType createStageExecutionRecord(CaseType aCase, Integer stageNumberObject, - int currentStageNumber) { - int stageNumber = stageNumberObject; - ApprovalStageExecutionRecordType rv = new ApprovalStageExecutionRecordType(prismContext); - aCase.getEvent().stream() - .filter(e -> e.getStageNumber() != null && e.getStageNumber() == stageNumber) - .forEach(e -> rv.getEvent().add(e.clone())); - if (stageNumber == currentStageNumber) { - rv.getWorkItem().addAll(CloneUtil.cloneCollectionMembers(aCase.getWorkItem())); - } - return rv; - } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java index a98b6d72f9a..1d925f7c9fc 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java @@ -169,13 +169,13 @@ public ChangesByState getChangesByState(CaseType approvalCase, CaseType rootCase } @Override - public ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String taskOid, Task opTask, + public ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String caseOid, Task opTask, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException { OperationResult result = parentResult.createSubresult(DOT_INTERFACE + "getApprovalSchemaExecutionInformation"); try { - return approvalSchemaExecutionInformationHelper.getApprovalSchemaExecutionInformation(taskOid, opTask, result); + return approvalSchemaExecutionInformationHelper.getApprovalSchemaExecutionInformation(caseOid, opTask, result); } catch (Throwable t) { result.recordFatalError("Couldn't determine schema execution information: " + t.getMessage(), t); throw t; diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java index 359389a2a65..f52ca58601a 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java @@ -7,13 +7,11 @@ package com.evolveum.midpoint.wf.impl; +import com.evolveum.midpoint.model.api.WorkflowService; import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.impl.AbstractModelImplementationIntegrationTest; import com.evolveum.midpoint.model.impl.lens.Clockwork; -import com.evolveum.midpoint.prism.Item; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismReference; -import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; @@ -21,9 +19,11 @@ import com.evolveum.midpoint.schema.RelationRegistry; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CaseWorkItemUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskExecutionStatus; @@ -86,6 +86,7 @@ public abstract class AbstractWfTest extends AbstractModelImplementationIntegrat @Autowired protected WorkflowManager workflowManager; @Autowired protected WorkflowEngine workflowEngine; @Autowired protected WorkItemManager workItemManager; + @Autowired protected WorkflowService workflowService; @Autowired protected PrimaryChangeProcessor primaryChangeProcessor; @Autowired protected GeneralChangeProcessor generalChangeProcessor; @Autowired protected SystemObjectCache systemObjectCache; @@ -330,6 +331,22 @@ protected ObjectQuery getOpenItemsQuery() { .build(); } + protected void approveWorkItem(CaseWorkItemType workItem, Task task, OperationResult result) throws CommunicationException, + ObjectNotFoundException, ObjectAlreadyExistsException, PolicyViolationException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + workflowService.completeWorkItem(WorkItemId.of(workItem), + new AbstractWorkItemOutputType(prismContext).outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE), + task, result); + } + + protected void rejectWorkItem(CaseWorkItemType workItem, Task task, OperationResult result) throws CommunicationException, + ObjectNotFoundException, ObjectAlreadyExistsException, PolicyViolationException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + workflowService.completeWorkItem(WorkItemId.of(workItem), + new AbstractWorkItemOutputType(prismContext).outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT), + task, result); + } + public class RelatedCases { private CaseType approvalCase; private CaseType requestCase; @@ -367,4 +384,18 @@ protected CaseAsserter assertCase(OperationResult result, String message) assertThat(caseOid).as("No background case OID").isNotNull(); return assertCase(caseOid, message); } + + /** + * Takes case from the work item (via parent reference). + */ + protected CaseAsserter assertCase(CaseWorkItemType workItem, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + PrismContainerable parent = workItem.asPrismContainerValue().getParent(); + assertThat(parent).isNotNull(); + //noinspection unchecked + PrismContainerValue grandParent = ((PrismContainer) parent).getParent(); + assertThat(grandParent).isNotNull(); + String approvalCaseOid = ((PrismObjectValue) grandParent).getOid(); + assertThat(approvalCaseOid).as("No parent case OID").isNotNull(); + return assertCase(approvalCaseOid, message); + } } diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java index 8c10493ab98..1db88ec04d4 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java @@ -146,13 +146,6 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_JSON); } - @Override - protected TracingProfileType getTestMethodTracingProfile() { - return null; -// return createModelAndWorkflowLoggingTracingProfile() -// .fileNamePattern(TEST_METHOD_TRACING_FILENAME_PATTERN); - } - @Test public void test102AddRoles123AssignmentYYYYDeputy() throws Exception { login(userAdministrator); diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java new file mode 100644 index 00000000000..8fb882b1cec --- /dev/null +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.wf.impl.other; + +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.SerializationOptions; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.SchemaConstantsGenerated; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.*; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.TestResource; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.wf.impl.AbstractWfTestPolicy; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +import static com.evolveum.midpoint.schema.util.ApprovalSchemaExecutionInformationUtil.getEmbeddedCaseBean; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests the preview feature: + * 1) before operation is executed, + * 2) in various stages of approval process as well. + */ +@ContextConfiguration(locations = {"classpath:ctx-workflow-test-main.xml"}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class TestPreview extends AbstractWfTestPolicy { + + private static final File TEST_RESOURCE_DIR = new File("src/test/resources/preview"); + + /* + * Alice is given lab-manager role. This assignment should be approved in three stages: + * 1. by Jane the Lab Owner + * 2. by Martin the Department Head + * 3. By Peter the Dean *OR* Kate the Administrator + */ + private static final TestResource ROLE_LAB_MANAGER = new TestResource<>(TEST_RESOURCE_DIR, "role-lab-manager.xml", "e4b5d89a-cb7f-4d26-b31f-e86556e2a4ca"); + private static final TestResource USER_ALICE = new TestResource<>(TEST_RESOURCE_DIR, "user-alice.xml", "0b728b21-1649-40d2-80dd-566a5faaeb86"); + private static final TestResource USER_JANE = new TestResource<>(TEST_RESOURCE_DIR, "user-jane-the-lab-owner.xml", "feb34927-7671-401e-9f5b-8f7ec94f3112"); + private static final TestResource USER_MARTIN = new TestResource<>(TEST_RESOURCE_DIR, "user-martin-the-dept-head.xml", "072bf16a-e424-456c-a212-7996f34c3c5c"); + private static final TestResource USER_PETER = new TestResource<>(TEST_RESOURCE_DIR, "user-peter-the-dean.xml", "408beff8-c988-4c77-ac5e-ed26697d6982"); + private static final TestResource USER_KATE = new TestResource<>(TEST_RESOURCE_DIR, "user-kate-the-administrator.xml", "4aab211b-5faf-45e2-acaf-a17a89d39fd1"); + + @Override + protected PrismObject getDefaultActor() { + return userAdministrator; + } + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + repoAdd(USER_ALICE, initResult); + repoAdd(USER_JANE, initResult); + repoAdd(USER_MARTIN, initResult); + repoAdd(USER_PETER, initResult); + repoAdd(USER_KATE, initResult); + repoAdd(ROLE_LAB_MANAGER, initResult); + + DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); + } + + /** + * Use pure "previewChanges" and check the approval schema execution information. + */ + @Test + public void test100PurePreview() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + ModelExecuteOptions options = new ModelExecuteOptions(); + options.getOrCreatePartialProcessing().setApprovals(PartialProcessingTypeType.PROCESS); + + when(); + ModelContext modelContext = + modelInteractionService.previewChanges(getAssignmentDeltas(), options, task, result); + + then(); + ApprovalSchemaExecutionInformationType execInfo = getSingleExecutionInformation(modelContext); + displayExecutionInformation(execInfo); + + // Everything is "in the future", so we have to obtain approvers information from the execInfo.stage list + assertThat(getFutureStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_JANE.oid); + assertThat(getFutureStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_MARTIN.oid); + assertThat(getFutureStageApprovers(execInfo, 3)).as("stage 3 approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Start the execution (i.e. case will be created) and check the approval schema execution information + * just after that. + */ + @Test + public void test110InfoAfterStart() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + executeChanges(getAssignmentDeltas(), null, task, result); + + then(); + CaseType approvalCase = assertCase(result, "after") + .display() + .subcases() + .single() + .display() + .assertStageNumber(1) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (1) approvers").containsExactlyInAnyOrder(USER_JANE.oid); + + // Everything after stage 1 is "in the future", so we have to obtain approvers information from the execInfo.stage list + assertThat(getFutureStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_MARTIN.oid); + assertThat(getFutureStageApprovers(execInfo, 3)).as("stage 3 approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Approve the work item by administrator; this will move the case into second stage. + */ + @Test + public void test120InfoAfterStageOneApproval() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + CaseWorkItemType workItem = getWorkItem(task, result); + approveWorkItem(workItem, task, result); + + then(); + CaseType approvalCase = assertCase(workItem, "after") + .display() + .assertStageNumber(2) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getPastStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (2) approvers").containsExactlyInAnyOrder(USER_MARTIN.oid); + // Everything after stage 2 is "in the future", so we have to obtain approvers information from the execInfo.stage list + assertThat(getFutureStageApprovers(execInfo, 3)).as("stage 3 approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Approve the work item again by administrator; this will move the case into the third stage. + */ + @Test + public void test130InfoAfterStageTwoApproval() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + CaseWorkItemType workItem = getWorkItem(task, result); + approveWorkItem(workItem, task, result); + + then(); + CaseType approvalCase = assertCase(workItem, "after") + .display() + .assertStageNumber(3) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getPastStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getPastStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (3) approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Approve one of the work items again by administrator. The other work item will be still open. + */ + @Test + public void test140InfoAfterStageThreeFirstApproval() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + List workItems = getWorkItems(task, result); + CaseWorkItemType workItem = workItems.stream() + .filter(wi -> MiscUtil.extractSingleton(wi.getAssigneeRef()).getOid().equals(USER_PETER.oid)) + .findFirst().orElseThrow(() -> new AssertionError("No Peter's work item")); + approveWorkItem(workItem, task, result); + + then(); + CaseType approvalCase = assertCase(workItem, "after") + .display() + .assertStageNumber(3) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getPastStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getPastStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (3) approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID, USER_KATE.oid); + assertThat(getOpenWorkItemsApprovers(execInfo)).as("open work items approvers").containsExactlyInAnyOrder(USER_KATE.oid); + } + + private Collection getPastStageApprovers(ApprovalSchemaExecutionInformationType execInfo, int stageNumber) { + return getApproversFromCompletionEvents(execInfo, stageNumber); + } + + private Collection getCurrentStageApprovers(ApprovalSchemaExecutionInformationType execInfo) { + Set approvers = new HashSet<>(getOpenWorkItemsApprovers(execInfo)); + approvers.addAll(getApproversFromCompletionEvents(execInfo, execInfo.getCurrentStageNumber())); + return approvers; + } + + private Collection getFutureStageApprovers(ApprovalSchemaExecutionInformationType executionInfo, int stageNumber) { + ApprovalStageExecutionInformationType stage = ApprovalSchemaExecutionInformationUtil.getStage(executionInfo, stageNumber); + assertThat(stage).isNotNull(); + assertThat(stage.getExecutionPreview()).isNotNull(); + return stage.getExecutionPreview().getExpectedApproverRef().stream() + .map(ObjectReferenceType::getOid) + .collect(Collectors.toSet()); + } + + private Collection getOpenWorkItemsApprovers(ApprovalSchemaExecutionInformationType execInfo) { + CaseType aCase = getEmbeddedCaseBean(execInfo); + return aCase.getWorkItem().stream() + .filter(wi -> java.util.Objects.equals(wi.getStageNumber(), aCase.getStageNumber())) + .filter(CaseWorkItemUtil::isCaseWorkItemNotClosed) + .flatMap(wi -> wi.getAssigneeRef().stream()) + .map(ObjectReferenceType::getOid) + .collect(Collectors.toSet()); + } + + // We could use either (closed) work items or WorkItemCompletionEventType events. The latter is better because we can also + // attorney information there. + private Collection getApproversFromCompletionEvents(ApprovalSchemaExecutionInformationType executionInfo, int stageNumber) { + CaseType aCase = getEmbeddedCaseBean(executionInfo); + return aCase.getEvent().stream() + .filter(event -> event instanceof WorkItemCompletionEventType) + .map(event -> (WorkItemCompletionEventType) event) + .filter(event -> java.util.Objects.equals(event.getStageNumber(), stageNumber)) + .filter(CaseEventUtil::completedByUserAction) + .map(event -> event.getInitiatorRef().getOid()) + .collect(Collectors.toSet()); + } + + private ApprovalSchemaExecutionInformationType getSingleExecutionInformation(ModelContext modelContext) { + List approvalsExecutionList = + modelContext.getHookPreviewResults(ApprovalSchemaExecutionInformationType.class); + assertThat(approvalsExecutionList).size().as("approvals execution list").isEqualTo(1); + return approvalsExecutionList.get(0); + } + + private void displayExecutionInformation(ApprovalSchemaExecutionInformationType execution) throws SchemaException { + String xml = prismContext.xmlSerializer().options(SerializationOptions.createSerializeCompositeObjects()) + .root(SchemaConstantsGenerated.C_APPROVAL_SCHEMA_EXECUTION_INFORMATION).serialize(execution.asPrismContainerValue()); + displayValue("execution information", xml); + } + + private List> getAssignmentDeltas() throws SchemaException { + return Collections.singletonList( + deltaFor(UserType.class) + .item(UserType.F_ASSIGNMENT) + .add(ObjectTypeUtil.createAssignmentTo(ROLE_LAB_MANAGER.oid, ObjectTypes.ROLE, prismContext)) + .asObjectDeltaCast(USER_ALICE.oid)); + } +} diff --git a/model/workflow-impl/src/test/resources/preview/role-lab-manager.xml b/model/workflow-impl/src/test/resources/preview/role-lab-manager.xml new file mode 100644 index 00000000000..aaed05871a0 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/role-lab-manager.xml @@ -0,0 +1,40 @@ + + + + lab-manager + + + + + add + + + + + + + 1 + + + + 2 + + + + 3 + + + allMustApprove + + + + + + + diff --git a/model/workflow-impl/src/test/resources/preview/user-alice.xml b/model/workflow-impl/src/test/resources/preview/user-alice.xml new file mode 100644 index 00000000000..59c9b063aaa --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-alice.xml @@ -0,0 +1,11 @@ + + + + alice + diff --git a/model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml b/model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml new file mode 100644 index 00000000000..8c805f5e9d1 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml @@ -0,0 +1,12 @@ + + + + jane + Jane the Lab Owner + diff --git a/model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml b/model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml new file mode 100644 index 00000000000..d751088f59a --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml @@ -0,0 +1,12 @@ + + + + kate + Kate the Administrator + diff --git a/model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml b/model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml new file mode 100644 index 00000000000..8542f134b00 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml @@ -0,0 +1,12 @@ + + + + martin + Martin the Dept Head + diff --git a/model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml b/model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml new file mode 100644 index 00000000000..f89cfbb8103 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml @@ -0,0 +1,12 @@ + + + + peter + Peter the Dean + diff --git a/model/workflow-impl/testng-integration.xml b/model/workflow-impl/testng-integration.xml index a5988623f9a..d53f44f0519 100644 --- a/model/workflow-impl/testng-integration.xml +++ b/model/workflow-impl/testng-integration.xml @@ -35,6 +35,7 @@ + diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index 8a7b1a268c4..4934230d87d 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -35,6 +35,9 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; + +import com.evolveum.midpoint.schema.result.CompiledTracingProfile; + import org.apache.commons.lang.SystemUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -264,18 +267,11 @@ public void startTestContext(ITestResult testResult) throws SchemaException { displayTestTitle(testName); Task task = createTask(testMethodName); - // TODO inttest: add tracing facility - ideally without the need to create subresult -// OperationResult rootResult = task.getResult(); -// TracingProfileType tracingProfile = getTestMethodTracingProfile(); -// CompiledTracingProfile compiledTracingProfile = tracingProfile != null ? -// tracer.compileProfile(tracingProfile, rootResult) : null; -// OperationResult result = rootResult.subresult(task.getName() + "Run") -// .tracingProfile(compiledTracingProfile) -// .build(); - - // This is quite a hack. We need to provide traced result to all clients that need to access it via the task. - // (I.e. not via the test context.) -// task.setResult(result); + TracingProfileType tracingProfile = getTestMethodTracingProfile(); + if (tracingProfile != null) { + CompiledTracingProfile compiledTracingProfile = tracer.compileProfile(tracingProfile, task.getResult()); + task.getResult().tracingProfile(compiledTracingProfile); + } MidpointTestContextWithTask.create(testClass, testMethodName, task, task.getResult()); tracer.setTemplateParametersCustomizer(params -> { From fc386727e0412716a88d45c0a0ab61c914ebb602 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Mon, 20 Apr 2020 19:28:46 +0200 Subject: [PATCH 16/27] removing needless println --- .../evolveum/midpoint/prism/impl/PrismContainerValueImpl.java | 1 - 1 file changed, 1 deletion(-) 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 6e9cbe6e012..dc4ff64faa0 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 @@ -1649,7 +1649,6 @@ public void keepPaths(List keep) throws SchemaException { ItemPath itemPath = item.getPath().removeIds(); 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) { From c4a9f23f95aebf18b0efa06380d274f3252f2f76 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 21 Apr 2020 14:45:40 +0200 Subject: [PATCH 17/27] Indirect checkbox visibility on the org members panel --- .../admin/roles/AbstractRoleMemberPanel.java | 2496 ++++++++--------- 1 file changed, 1248 insertions(+), 1248 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java index 372024a1c0e..2a7b6c647da 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java @@ -1,1248 +1,1248 @@ -/* - * Copyright (c) 2010-2017 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.web.page.admin.roles; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.gui.impl.component.icon.CompositedIcon; -import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; -import com.evolveum.midpoint.model.api.AssignmentCandidatesSpecification; -import com.evolveum.midpoint.model.api.AssignmentObjectRelation; -import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.query.QueryFactory; -import com.evolveum.midpoint.prism.query.builder.S_FilterEntryOrEmpty; -import com.evolveum.midpoint.schema.constants.RelationTypes; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.web.component.MultiFunctinalButtonDto; -import com.evolveum.midpoint.web.component.MultifunctionalButton; -import com.evolveum.midpoint.web.component.data.column.ColumnUtils; -import com.evolveum.midpoint.web.component.dialog.ConfigureTaskConfirmationPanel; -import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; -import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; -import com.evolveum.midpoint.web.component.util.SelectableBean; -import com.evolveum.midpoint.web.page.admin.configuration.component.HeaderMenuAction; -import com.evolveum.midpoint.web.session.MemberPanelStorage; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.wicket.Component; -import org.apache.wicket.RestartResponseException; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; -import org.apache.wicket.ajax.form.OnChangeAjaxBehavior; -import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.markup.html.form.IChoiceRenderer; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; - -import com.evolveum.midpoint.gui.api.GuiStyleConstants; -import com.evolveum.midpoint.gui.api.component.BasePanel; -import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; -import com.evolveum.midpoint.gui.api.page.PageBase; -import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.prism.PrismConstants; -import com.evolveum.midpoint.prism.query.ObjectFilter; -import com.evolveum.midpoint.prism.query.ObjectQuery; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SystemException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.web.component.dialog.ChooseFocusTypeAndRelationDialogPanel; -import com.evolveum.midpoint.web.component.form.CheckFormGroup; -import com.evolveum.midpoint.web.component.form.DropDownFormGroup; -import com.evolveum.midpoint.web.component.input.QNameObjectTypeChoiceRenderer; -import com.evolveum.midpoint.web.component.input.RelationDropDownChoicePanel; -import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; -import com.evolveum.midpoint.web.component.search.Search; -import com.evolveum.midpoint.web.component.search.SearchFactory; -import com.evolveum.midpoint.web.component.util.EnableBehaviour; -import com.evolveum.midpoint.web.component.util.VisibleBehaviour; -import com.evolveum.midpoint.web.page.admin.configuration.component.ChooseTypePanel; -import com.evolveum.midpoint.web.page.admin.dto.ObjectViewDto; -import com.evolveum.midpoint.web.security.GuiAuthorizationConstants; -import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; - -import org.apache.wicket.model.StringResourceModel; - -import static com.evolveum.midpoint.web.component.data.column.ColumnUtils.createStringResource; - -public abstract class AbstractRoleMemberPanel extends BasePanel { - - private static final long serialVersionUID = 1L; - - protected enum QueryScope { - SELECTED, ALL, ALL_DIRECT - } - - protected enum MemberOperation { - ADD, REMOVE, RECOMPUTE - } - - private static final Trace LOGGER = TraceManager.getTrace(AbstractRoleMemberPanel.class); - private static final String DOT_CLASS = AbstractRoleMemberPanel.class.getName() + "."; - protected static final String OPERATION_LOAD_MEMBER_RELATIONS = DOT_CLASS + "loadMemberRelationsList"; - protected static final String OPERATION_LOAD_ARCHETYPE_OBJECT = DOT_CLASS + "loadArchetypeObject"; - - protected static final String ID_FORM = "form"; - - protected static final String ID_CONTAINER_MEMBER = "memberContainer"; - protected static final String ID_CHILD_TABLE = "childUnitTable"; - protected static final String ID_MEMBER_TABLE = "memberTable"; - - private static final String ID_OBJECT_TYPE = "type"; - private static final String ID_TENANT = "tenant"; - private static final String ID_PROJECT = "project"; - private static final String ID_INDIRECT_MEMBERS = "indirectMembers"; - - protected static final String ID_SEARCH_SCOPE = "searchScope"; - protected SearchBoxScopeType scopeDefaultValue = null; - protected QName objectTypeDefaultValue = null; - - protected static final String ID_SEARCH_BY_RELATION = "searchByRelation"; - - private static Map> authorizations = new HashMap<>(); - private static Map tablesId = new HashMap<>(); - - static { - tablesId.put(RoleType.COMPLEX_TYPE, TableId.ROLE_MEMEBER_PANEL); - tablesId.put(ServiceType.COMPLEX_TYPE, TableId.SERVICE_MEMEBER_PANEL); - tablesId.put(OrgType.COMPLEX_TYPE, TableId.ORG_MEMEBER_PANEL); - tablesId.put(ArchetypeType.COMPLEX_TYPE, TableId.ARCHETYPE_MEMEBER_PANEL); - } - - static { - authorizations.put(RoleType.COMPLEX_TYPE, GuiAuthorizationConstants.ROLE_MEMBERS_AUTHORIZATIONS); - authorizations.put(ServiceType.COMPLEX_TYPE, GuiAuthorizationConstants.SERVICE_MEMBERS_AUTHORIZATIONS); - authorizations.put(OrgType.COMPLEX_TYPE, GuiAuthorizationConstants.ORG_MEMBERS_AUTHORIZATIONS); - authorizations.put(ArchetypeType.COMPLEX_TYPE, GuiAuthorizationConstants.ARCHETYPE_MEMBERS_AUTHORIZATIONS); - } - - public AbstractRoleMemberPanel(String id, IModel model) { - super(id, model); - } - - @Override - protected void onInitialize(){ - super.onInitialize(); - initLayout(); - } - - protected void initLayout() { - Form form = new com.evolveum.midpoint.web.component.form.Form(ID_FORM); - form.setOutputMarkupId(true); - add(form); - initDefaultSearchParameters(); - initSearch(form); - initMemberTable(form); - setOutputMarkupId(true); - - } - - private void initDefaultSearchParameters(){ - GuiObjectListPanelConfigurationType additionalPanel = getAdditionalPanelConfig(); - if (additionalPanel != null && additionalPanel.getSearchBoxConfiguration() != null) { - scopeDefaultValue = additionalPanel.getSearchBoxConfiguration().getDefaultScope(); - objectTypeDefaultValue = additionalPanel.getSearchBoxConfiguration().getDefaultObjectType(); - } - if (scopeDefaultValue == null){ - scopeDefaultValue = SearchBoxScopeType.ONE_LEVEL; - } - if (objectTypeDefaultValue == null){ - objectTypeDefaultValue = WebComponentUtil.classToQName(getPrismContext(), getDefaultObjectType()); - } - if (getMemberPanelStorage() != null){ - if (getMemberPanelStorage().getOrgSearchScope() == null){ - getMemberPanelStorage().setOrgSearchScope(scopeDefaultValue); - } - if (getMemberPanelStorage().getType() == null){ - getMemberPanelStorage().setType(ObjectTypes.getObjectType(objectTypeDefaultValue.getLocalPart())); - } - } - } - - protected Form getForm() { - return (Form) get(ID_FORM); - } - - private void initMemberTable(Form form) { - WebMarkupContainer memberContainer = new WebMarkupContainer(ID_CONTAINER_MEMBER); - memberContainer.setOutputMarkupId(true); - memberContainer.setOutputMarkupPlaceholderTag(true); - form.add(memberContainer); - - PageBase pageBase = getPageBase(); - Class type = getMemberPanelStorage() != null && getMemberPanelStorage().getType() != null ? - getMemberPanelStorage().getType().getClassDefinition() : ObjectType.class; - //TODO QName defines a relation value which will be used for new member creation - MainObjectListPanel childrenListPanel = new MainObjectListPanel( - ID_MEMBER_TABLE, type, getTableId(getComplexTypeQName()), getSearchOptions()) { - - private static final long serialVersionUID = 1L; - - @Override - protected void objectDetailsPerformed(AjaxRequestTarget target, ObjectType object) { - detailsPerformed(target, object); - } - - @Override - protected boolean isObjectDetailsEnabled(IModel> rowModel) { - if (rowModel == null || rowModel.getObject() == null - || rowModel.getObject().getValue() == null) { - return false; - } - Class objectClass = rowModel.getObject().getValue().getClass(); - return WebComponentUtil.hasDetailsPage(objectClass); - } - - @Override - protected DisplayType getNewObjectButtonSpecialDisplayType(){ - return getCreateMemberButtonDisplayType(); - } - - @Override - protected DisplayType getNewObjectButtonStandardDisplayType(){ - return WebComponentUtil.createDisplayType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green", - createStringResource("abstractRoleMemberPanel.menu.createMember", "", "").getString()); - } - - @Override - protected List loadButtonDescriptions() { - return createAdditionalButtonsDescription(); - } - - @Override - protected void newObjectPerformed(AjaxRequestTarget target, AssignmentObjectRelation relation, CompiledObjectCollectionView collectionView) { - AbstractRoleMemberPanel.this.createFocusMemberPerformed(target, relation); - } - - @Override - protected List createToolbarButtonsList(String buttonId){ - List buttonsList = super.createToolbarButtonsList(buttonId); - MultifunctionalButton assignButton = createAssignButton(buttonId); - buttonsList.add(1, assignButton); - return buttonsList; - } - - @Override - protected IColumn, String> createIconColumn(){ - return (IColumn) ColumnUtils.createIconColumn(pageBase); - } - - @Override - protected List, String>> createColumns() { - return createMembersColumns(); - } - - @Override - protected List createInlineMenu() { - return createRowActions(); - } - - @Override - protected Search createSearch() { - return getMemberPanelStorage() != null && getMemberPanelStorage().getSearch() != null ? - getMemberPanelStorage().getSearch() : SearchFactory.createSearch(getDefaultObjectType(), pageBase); - } - - @Override - protected ObjectQuery createContentQuery() { - ObjectQuery q = super.createContentQuery(); - - ObjectQuery members = AbstractRoleMemberPanel.this.createContentQuery(); - - List filters = new ArrayList<>(); - - if (q != null && q.getFilter() != null) { - filters.add(q.getFilter()); - } - - if (members != null && members.getFilter() != null) { - filters.add(members.getFilter()); - } - - QueryFactory queryFactory = pageBase.getPrismContext().queryFactory(); - if (filters.size() == 1) { - return queryFactory.createQuery(filters.iterator().next()); - } else { - return queryFactory.createQuery(queryFactory.createAnd(filters)); - } - } - - @Override - protected GuiObjectListPanelConfigurationType getAdditionalPanelConfig(){ - return AbstractRoleMemberPanel.this.getAdditionalPanelConfig(); - } - - @Override - protected boolean isAdditionalPanel(){ - return true; - } - - protected boolean isTypeChanged(Class newTypeClass){ - return true; - } - }; - childrenListPanel.setOutputMarkupId(true); - memberContainer.add(childrenListPanel); - } - - private List createAdditionalButtonsDescription() { - List multiFunctinalButtonDtos = new ArrayList<>(); - List loadedRelations = loadMemberRelationsList(); - if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(loadedRelations)) { - List relations = WebComponentUtil.divideAssignmentRelationsByAllValues(loadedRelations); - relations.forEach(relation -> { - MultiFunctinalButtonDto buttonDto = new MultiFunctinalButtonDto(); - DisplayType additionalButtonDisplayType = WebComponentUtil.getAssignmentObjectRelationDisplayType(getPageBase(), relation, - "abstractRoleMemberPanel.menu.createMember"); - buttonDto.setAdditionalButtonDisplayType(additionalButtonDisplayType); - buttonDto.setCompositedIcon(createCompositedIcon(relation, additionalButtonDisplayType)); - buttonDto.setAssignmentObjectRelation(relation); - multiFunctinalButtonDtos.add(buttonDto); - }); - } - return multiFunctinalButtonDtos; - } - - private CompositedIcon createCompositedIcon(AssignmentObjectRelation relation, DisplayType additionalButtonDisplayType) { - CompositedIconBuilder builder = WebComponentUtil.getAssignmentRelationIconBuilder(getPageBase(), relation, - additionalButtonDisplayType.getIcon(), WebComponentUtil.createIconType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green")); - if (builder == null) { - return null; - } - return builder.build(); - } - - private MultifunctionalButton createAssignButton(String buttonId) { - MultifunctionalButton assignButton = new MultifunctionalButton(buttonId, createAssignmentAdditionalButtons()) { - private static final long serialVersionUID = 1L; - - - @Override - protected void buttonClickPerformed(AjaxRequestTarget target, AssignmentObjectRelation relation, CompiledObjectCollectionView collectionView) { - List relations = relation != null && !CollectionUtils.isEmpty(relation.getRelations()) ? - Arrays.asList(relation.getRelations().get(0)) : getSupportedRelations().getAvailableRelationList(); - AvailableRelationDto avariableRelations = new AvailableRelationDto(relations, getSupportedRelations().getDefaultRelation()); - List objectTypes = relation != null && !CollectionUtils.isEmpty(relation.getObjectTypes()) ? - relation.getObjectTypes() : null; - List archetypeRefList = relation != null && !CollectionUtils.isEmpty(relation.getArchetypeRefs()) ? - relation.getArchetypeRefs() : null; - assignMembers(target, avariableRelations, objectTypes, archetypeRefList, relation == null); - } - - @Override - protected DisplayType getMainButtonDisplayType(){ - return getAssignMemberButtonDisplayType(); - } - - @Override - protected DisplayType getDefaultObjectButtonDisplayType(){ - return getAssignMemberButtonDisplayType(); - } - - }; - assignButton.add(AttributeAppender.append("class", "btn-margin-right")); - - - return assignButton; - } - - private List createAssignmentAdditionalButtons() { - List additionalAssignmentButtons = new ArrayList<>(); - List assignmentObjectRelations = WebComponentUtil.divideAssignmentRelationsByAllValues(loadMemberRelationsList()); - if (assignmentObjectRelations == null) { - return additionalAssignmentButtons; - } - assignmentObjectRelations.forEach(relation -> { - MultiFunctinalButtonDto buttonDto = new MultiFunctinalButtonDto(); - buttonDto.setAssignmentObjectRelation(relation); - - DisplayType additionalDispayType = WebComponentUtil.getAssignmentObjectRelationDisplayType(AbstractRoleMemberPanel.this.getPageBase(), relation, - "abstractRoleMemberPanel.menu.assignMember"); - //TODO null additinalDisplayType - CompositedIconBuilder builder = WebComponentUtil.getAssignmentRelationIconBuilder(AbstractRoleMemberPanel.this.getPageBase(), relation, - additionalDispayType.getIcon(), WebComponentUtil.createIconType(GuiStyleConstants.EVO_ASSIGNMENT_ICON, "green")); - CompositedIcon icon = builder.build(); - buttonDto.setAdditionalButtonDisplayType(additionalDispayType); - buttonDto.setCompositedIcon(icon); - additionalAssignmentButtons.add(buttonDto); - }); - - return additionalAssignmentButtons; - - } - - protected TableId getTableId(QName complextType) { - return tablesId.get(complextType); - } - - protected Map getAuthorizations(QName complexType) { - return authorizations.get(complexType); - } - - protected QName getComplexTypeQName() { - return getModelObject().asPrismObject().getComplexTypeDefinition().getTypeName(); - } - - private DisplayType getCreateMemberButtonDisplayType(){ - return WebComponentUtil.createDisplayType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green", - AbstractRoleMemberPanel.this.createStringResource("abstractRoleMemberPanel.menu.createMember", "", "").getString()); - } - - private DisplayType getAssignMemberButtonDisplayType(){ - return WebComponentUtil.createDisplayType(GuiStyleConstants.EVO_ASSIGNMENT_ICON, "green", - AbstractRoleMemberPanel.this.createStringResource("abstractRoleMemberPanel.menu.assignMember", "", "").getString()); - } - - protected List createRowActions() { - List menu = new ArrayList<>(); - createAssignMemberRowAction(menu); - - if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_UNASSIGN)) { - menu.add(new ButtonInlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.unassign")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new HeaderMenuAction(AbstractRoleMemberPanel.this) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - unassignMembersPerformed(target); - } - }; - - } - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_UNASSIGN; - } - }); - } - - createRecomputeMemberRowAction(menu); - - if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_CREATE)) { - menu.add(new InlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.create")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new HeaderMenuAction(AbstractRoleMemberPanel.this) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - createFocusMemberPerformed(target); - } - }; - } - }); - } - if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_DELETE)) { - menu.add(new InlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.delete")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new HeaderMenuAction(AbstractRoleMemberPanel.this) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - deleteMembersPerformed(target); - } - }; - } - - }); - } - return menu; - } - - protected void createAssignMemberRowAction(List menu) { - if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_ASSIGN)) { - menu.add(new InlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.assign")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new HeaderMenuAction(AbstractRoleMemberPanel.this) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - MemberOperationsHelper.assignMembers(getPageBase(), AbstractRoleMemberPanel.this.getModelObject(), target, getSupportedRelations(), null); - } - }; - } - }); - } - } - - protected void createRecomputeMemberRowAction(List menu) { - if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_RECOMPUTE)) { - menu.add(new ButtonInlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.recompute")) { - private static final long serialVersionUID = 1L; - - @Override - public InlineMenuItemAction initAction() { - return new HeaderMenuAction(AbstractRoleMemberPanel.this) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - recomputeMembersPerformed(target); - } - }; - } - -// @Override -// public IModel getConfirmationMessageModel() { -// return getMemberTable().getSelectedObjectsCount() > 0 ? -// createStringResource("abstractRoleMemberPanel.recomputeSelectedMembersConfirmationLabel") -// : createStringResource("abstractRoleMemberPanel.recomputeAllMembersConfirmationLabel"); -// } - - @Override - public String getButtonIconCssClass() { - return GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM; - } - - }); - } - } - - - - protected abstract AvailableRelationDto getSupportedRelations(); - - protected GuiObjectListPanelConfigurationType getAdditionalPanelConfig(){ - return null; - } - - private boolean isAuthorized(String action) { - Map memeberAuthz = getAuthorizations(getComplexTypeQName()); - return WebComponentUtil.isAuthorized(memeberAuthz.get(action)); - } - - - - private List loadMemberRelationsList(){ - List assignmentTargetRelations = new ArrayList<>(); - AssignmentCandidatesSpecification spec = loadCandidateSpecification(); - assignmentTargetRelations = spec != null ? spec.getAssignmentObjectRelations() : new ArrayList<>(); - return assignmentTargetRelations; - } - - private AssignmentCandidatesSpecification loadCandidateSpecification(){ - OperationResult result = new OperationResult(OPERATION_LOAD_MEMBER_RELATIONS); - PrismObject obj = getModelObject().asPrismObject(); - AssignmentCandidatesSpecification spec = null; - try { - spec = getPageBase().getModelInteractionService() - .determineAssignmentHolderSpecification(obj, result); - } catch (SchemaException | ConfigurationException ex){ - result.recordPartialError(ex.getLocalizedMessage()); - LOGGER.error("Couldn't load member relations list for the object {} , {}", obj.getName(), ex.getLocalizedMessage()); - } - return spec; - } - - protected void assignMembers(AjaxRequestTarget target, AvailableRelationDto availableRelationList, - List objectTypes, List archetypeRefList, boolean isOrgTreePanelVisible) { - MemberOperationsHelper.assignMembers(getPageBase(), getModelObject(), target, availableRelationList, - objectTypes, archetypeRefList, isOrgTreePanelVisible); - } - - private void unassignMembersPerformed(AjaxRequestTarget target) { - QueryScope scope = getQueryScope(false); - - ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel(getPageBase().getMainPopupBodyId(), - createStringResource("abstractRoleMemberPanel.unassignAllMembersConfirmationLabel")) { - private static final long serialVersionUID = 1L; - - @Override - protected List getSupportedObjectTypes() { - return AbstractRoleMemberPanel.this.getSupportedObjectTypes(true); - } - - @Override - protected List getSupportedRelations() { - return AbstractRoleMemberPanel.this.getSupportedRelations().getAvailableRelationList(); - } - - @Override - protected boolean isFocusTypeSelectorVisible() { - return !QueryScope.SELECTED.equals(scope); - } - - - protected void okPerformed(QName type, Collection relations, AjaxRequestTarget target) { - unassignMembersPerformed(type, scope, relations, target); - } - - @Override - protected QName getDefaultObjectType() { - return WebComponentUtil.classToQName(AbstractRoleMemberPanel.this.getPrismContext(), - AbstractRoleMemberPanel.this.getDefaultObjectType()); - } - }; - - getPageBase().showMainPopup(chooseTypePopupContent, target); - } - - private void deleteMembersPerformed(AjaxRequestTarget target) { - QueryScope scope = getQueryScope(false); - ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel(getPageBase().getMainPopupBodyId(), - createStringResource("abstractRoleMemberPanel.deleteAllMembersConfirmationLabel")) { - private static final long serialVersionUID = 1L; - - @Override - protected List getSupportedObjectTypes() { - return AbstractRoleMemberPanel.this.getSupportedObjectTypes(true); - } - - @Override - protected List getSupportedRelations() { - return AbstractRoleMemberPanel.this.getSupportedRelations().getAvailableRelationList(); - } - - protected void okPerformed(QName type, Collection relations, AjaxRequestTarget target) { - deleteMembersPerformed(scope, relations, target); - } - - @Override - protected boolean isFocusTypeSelectorVisible() { - return !QueryScope.SELECTED.equals(scope); - } - - @Override - protected QName getDefaultObjectType() { - return WebComponentUtil.classToQName(AbstractRoleMemberPanel.this.getPrismContext(), - AbstractRoleMemberPanel.this.getDefaultObjectType()); - } - }; - - getPageBase().showMainPopup(chooseTypePopupContent, target); - } - - protected void createFocusMemberPerformed(AjaxRequestTarget target) { - createFocusMemberPerformed(target, null); - } - - protected void createFocusMemberPerformed(AjaxRequestTarget target, AssignmentObjectRelation relationSpec) { - if (relationSpec != null){ - try { - List newReferences = new ArrayList<>(); - if (CollectionUtils.isEmpty(relationSpec.getRelations())){ - relationSpec.setRelations(Arrays.asList(RelationTypes.MEMBER.getRelation())); - } - ObjectReferenceType memberRef = ObjectTypeUtil.createObjectRef(AbstractRoleMemberPanel.this.getModelObject(), relationSpec.getRelations().get(0)); - newReferences.add(memberRef); - if (CollectionUtils.isNotEmpty(relationSpec.getArchetypeRefs())) { - newReferences.add(relationSpec.getArchetypeRefs().get(0)); - } - QName newMemberType = CollectionUtils.isNotEmpty(relationSpec.getObjectTypes()) ? relationSpec.getObjectTypes().get(0) : - getSupportedObjectTypes(false).get(0); - WebComponentUtil.initNewObjectWithReference(AbstractRoleMemberPanel.this.getPageBase(), newMemberType, newReferences); - } catch (SchemaException e) { - throw new SystemException(e.getMessage(), e); - } - } else { - ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel( - getPageBase().getMainPopupBodyId()) { - private static final long serialVersionUID = 1L; - - @Override - protected List getSupportedObjectTypes() { - return AbstractRoleMemberPanel.this.getNewMemberObjectTypes(); - } - - @Override - protected List getSupportedRelations() { - return AbstractRoleMemberPanel.this.getSupportedRelations().getAvailableRelationList(); - } - - protected void okPerformed(QName type, Collection relations, AjaxRequestTarget target) { - if (type == null) { - getSession().warn("No type was selected. Cannot create member"); - target.add(this); - target.add(getPageBase().getFeedbackPanel()); - return; - } - if (relations == null || relations.isEmpty()) { - getSession().warn("No relation was selected. Cannot create member"); - target.add(this); - target.add(getPageBase().getFeedbackPanel()); - return; - } - AbstractRoleMemberPanel.this.getPageBase().hideMainPopup(target); - try { - List newReferences = new ArrayList<>(); - for (QName relation : relations) { - newReferences.add(ObjectTypeUtil.createObjectRef(AbstractRoleMemberPanel.this.getModelObject(), relation)); - } - WebComponentUtil.initNewObjectWithReference(AbstractRoleMemberPanel.this.getPageBase(), type, newReferences); - } catch (SchemaException e) { - throw new SystemException(e.getMessage(), e); - } - - } - - @Override - protected QName getDefaultObjectType() { - if (relationSpec != null && CollectionUtils.isNotEmpty(relationSpec.getObjectTypes())) { - return relationSpec.getObjectTypes().get(0); - } - return super.getDefaultObjectType(); - } - - @Override - protected boolean isFocusTypeSelectorVisible() { - return relationSpec == null; - } - }; - - getPageBase().showMainPopup(chooseTypePopupContent, target); - } - } - - protected void deleteMembersPerformed(QueryScope scope, Collection relations, AjaxRequestTarget target) { - if (relations == null || relations.isEmpty()) { - getSession().warn("No relation was selected. Cannot perform delete members"); - target.add(this); - target.add(getPageBase().getFeedbackPanel()); - return; - } - MemberOperationsHelper.deleteMembersPerformed(getPageBase(), scope, getActionQuery(scope, relations), target); - } - - protected void unassignMembersPerformed(QName type, QueryScope scope, Collection relations, AjaxRequestTarget target) { - if (relations == null || relations.isEmpty()) { - getSession().warn("No relation was selected. Cannot perform unassign members"); - target.add(this); - target.add(getPageBase().getFeedbackPanel()); - return; - } - MemberOperationsHelper.unassignMembersPerformed(getPageBase(), getModelObject(), scope, getActionQuery(scope, relations), relations, type, target); - } - - private ObjectViewDto getParameter(String panelId) { - ChooseTypePanel tenantChoice = (ChooseTypePanel) get(createComponentPath(ID_FORM, panelId)); - return tenantChoice.getModelObject(); - } - - protected ObjectQuery getActionQuery(QueryScope scope, Collection relations) { - switch (scope) { - case ALL: - return createAllMemberQuery(relations); - case ALL_DIRECT: - return MemberOperationsHelper.createDirectMemberQuery(getModelObject(), getSearchType().getTypeQName(), relations, getParameter(ID_TENANT), getParameter(ID_PROJECT), getPrismContext()); - case SELECTED: - return MemberOperationsHelper.createSelectedObjectsQuery(getMemberTable().getSelectedObjects(), getPrismContext()); - } - - return null; - } - - protected void initSearch(Form form) { - - List scopeValues = Arrays.asList(SearchBoxScopeType.values()); - DropDownFormGroup searchScrope = createDropDown(ID_SEARCH_SCOPE, - Model.of(getMemberPanelStorage() != null ? getMemberPanelStorage().getOrgSearchScope() : scopeDefaultValue), - scopeValues, - WebComponentUtil.getEnumChoiceRenderer(AbstractRoleMemberPanel.this), - "abstractRoleMemberPanel.searchScope", "abstractRoleMemberPanel.searchScope.tooltip", true); - searchScrope.add(new VisibleBehaviour(() -> getModelObject() instanceof OrgType)); - form.add(searchScrope); - - List supportedTypes = getSupportedObjectTypes(false); - DropDownFormGroup typeSelect = createDropDown(ID_OBJECT_TYPE, - Model.of(getMemberPanelStorage() != null ? getMemberPanelStorage().getType().getTypeQName() : WebComponentUtil.classToQName(getPrismContext(), getDefaultObjectType())), - supportedTypes, new QNameObjectTypeChoiceRenderer(){ - private static final long serialVersionUID = 1L; - - @Override - public Object getDisplayValue(QName qname) { - if (qname == null || getObjectTypesListParentType().equals(qname)){ - return StringUtils.leftPad(createStringResource("ObjectTypes.all").getString(), 1); - } else { - return super.getDisplayValue(qname); - } - } - - @Override - public QName getObject(String id, IModel> choices) { - QName qname = super.getObject(id, choices); - if (qname == null){ - return getObjectTypesListParentType(); - } - return qname; - } - - }, - "abstractRoleMemberPanel.type", "abstractRoleMemberPanel.type.tooltip", false); - form.add(typeSelect); - - RelationDropDownChoicePanel relationSelector = new RelationDropDownChoicePanel(ID_SEARCH_BY_RELATION, - getMemberPanelStorage() != null ? getMemberPanelStorage().getRelation() : getSupportedRelations().getDefaultRelation(), - getSupportedRelations().getAvailableRelationList(), true){ - private static final long serialVersionUID = 1L; - - @Override - protected void onValueChanged(AjaxRequestTarget target){ - refreshAll(target); - } - - @Override - protected String getNullValidDisplayValue(){ - return getString("RelationTypes.ANY"); - } - }; - form.add(relationSelector); - - ChooseTypePanel tenant = createParameterPanel(ID_TENANT, true); - form.add(tenant); - tenant.add(new VisibleBehaviour(() -> getModelObject() instanceof RoleType)); - - ChooseTypePanel project = createParameterPanel(ID_PROJECT, false); - form.add(project); - project.add(new VisibleBehaviour(() -> getModelObject() instanceof RoleType)); - - CheckFormGroup includeIndirectMembers = new CheckFormGroup(ID_INDIRECT_MEMBERS, - Model.of(getMemberPanelStorage() != null ? getMemberPanelStorage().getIndirect() : false), - createStringResource("abstractRoleMemberPanel.indirectMembers"), "abstractRoleMemberPanel.indirectMembers.tooltip", false, "col-md-4", "col-md-2"); - includeIndirectMembers.getCheck().add(new AjaxFormComponentUpdatingBehavior("change") { - - private static final long serialVersionUID = 1L; - - protected void onUpdate(AjaxRequestTarget target) { - refreshAll(target); - } - - }); - - includeIndirectMembers.getCheck().add(new EnableBehaviour(() -> - getSearchScopeValue().equals(SearchBoxScopeType.ONE_LEVEL) || !searchScrope.isVisible())); - includeIndirectMembers.setOutputMarkupId(true); - form.add(includeIndirectMembers); - - } - - protected List getSupportedObjectTypes(boolean includeAbstractTypes) { - return WebComponentUtil.createFocusTypeList(includeAbstractTypes); - } - - protected QName getObjectTypesListParentType(){ - return FocusType.COMPLEX_TYPE; - } - - protected List getNewMemberObjectTypes() { - return WebComponentUtil.createFocusTypeList(); - } - - private ChooseTypePanel createParameterPanel(String id, boolean isTenant) { - - ChooseTypePanel orgSelector = new ChooseTypePanel(id, Model.of(new ObjectViewDto())) { - - private static final long serialVersionUID = 1L; - - @Override - protected void executeCustomAction(AjaxRequestTarget target, OrgType object) { - refreshAll(target); - } - - @Override - protected void executeCustomRemoveAction(AjaxRequestTarget target) { - refreshAll(target); - } - - @Override - protected ObjectQuery getChooseQuery() { - S_FilterEntryOrEmpty q = getPrismContext().queryFor(OrgType.class); - if (isTenant) { - return q.item(OrgType.F_TENANT).eq(true).build(); - } else { - return q.not().item(OrgType.F_TENANT).eq(true).build(); - } - } - - @Override - protected boolean isSearchEnabled() { - return true; - } - - @Override - public Class getObjectTypeClass() { - return OrgType.class; - } - - @Override - protected AttributeAppender getInputStyleClass(){ - return AttributeAppender.append("class", "col-md-10"); - } - - }; - orgSelector.setOutputMarkupId(true); - orgSelector.setOutputMarkupPlaceholderTag(true); - return orgSelector; - - } - - private DropDownFormGroup createDropDown(String id, IModel defaultModel, final List values, - IChoiceRenderer renderer, String labelKey, String tooltipKey, boolean required) { - DropDownFormGroup listSelect = new DropDownFormGroup(id, defaultModel, Model.ofList(values), renderer, createStringResource(labelKey), - tooltipKey, false, "col-md-4", "col-md-8", required){ - private static final long serialVersionUID = 1L; - - @Override - protected String getNullValidDisplayValue(){ - return getString("ObjectTypes.all"); - } - }; - - listSelect.getInput().add(new OnChangeAjaxBehavior() { - private static final long serialVersionUID = 1L; - - @Override - protected void onUpdate(AjaxRequestTarget target) { - refreshAll(target); - } - }); - listSelect.setOutputMarkupId(true); - return listSelect; - } - - protected void refreshAll(AjaxRequestTarget target) { - updateMembersPanelSessionStorage(); - - DropDownFormGroup typeChoice = (DropDownFormGroup) get(createComponentPath(ID_FORM, ID_OBJECT_TYPE)); - QName type = getMemberPanelStorage() != null ? getMemberPanelStorage().getType().getTypeQName() : typeChoice.getModelObject(); - getMemberTable().clearCache(); - getMemberTable().refreshTable(WebComponentUtil.qnameToClass(getPrismContext(), type, FocusType.class), target); - target.add(this); - } - - - private MainObjectListPanel getMemberTable() { - return (MainObjectListPanel) get(createComponentPath(ID_FORM, ID_CONTAINER_MEMBER, ID_MEMBER_TABLE)); - } - - protected QueryScope getQueryScope(boolean isRecompute) { - if (CollectionUtils.isNotEmpty(MemberOperationsHelper.getFocusOidToRecompute(getMemberTable().getSelectedObjects()))) { - return QueryScope.SELECTED; - } - - if (getIndirectmembersPanel().getValue()) { - return QueryScope.ALL; - } - - return QueryScope.ALL_DIRECT; - } - - private CheckFormGroup getIndirectmembersPanel() { - return (CheckFormGroup) get(createComponentPath(ID_FORM, ID_INDIRECT_MEMBERS)); - } - - protected void recomputeMembersPerformed(AjaxRequestTarget target) { - - StringResourceModel confirmModel = getMemberTable().getSelectedObjectsCount() > 0 ? - createStringResource("abstractRoleMemberPanel.recomputeSelectedMembersConfirmationLabel") - : createStringResource("abstractRoleMemberPanel.recomputeAllMembersConfirmationLabel"); - - ConfigureTaskConfirmationPanel dialog = new ConfigureTaskConfirmationPanel(((PageBase)getPage()).getMainPopupBodyId(), - confirmModel) { - - private static final long serialVersionUID = 1L; - - @Override - protected PrismObject getTask(AjaxRequestTarget target) { - Task task = MemberOperationsHelper.createRecomputeMembersTask(getPageBase(), getQueryScope(true), - getActionQuery(getQueryScope(true), getSupportedRelations().getAvailableRelationList()), target); - if (task == null) { - return null; - } - PrismObject recomputeTask = task.getClonedTaskObject(); - TaskType recomputeTaskType = recomputeTask.asObjectable(); - recomputeTaskType.getAssignment().add(ObjectTypeUtil.createAssignmentTo(SystemObjectsType.ARCHETYPE_RECOMPUTATION_TASK.value(), ObjectTypes.ARCHETYPE, getPrismContext())); - return recomputeTask; - } - - @Override - public StringResourceModel getTitle() { - return createStringResource("pageUsers.message.confirmActionPopupTitle"); - } - - @Override - public void yesPerformed(AjaxRequestTarget target) { - MemberOperationsHelper.recomputeMembersPerformed(getPageBase(), getQueryScope(true), - getActionQuery(getQueryScope(true), getSupportedRelations().getAvailableRelationList()), target); - } - }; - ((PageBase)getPage()).showMainPopup(dialog, target); - } - - protected ObjectQuery createContentQuery() { - CheckFormGroup isIndirect = getIndirectmembersPanel(); - List relations = QNameUtil.match(getSelectedRelation(), PrismConstants.Q_ANY) ? getSupportedRelations().getAvailableRelationList() : Arrays.asList(getSelectedRelation()); - return createMemberQuery(isIndirect != null ? isIndirect.getValue() : false, relations); - - } - - protected QName getSelectedRelation(){ - MemberPanelStorage storage = getMemberPanelStorage(); - if (storage != null){ - return storage.getRelation(); - } - RelationDropDownChoicePanel relationDropDown = (RelationDropDownChoicePanel) get(createComponentPath(ID_FORM, ID_SEARCH_BY_RELATION)); - return relationDropDown.getRelationValue(); - } - - private SearchBoxScopeType getSearchScopeValue(){ - if (getMemberPanelStorage() != null){ - return getMemberPanelStorage().getOrgSearchScope(); - } - DropDownFormGroup searchScopeComponent = (DropDownFormGroup)get(createComponentPath(ID_FORM, ID_SEARCH_SCOPE)); - return searchScopeComponent.getModelObject(); - } - - protected ObjectTypes getSearchType() { - DropDownFormGroup searchByTypeChoice = (DropDownFormGroup) get( - createComponentPath(ID_FORM, ID_OBJECT_TYPE)); - QName typeName = searchByTypeChoice.getModelObject(); - return ObjectTypes.getObjectTypeFromTypeQName(typeName); - } - - protected ObjectQuery createMemberQuery(boolean indirect, Collection relations) { - if (indirect) { - return createAllMemberQuery(relations); - } - - return MemberOperationsHelper.createDirectMemberQuery(getModelObject(), getSearchType().getTypeQName(), relations, getParameter(ID_TENANT), getParameter(ID_PROJECT), getPrismContext()); - } - - - protected ObjectQuery createAllMemberQuery(Collection relations) { - return getPrismContext().queryFor(FocusType.class) - .item(FocusType.F_ROLE_MEMBERSHIP_REF).ref(MemberOperationsHelper.createReferenceValuesList(getModelObject(), relations)) - .build(); - } - - - protected ObjectReferenceType createReference() { - ObjectReferenceType ref = ObjectTypeUtil.createObjectRef(getModelObject(), getPageBase().getPrismContext()); - return ref; - } - - protected void detailsPerformed(AjaxRequestTarget target, ObjectType object) { - if (WebComponentUtil.hasDetailsPage(object.getClass())) { - WebComponentUtil.dispatchToObjectDetailsPage(object.getClass(), object.getOid(), this, true); - } else { - error("Could not find proper response page"); - throw new RestartResponseException(getPageBase()); - } - } - - protected List, String>> createMembersColumns() { - List, String>> columns = new ArrayList<>(); - - IColumn, String> column = new AbstractExportableColumn, String>( - createStringResource("TreeTablePanel.fullName.displayName")) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(Item>> cellItem, - String componentId, IModel> rowModel) { - SelectableBean bean = rowModel.getObject(); - ObjectType object = bean.getValue(); - cellItem.add(new Label(componentId, - getMemberObjectDisplayName(object, true))); - } - - @Override - public IModel getDataModel(IModel> rowModel) { - return Model.of(getMemberObjectDisplayName(rowModel.getObject().getValue(), true)); - } - - }; - columns.add(column); - - column = new AbstractExportableColumn, String>( - createStringResource("TreeTablePanel.identifier.description")) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(Item>> cellItem, - String componentId, IModel> rowModel) { - SelectableBean bean = rowModel.getObject(); - ObjectType object = bean.getValue(); - cellItem.add(new Label(componentId, getMemberObjectIdentifier(object))); - } - - @Override - public IModel getDataModel(IModel> rowModel) { - return Model.of(getMemberObjectIdentifier(rowModel.getObject().getValue())); - } - - }; - columns.add(column); -// if (isRelationColumnVisible()){ - columns.add(createRelationColumn()); -// } - return columns; - } - - protected IColumn, String> createRelationColumn() { - return new AbstractExportableColumn, String>( - createStringResource("roleMemberPanel.relation")) { - private static final long serialVersionUID = 1L; - - @Override - public void populateItem(Item>> cellItem, - String componentId, IModel> rowModel) { - cellItem.add(new Label(componentId, - getRelationValue(rowModel.getObject().getValue()))); - } - - @Override - public IModel getDataModel(IModel> rowModel) { - return Model.of(getRelationValue(rowModel.getObject().getValue())); - } - - }; - } - - protected boolean isRelationColumnVisible(){ - return false; - } - - - private String getMemberObjectDisplayName(ObjectType object){ - return getMemberObjectDisplayName(object, false); - } - - private String getMemberObjectDisplayName(ObjectType object, boolean translate){ - if (object == null){ - return ""; - } - if (object instanceof UserType) { - return WebComponentUtil.getTranslatedPolyString(((UserType) object).getFullName()); - } else if (object instanceof AbstractRoleType) { - return WebComponentUtil.getTranslatedPolyString(((AbstractRoleType) object).getDisplayName()); - } else { - return ""; - } - } - - private String getMemberObjectIdentifier(ObjectType object){ - if (object == null){ - return ""; - } - if (object instanceof UserType) { - return ((UserType) object).getEmailAddress(); - } else if (object instanceof AbstractRoleType) { - return ((AbstractRoleType) object).getIdentifier(); - } else { - return object.getDescription(); - } - } - - private Collection> getSearchOptions(){ - return SelectorOptions - .createCollection(GetOperationOptions.createDistinct()); - } - - protected Class getDefaultObjectType(){ - return (Class) FocusType.class; - } - - protected Form getFormComponent(){ - return (Form) get(ID_FORM); - } - - - private String getRelationValue(ObjectType focusObject){ - String relation = ""; - if (FocusType.class.isAssignableFrom(focusObject.getClass())) { - // Do NOT take relation from an assignment. Use roleMembershipRef instead. Reasons: - // 1. Authorizations (MID-4893). User may be authorized just for roleMemberhsipRef and not for assignment - // Authorization for roleMembershipRef is enough to display member panel. - // 2. There may be assignments that are not valid. We do not want to display relation for those. - for (ObjectReferenceType roleMembershipRef : getMembershipReferenceList((FocusType) focusObject)) { - relation = buildRelation(roleMembershipRef, relation); - } - - } - return relation; - - } - - protected List getMembershipReferenceList(FocusType focusObject){ - return focusObject.getRoleMembershipRef(); - } - - private String buildRelation(ObjectReferenceType roleMembershipRef, String relation) { - if (roleMembershipRef.getOid().equals(getModelObject().getOid())) { - QName assignmentRelation = roleMembershipRef.getRelation(); - if (getSupportedRelations().getAvailableRelationList().stream().anyMatch(r -> QNameUtil.match(r, assignmentRelation))) { - if (!StringUtils.isBlank(relation)) { - relation += ","; - } - relation += assignmentRelation.getLocalPart(); - } - } - return relation; - } - - protected void updateMembersPanelSessionStorage(){ - MemberPanelStorage storage = getMemberPanelStorage(); - if (storage != null){ - storage.setType(getSearchType()); - - RelationDropDownChoicePanel relationDropDown = (RelationDropDownChoicePanel) get(createComponentPath(ID_FORM, ID_SEARCH_BY_RELATION)); - storage.setRelation(relationDropDown.getRelationValue()); - - CheckFormGroup indirectPanel = getIndirectmembersPanel(); - if (indirectPanel != null){ - storage.setIndirect(indirectPanel.getValue()); - } - - DropDownFormGroup searchScopeComponent = - (DropDownFormGroup)get(createComponentPath(ID_FORM, ID_SEARCH_SCOPE)); - storage.setOrgSearchScope(searchScopeComponent.getModelObject()); - } - } - - protected MemberPanelStorage getMemberPanelStorage(){ - return null; - } -} +/* + * Copyright (c) 2010-2017 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.web.page.admin.roles; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIcon; +import com.evolveum.midpoint.gui.impl.component.icon.CompositedIconBuilder; +import com.evolveum.midpoint.model.api.AssignmentCandidatesSpecification; +import com.evolveum.midpoint.model.api.AssignmentObjectRelation; +import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.query.QueryFactory; +import com.evolveum.midpoint.prism.query.builder.S_FilterEntryOrEmpty; +import com.evolveum.midpoint.schema.constants.RelationTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.web.component.MultiFunctinalButtonDto; +import com.evolveum.midpoint.web.component.MultifunctionalButton; +import com.evolveum.midpoint.web.component.data.column.ColumnUtils; +import com.evolveum.midpoint.web.component.dialog.ConfigureTaskConfirmationPanel; +import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; +import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; +import com.evolveum.midpoint.web.component.util.SelectableBean; +import com.evolveum.midpoint.web.page.admin.configuration.component.HeaderMenuAction; +import com.evolveum.midpoint.web.session.MemberPanelStorage; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.wicket.Component; +import org.apache.wicket.RestartResponseException; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.ajax.form.OnChangeAjaxBehavior; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.form.IChoiceRenderer; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; + +import com.evolveum.midpoint.gui.api.GuiStyleConstants; +import com.evolveum.midpoint.gui.api.component.BasePanel; +import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; +import com.evolveum.midpoint.gui.api.page.PageBase; +import com.evolveum.midpoint.gui.api.util.WebComponentUtil; +import com.evolveum.midpoint.prism.PrismConstants; +import com.evolveum.midpoint.prism.query.ObjectFilter; +import com.evolveum.midpoint.prism.query.ObjectQuery; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.web.component.dialog.ChooseFocusTypeAndRelationDialogPanel; +import com.evolveum.midpoint.web.component.form.CheckFormGroup; +import com.evolveum.midpoint.web.component.form.DropDownFormGroup; +import com.evolveum.midpoint.web.component.input.QNameObjectTypeChoiceRenderer; +import com.evolveum.midpoint.web.component.input.RelationDropDownChoicePanel; +import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; +import com.evolveum.midpoint.web.component.search.Search; +import com.evolveum.midpoint.web.component.search.SearchFactory; +import com.evolveum.midpoint.web.component.util.EnableBehaviour; +import com.evolveum.midpoint.web.component.util.VisibleBehaviour; +import com.evolveum.midpoint.web.page.admin.configuration.component.ChooseTypePanel; +import com.evolveum.midpoint.web.page.admin.dto.ObjectViewDto; +import com.evolveum.midpoint.web.security.GuiAuthorizationConstants; +import com.evolveum.midpoint.web.session.UserProfileStorage.TableId; + +import org.apache.wicket.model.StringResourceModel; + +import static com.evolveum.midpoint.web.component.data.column.ColumnUtils.createStringResource; + +public abstract class AbstractRoleMemberPanel extends BasePanel { + + private static final long serialVersionUID = 1L; + + protected enum QueryScope { + SELECTED, ALL, ALL_DIRECT + } + + protected enum MemberOperation { + ADD, REMOVE, RECOMPUTE + } + + private static final Trace LOGGER = TraceManager.getTrace(AbstractRoleMemberPanel.class); + private static final String DOT_CLASS = AbstractRoleMemberPanel.class.getName() + "."; + protected static final String OPERATION_LOAD_MEMBER_RELATIONS = DOT_CLASS + "loadMemberRelationsList"; + protected static final String OPERATION_LOAD_ARCHETYPE_OBJECT = DOT_CLASS + "loadArchetypeObject"; + + protected static final String ID_FORM = "form"; + + protected static final String ID_CONTAINER_MEMBER = "memberContainer"; + protected static final String ID_CHILD_TABLE = "childUnitTable"; + protected static final String ID_MEMBER_TABLE = "memberTable"; + + private static final String ID_OBJECT_TYPE = "type"; + private static final String ID_TENANT = "tenant"; + private static final String ID_PROJECT = "project"; + private static final String ID_INDIRECT_MEMBERS = "indirectMembers"; + + protected static final String ID_SEARCH_SCOPE = "searchScope"; + protected SearchBoxScopeType scopeDefaultValue = null; + protected QName objectTypeDefaultValue = null; + + protected static final String ID_SEARCH_BY_RELATION = "searchByRelation"; + + private static Map> authorizations = new HashMap<>(); + private static Map tablesId = new HashMap<>(); + + static { + tablesId.put(RoleType.COMPLEX_TYPE, TableId.ROLE_MEMEBER_PANEL); + tablesId.put(ServiceType.COMPLEX_TYPE, TableId.SERVICE_MEMEBER_PANEL); + tablesId.put(OrgType.COMPLEX_TYPE, TableId.ORG_MEMEBER_PANEL); + tablesId.put(ArchetypeType.COMPLEX_TYPE, TableId.ARCHETYPE_MEMEBER_PANEL); + } + + static { + authorizations.put(RoleType.COMPLEX_TYPE, GuiAuthorizationConstants.ROLE_MEMBERS_AUTHORIZATIONS); + authorizations.put(ServiceType.COMPLEX_TYPE, GuiAuthorizationConstants.SERVICE_MEMBERS_AUTHORIZATIONS); + authorizations.put(OrgType.COMPLEX_TYPE, GuiAuthorizationConstants.ORG_MEMBERS_AUTHORIZATIONS); + authorizations.put(ArchetypeType.COMPLEX_TYPE, GuiAuthorizationConstants.ARCHETYPE_MEMBERS_AUTHORIZATIONS); + } + + public AbstractRoleMemberPanel(String id, IModel model) { + super(id, model); + } + + @Override + protected void onInitialize(){ + super.onInitialize(); + initLayout(); + } + + protected void initLayout() { + Form form = new com.evolveum.midpoint.web.component.form.Form(ID_FORM); + form.setOutputMarkupId(true); + add(form); + initDefaultSearchParameters(); + initSearch(form); + initMemberTable(form); + setOutputMarkupId(true); + + } + + private void initDefaultSearchParameters(){ + GuiObjectListPanelConfigurationType additionalPanel = getAdditionalPanelConfig(); + if (additionalPanel != null && additionalPanel.getSearchBoxConfiguration() != null) { + scopeDefaultValue = additionalPanel.getSearchBoxConfiguration().getDefaultScope(); + objectTypeDefaultValue = additionalPanel.getSearchBoxConfiguration().getDefaultObjectType(); + } + if (scopeDefaultValue == null){ + scopeDefaultValue = SearchBoxScopeType.ONE_LEVEL; + } + if (objectTypeDefaultValue == null){ + objectTypeDefaultValue = WebComponentUtil.classToQName(getPrismContext(), getDefaultObjectType()); + } + if (getMemberPanelStorage() != null){ + if (getMemberPanelStorage().getOrgSearchScope() == null){ + getMemberPanelStorage().setOrgSearchScope(scopeDefaultValue); + } + if (getMemberPanelStorage().getType() == null){ + getMemberPanelStorage().setType(ObjectTypes.getObjectType(objectTypeDefaultValue.getLocalPart())); + } + } + } + + protected Form getForm() { + return (Form) get(ID_FORM); + } + + private void initMemberTable(Form form) { + WebMarkupContainer memberContainer = new WebMarkupContainer(ID_CONTAINER_MEMBER); + memberContainer.setOutputMarkupId(true); + memberContainer.setOutputMarkupPlaceholderTag(true); + form.add(memberContainer); + + PageBase pageBase = getPageBase(); + Class type = getMemberPanelStorage() != null && getMemberPanelStorage().getType() != null ? + getMemberPanelStorage().getType().getClassDefinition() : ObjectType.class; + //TODO QName defines a relation value which will be used for new member creation + MainObjectListPanel childrenListPanel = new MainObjectListPanel( + ID_MEMBER_TABLE, type, getTableId(getComplexTypeQName()), getSearchOptions()) { + + private static final long serialVersionUID = 1L; + + @Override + protected void objectDetailsPerformed(AjaxRequestTarget target, ObjectType object) { + detailsPerformed(target, object); + } + + @Override + protected boolean isObjectDetailsEnabled(IModel> rowModel) { + if (rowModel == null || rowModel.getObject() == null + || rowModel.getObject().getValue() == null) { + return false; + } + Class objectClass = rowModel.getObject().getValue().getClass(); + return WebComponentUtil.hasDetailsPage(objectClass); + } + + @Override + protected DisplayType getNewObjectButtonSpecialDisplayType(){ + return getCreateMemberButtonDisplayType(); + } + + @Override + protected DisplayType getNewObjectButtonStandardDisplayType(){ + return WebComponentUtil.createDisplayType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green", + createStringResource("abstractRoleMemberPanel.menu.createMember", "", "").getString()); + } + + @Override + protected List loadButtonDescriptions() { + return createAdditionalButtonsDescription(); + } + + @Override + protected void newObjectPerformed(AjaxRequestTarget target, AssignmentObjectRelation relation, CompiledObjectCollectionView collectionView) { + AbstractRoleMemberPanel.this.createFocusMemberPerformed(target, relation); + } + + @Override + protected List createToolbarButtonsList(String buttonId){ + List buttonsList = super.createToolbarButtonsList(buttonId); + MultifunctionalButton assignButton = createAssignButton(buttonId); + buttonsList.add(1, assignButton); + return buttonsList; + } + + @Override + protected IColumn, String> createIconColumn(){ + return (IColumn) ColumnUtils.createIconColumn(pageBase); + } + + @Override + protected List, String>> createColumns() { + return createMembersColumns(); + } + + @Override + protected List createInlineMenu() { + return createRowActions(); + } + + @Override + protected Search createSearch() { + return getMemberPanelStorage() != null && getMemberPanelStorage().getSearch() != null ? + getMemberPanelStorage().getSearch() : SearchFactory.createSearch(getDefaultObjectType(), pageBase); + } + + @Override + protected ObjectQuery createContentQuery() { + ObjectQuery q = super.createContentQuery(); + + ObjectQuery members = AbstractRoleMemberPanel.this.createContentQuery(); + + List filters = new ArrayList<>(); + + if (q != null && q.getFilter() != null) { + filters.add(q.getFilter()); + } + + if (members != null && members.getFilter() != null) { + filters.add(members.getFilter()); + } + + QueryFactory queryFactory = pageBase.getPrismContext().queryFactory(); + if (filters.size() == 1) { + return queryFactory.createQuery(filters.iterator().next()); + } else { + return queryFactory.createQuery(queryFactory.createAnd(filters)); + } + } + + @Override + protected GuiObjectListPanelConfigurationType getAdditionalPanelConfig(){ + return AbstractRoleMemberPanel.this.getAdditionalPanelConfig(); + } + + @Override + protected boolean isAdditionalPanel(){ + return true; + } + + protected boolean isTypeChanged(Class newTypeClass){ + return true; + } + }; + childrenListPanel.setOutputMarkupId(true); + memberContainer.add(childrenListPanel); + } + + private List createAdditionalButtonsDescription() { + List multiFunctinalButtonDtos = new ArrayList<>(); + List loadedRelations = loadMemberRelationsList(); + if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(loadedRelations)) { + List relations = WebComponentUtil.divideAssignmentRelationsByAllValues(loadedRelations); + relations.forEach(relation -> { + MultiFunctinalButtonDto buttonDto = new MultiFunctinalButtonDto(); + DisplayType additionalButtonDisplayType = WebComponentUtil.getAssignmentObjectRelationDisplayType(getPageBase(), relation, + "abstractRoleMemberPanel.menu.createMember"); + buttonDto.setAdditionalButtonDisplayType(additionalButtonDisplayType); + buttonDto.setCompositedIcon(createCompositedIcon(relation, additionalButtonDisplayType)); + buttonDto.setAssignmentObjectRelation(relation); + multiFunctinalButtonDtos.add(buttonDto); + }); + } + return multiFunctinalButtonDtos; + } + + private CompositedIcon createCompositedIcon(AssignmentObjectRelation relation, DisplayType additionalButtonDisplayType) { + CompositedIconBuilder builder = WebComponentUtil.getAssignmentRelationIconBuilder(getPageBase(), relation, + additionalButtonDisplayType.getIcon(), WebComponentUtil.createIconType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green")); + if (builder == null) { + return null; + } + return builder.build(); + } + + private MultifunctionalButton createAssignButton(String buttonId) { + MultifunctionalButton assignButton = new MultifunctionalButton(buttonId, createAssignmentAdditionalButtons()) { + private static final long serialVersionUID = 1L; + + + @Override + protected void buttonClickPerformed(AjaxRequestTarget target, AssignmentObjectRelation relation, CompiledObjectCollectionView collectionView) { + List relations = relation != null && !CollectionUtils.isEmpty(relation.getRelations()) ? + Arrays.asList(relation.getRelations().get(0)) : getSupportedRelations().getAvailableRelationList(); + AvailableRelationDto avariableRelations = new AvailableRelationDto(relations, getSupportedRelations().getDefaultRelation()); + List objectTypes = relation != null && !CollectionUtils.isEmpty(relation.getObjectTypes()) ? + relation.getObjectTypes() : null; + List archetypeRefList = relation != null && !CollectionUtils.isEmpty(relation.getArchetypeRefs()) ? + relation.getArchetypeRefs() : null; + assignMembers(target, avariableRelations, objectTypes, archetypeRefList, relation == null); + } + + @Override + protected DisplayType getMainButtonDisplayType(){ + return getAssignMemberButtonDisplayType(); + } + + @Override + protected DisplayType getDefaultObjectButtonDisplayType(){ + return getAssignMemberButtonDisplayType(); + } + + }; + assignButton.add(AttributeAppender.append("class", "btn-margin-right")); + + + return assignButton; + } + + private List createAssignmentAdditionalButtons() { + List additionalAssignmentButtons = new ArrayList<>(); + List assignmentObjectRelations = WebComponentUtil.divideAssignmentRelationsByAllValues(loadMemberRelationsList()); + if (assignmentObjectRelations == null) { + return additionalAssignmentButtons; + } + assignmentObjectRelations.forEach(relation -> { + MultiFunctinalButtonDto buttonDto = new MultiFunctinalButtonDto(); + buttonDto.setAssignmentObjectRelation(relation); + + DisplayType additionalDispayType = WebComponentUtil.getAssignmentObjectRelationDisplayType(AbstractRoleMemberPanel.this.getPageBase(), relation, + "abstractRoleMemberPanel.menu.assignMember"); + //TODO null additinalDisplayType + CompositedIconBuilder builder = WebComponentUtil.getAssignmentRelationIconBuilder(AbstractRoleMemberPanel.this.getPageBase(), relation, + additionalDispayType.getIcon(), WebComponentUtil.createIconType(GuiStyleConstants.EVO_ASSIGNMENT_ICON, "green")); + CompositedIcon icon = builder.build(); + buttonDto.setAdditionalButtonDisplayType(additionalDispayType); + buttonDto.setCompositedIcon(icon); + additionalAssignmentButtons.add(buttonDto); + }); + + return additionalAssignmentButtons; + + } + + protected TableId getTableId(QName complextType) { + return tablesId.get(complextType); + } + + protected Map getAuthorizations(QName complexType) { + return authorizations.get(complexType); + } + + protected QName getComplexTypeQName() { + return getModelObject().asPrismObject().getComplexTypeDefinition().getTypeName(); + } + + private DisplayType getCreateMemberButtonDisplayType(){ + return WebComponentUtil.createDisplayType(GuiStyleConstants.CLASS_ADD_NEW_OBJECT, "green", + AbstractRoleMemberPanel.this.createStringResource("abstractRoleMemberPanel.menu.createMember", "", "").getString()); + } + + private DisplayType getAssignMemberButtonDisplayType(){ + return WebComponentUtil.createDisplayType(GuiStyleConstants.EVO_ASSIGNMENT_ICON, "green", + AbstractRoleMemberPanel.this.createStringResource("abstractRoleMemberPanel.menu.assignMember", "", "").getString()); + } + + protected List createRowActions() { + List menu = new ArrayList<>(); + createAssignMemberRowAction(menu); + + if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_UNASSIGN)) { + menu.add(new ButtonInlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.unassign")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new HeaderMenuAction(AbstractRoleMemberPanel.this) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + unassignMembersPerformed(target); + } + }; + + } + + @Override + public String getButtonIconCssClass() { + return GuiStyleConstants.CLASS_UNASSIGN; + } + }); + } + + createRecomputeMemberRowAction(menu); + + if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_CREATE)) { + menu.add(new InlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.create")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new HeaderMenuAction(AbstractRoleMemberPanel.this) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + createFocusMemberPerformed(target); + } + }; + } + }); + } + if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_DELETE)) { + menu.add(new InlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.delete")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new HeaderMenuAction(AbstractRoleMemberPanel.this) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + deleteMembersPerformed(target); + } + }; + } + + }); + } + return menu; + } + + protected void createAssignMemberRowAction(List menu) { + if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_ASSIGN)) { + menu.add(new InlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.assign")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new HeaderMenuAction(AbstractRoleMemberPanel.this) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + MemberOperationsHelper.assignMembers(getPageBase(), AbstractRoleMemberPanel.this.getModelObject(), target, getSupportedRelations(), null); + } + }; + } + }); + } + } + + protected void createRecomputeMemberRowAction(List menu) { + if (isAuthorized(GuiAuthorizationConstants.MEMBER_OPERATION_RECOMPUTE)) { + menu.add(new ButtonInlineMenuItem(createStringResource("abstractRoleMemberPanel.menu.recompute")) { + private static final long serialVersionUID = 1L; + + @Override + public InlineMenuItemAction initAction() { + return new HeaderMenuAction(AbstractRoleMemberPanel.this) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + recomputeMembersPerformed(target); + } + }; + } + +// @Override +// public IModel getConfirmationMessageModel() { +// return getMemberTable().getSelectedObjectsCount() > 0 ? +// createStringResource("abstractRoleMemberPanel.recomputeSelectedMembersConfirmationLabel") +// : createStringResource("abstractRoleMemberPanel.recomputeAllMembersConfirmationLabel"); +// } + + @Override + public String getButtonIconCssClass() { + return GuiStyleConstants.CLASS_RECONCILE_MENU_ITEM; + } + + }); + } + } + + + + protected abstract AvailableRelationDto getSupportedRelations(); + + protected GuiObjectListPanelConfigurationType getAdditionalPanelConfig(){ + return null; + } + + private boolean isAuthorized(String action) { + Map memeberAuthz = getAuthorizations(getComplexTypeQName()); + return WebComponentUtil.isAuthorized(memeberAuthz.get(action)); + } + + + + private List loadMemberRelationsList(){ + List assignmentTargetRelations = new ArrayList<>(); + AssignmentCandidatesSpecification spec = loadCandidateSpecification(); + assignmentTargetRelations = spec != null ? spec.getAssignmentObjectRelations() : new ArrayList<>(); + return assignmentTargetRelations; + } + + private AssignmentCandidatesSpecification loadCandidateSpecification(){ + OperationResult result = new OperationResult(OPERATION_LOAD_MEMBER_RELATIONS); + PrismObject obj = getModelObject().asPrismObject(); + AssignmentCandidatesSpecification spec = null; + try { + spec = getPageBase().getModelInteractionService() + .determineAssignmentHolderSpecification(obj, result); + } catch (SchemaException | ConfigurationException ex){ + result.recordPartialError(ex.getLocalizedMessage()); + LOGGER.error("Couldn't load member relations list for the object {} , {}", obj.getName(), ex.getLocalizedMessage()); + } + return spec; + } + + protected void assignMembers(AjaxRequestTarget target, AvailableRelationDto availableRelationList, + List objectTypes, List archetypeRefList, boolean isOrgTreePanelVisible) { + MemberOperationsHelper.assignMembers(getPageBase(), getModelObject(), target, availableRelationList, + objectTypes, archetypeRefList, isOrgTreePanelVisible); + } + + private void unassignMembersPerformed(AjaxRequestTarget target) { + QueryScope scope = getQueryScope(false); + + ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel(getPageBase().getMainPopupBodyId(), + createStringResource("abstractRoleMemberPanel.unassignAllMembersConfirmationLabel")) { + private static final long serialVersionUID = 1L; + + @Override + protected List getSupportedObjectTypes() { + return AbstractRoleMemberPanel.this.getSupportedObjectTypes(true); + } + + @Override + protected List getSupportedRelations() { + return AbstractRoleMemberPanel.this.getSupportedRelations().getAvailableRelationList(); + } + + @Override + protected boolean isFocusTypeSelectorVisible() { + return !QueryScope.SELECTED.equals(scope); + } + + + protected void okPerformed(QName type, Collection relations, AjaxRequestTarget target) { + unassignMembersPerformed(type, scope, relations, target); + } + + @Override + protected QName getDefaultObjectType() { + return WebComponentUtil.classToQName(AbstractRoleMemberPanel.this.getPrismContext(), + AbstractRoleMemberPanel.this.getDefaultObjectType()); + } + }; + + getPageBase().showMainPopup(chooseTypePopupContent, target); + } + + private void deleteMembersPerformed(AjaxRequestTarget target) { + QueryScope scope = getQueryScope(false); + ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel(getPageBase().getMainPopupBodyId(), + createStringResource("abstractRoleMemberPanel.deleteAllMembersConfirmationLabel")) { + private static final long serialVersionUID = 1L; + + @Override + protected List getSupportedObjectTypes() { + return AbstractRoleMemberPanel.this.getSupportedObjectTypes(true); + } + + @Override + protected List getSupportedRelations() { + return AbstractRoleMemberPanel.this.getSupportedRelations().getAvailableRelationList(); + } + + protected void okPerformed(QName type, Collection relations, AjaxRequestTarget target) { + deleteMembersPerformed(scope, relations, target); + } + + @Override + protected boolean isFocusTypeSelectorVisible() { + return !QueryScope.SELECTED.equals(scope); + } + + @Override + protected QName getDefaultObjectType() { + return WebComponentUtil.classToQName(AbstractRoleMemberPanel.this.getPrismContext(), + AbstractRoleMemberPanel.this.getDefaultObjectType()); + } + }; + + getPageBase().showMainPopup(chooseTypePopupContent, target); + } + + protected void createFocusMemberPerformed(AjaxRequestTarget target) { + createFocusMemberPerformed(target, null); + } + + protected void createFocusMemberPerformed(AjaxRequestTarget target, AssignmentObjectRelation relationSpec) { + if (relationSpec != null){ + try { + List newReferences = new ArrayList<>(); + if (CollectionUtils.isEmpty(relationSpec.getRelations())){ + relationSpec.setRelations(Arrays.asList(RelationTypes.MEMBER.getRelation())); + } + ObjectReferenceType memberRef = ObjectTypeUtil.createObjectRef(AbstractRoleMemberPanel.this.getModelObject(), relationSpec.getRelations().get(0)); + newReferences.add(memberRef); + if (CollectionUtils.isNotEmpty(relationSpec.getArchetypeRefs())) { + newReferences.add(relationSpec.getArchetypeRefs().get(0)); + } + QName newMemberType = CollectionUtils.isNotEmpty(relationSpec.getObjectTypes()) ? relationSpec.getObjectTypes().get(0) : + getSupportedObjectTypes(false).get(0); + WebComponentUtil.initNewObjectWithReference(AbstractRoleMemberPanel.this.getPageBase(), newMemberType, newReferences); + } catch (SchemaException e) { + throw new SystemException(e.getMessage(), e); + } + } else { + ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel( + getPageBase().getMainPopupBodyId()) { + private static final long serialVersionUID = 1L; + + @Override + protected List getSupportedObjectTypes() { + return AbstractRoleMemberPanel.this.getNewMemberObjectTypes(); + } + + @Override + protected List getSupportedRelations() { + return AbstractRoleMemberPanel.this.getSupportedRelations().getAvailableRelationList(); + } + + protected void okPerformed(QName type, Collection relations, AjaxRequestTarget target) { + if (type == null) { + getSession().warn("No type was selected. Cannot create member"); + target.add(this); + target.add(getPageBase().getFeedbackPanel()); + return; + } + if (relations == null || relations.isEmpty()) { + getSession().warn("No relation was selected. Cannot create member"); + target.add(this); + target.add(getPageBase().getFeedbackPanel()); + return; + } + AbstractRoleMemberPanel.this.getPageBase().hideMainPopup(target); + try { + List newReferences = new ArrayList<>(); + for (QName relation : relations) { + newReferences.add(ObjectTypeUtil.createObjectRef(AbstractRoleMemberPanel.this.getModelObject(), relation)); + } + WebComponentUtil.initNewObjectWithReference(AbstractRoleMemberPanel.this.getPageBase(), type, newReferences); + } catch (SchemaException e) { + throw new SystemException(e.getMessage(), e); + } + + } + + @Override + protected QName getDefaultObjectType() { + if (relationSpec != null && CollectionUtils.isNotEmpty(relationSpec.getObjectTypes())) { + return relationSpec.getObjectTypes().get(0); + } + return super.getDefaultObjectType(); + } + + @Override + protected boolean isFocusTypeSelectorVisible() { + return relationSpec == null; + } + }; + + getPageBase().showMainPopup(chooseTypePopupContent, target); + } + } + + protected void deleteMembersPerformed(QueryScope scope, Collection relations, AjaxRequestTarget target) { + if (relations == null || relations.isEmpty()) { + getSession().warn("No relation was selected. Cannot perform delete members"); + target.add(this); + target.add(getPageBase().getFeedbackPanel()); + return; + } + MemberOperationsHelper.deleteMembersPerformed(getPageBase(), scope, getActionQuery(scope, relations), target); + } + + protected void unassignMembersPerformed(QName type, QueryScope scope, Collection relations, AjaxRequestTarget target) { + if (relations == null || relations.isEmpty()) { + getSession().warn("No relation was selected. Cannot perform unassign members"); + target.add(this); + target.add(getPageBase().getFeedbackPanel()); + return; + } + MemberOperationsHelper.unassignMembersPerformed(getPageBase(), getModelObject(), scope, getActionQuery(scope, relations), relations, type, target); + } + + private ObjectViewDto getParameter(String panelId) { + ChooseTypePanel tenantChoice = (ChooseTypePanel) get(createComponentPath(ID_FORM, panelId)); + return tenantChoice.getModelObject(); + } + + protected ObjectQuery getActionQuery(QueryScope scope, Collection relations) { + switch (scope) { + case ALL: + return createAllMemberQuery(relations); + case ALL_DIRECT: + return MemberOperationsHelper.createDirectMemberQuery(getModelObject(), getSearchType().getTypeQName(), relations, getParameter(ID_TENANT), getParameter(ID_PROJECT), getPrismContext()); + case SELECTED: + return MemberOperationsHelper.createSelectedObjectsQuery(getMemberTable().getSelectedObjects(), getPrismContext()); + } + + return null; + } + + protected void initSearch(Form form) { + + List scopeValues = Arrays.asList(SearchBoxScopeType.values()); + DropDownFormGroup searchScrope = createDropDown(ID_SEARCH_SCOPE, + Model.of(getMemberPanelStorage() != null ? getMemberPanelStorage().getOrgSearchScope() : scopeDefaultValue), + scopeValues, + WebComponentUtil.getEnumChoiceRenderer(AbstractRoleMemberPanel.this), + "abstractRoleMemberPanel.searchScope", "abstractRoleMemberPanel.searchScope.tooltip", true); + searchScrope.add(new VisibleBehaviour(() -> getModelObject() instanceof OrgType)); + form.add(searchScrope); + + List supportedTypes = getSupportedObjectTypes(false); + DropDownFormGroup typeSelect = createDropDown(ID_OBJECT_TYPE, + Model.of(getMemberPanelStorage() != null ? getMemberPanelStorage().getType().getTypeQName() : WebComponentUtil.classToQName(getPrismContext(), getDefaultObjectType())), + supportedTypes, new QNameObjectTypeChoiceRenderer(){ + private static final long serialVersionUID = 1L; + + @Override + public Object getDisplayValue(QName qname) { + if (qname == null || getObjectTypesListParentType().equals(qname)){ + return StringUtils.leftPad(createStringResource("ObjectTypes.all").getString(), 1); + } else { + return super.getDisplayValue(qname); + } + } + + @Override + public QName getObject(String id, IModel> choices) { + QName qname = super.getObject(id, choices); + if (qname == null){ + return getObjectTypesListParentType(); + } + return qname; + } + + }, + "abstractRoleMemberPanel.type", "abstractRoleMemberPanel.type.tooltip", false); + form.add(typeSelect); + + RelationDropDownChoicePanel relationSelector = new RelationDropDownChoicePanel(ID_SEARCH_BY_RELATION, + getMemberPanelStorage() != null ? getMemberPanelStorage().getRelation() : getSupportedRelations().getDefaultRelation(), + getSupportedRelations().getAvailableRelationList(), true){ + private static final long serialVersionUID = 1L; + + @Override + protected void onValueChanged(AjaxRequestTarget target){ + refreshAll(target); + } + + @Override + protected String getNullValidDisplayValue(){ + return getString("RelationTypes.ANY"); + } + }; + form.add(relationSelector); + + ChooseTypePanel tenant = createParameterPanel(ID_TENANT, true); + form.add(tenant); + tenant.add(new VisibleBehaviour(() -> getModelObject() instanceof RoleType)); + + ChooseTypePanel project = createParameterPanel(ID_PROJECT, false); + form.add(project); + project.add(new VisibleBehaviour(() -> getModelObject() instanceof RoleType)); + + CheckFormGroup includeIndirectMembers = new CheckFormGroup(ID_INDIRECT_MEMBERS, + Model.of(getMemberPanelStorage() != null ? getMemberPanelStorage().getIndirect() : false), + createStringResource("abstractRoleMemberPanel.indirectMembers"), "abstractRoleMemberPanel.indirectMembers.tooltip", false, "col-md-4", "col-md-2"); + includeIndirectMembers.getCheck().add(new AjaxFormComponentUpdatingBehavior("change") { + + private static final long serialVersionUID = 1L; + + protected void onUpdate(AjaxRequestTarget target) { + refreshAll(target); + } + + }); + + includeIndirectMembers.add(new VisibleBehaviour(() -> + getSearchScopeValue().equals(SearchBoxScopeType.ONE_LEVEL) || !searchScrope.isVisible())); + includeIndirectMembers.setOutputMarkupId(true); + form.add(includeIndirectMembers); + + } + + protected List getSupportedObjectTypes(boolean includeAbstractTypes) { + return WebComponentUtil.createFocusTypeList(includeAbstractTypes); + } + + protected QName getObjectTypesListParentType(){ + return FocusType.COMPLEX_TYPE; + } + + protected List getNewMemberObjectTypes() { + return WebComponentUtil.createFocusTypeList(); + } + + private ChooseTypePanel createParameterPanel(String id, boolean isTenant) { + + ChooseTypePanel orgSelector = new ChooseTypePanel(id, Model.of(new ObjectViewDto())) { + + private static final long serialVersionUID = 1L; + + @Override + protected void executeCustomAction(AjaxRequestTarget target, OrgType object) { + refreshAll(target); + } + + @Override + protected void executeCustomRemoveAction(AjaxRequestTarget target) { + refreshAll(target); + } + + @Override + protected ObjectQuery getChooseQuery() { + S_FilterEntryOrEmpty q = getPrismContext().queryFor(OrgType.class); + if (isTenant) { + return q.item(OrgType.F_TENANT).eq(true).build(); + } else { + return q.not().item(OrgType.F_TENANT).eq(true).build(); + } + } + + @Override + protected boolean isSearchEnabled() { + return true; + } + + @Override + public Class getObjectTypeClass() { + return OrgType.class; + } + + @Override + protected AttributeAppender getInputStyleClass(){ + return AttributeAppender.append("class", "col-md-10"); + } + + }; + orgSelector.setOutputMarkupId(true); + orgSelector.setOutputMarkupPlaceholderTag(true); + return orgSelector; + + } + + private DropDownFormGroup createDropDown(String id, IModel defaultModel, final List values, + IChoiceRenderer renderer, String labelKey, String tooltipKey, boolean required) { + DropDownFormGroup listSelect = new DropDownFormGroup(id, defaultModel, Model.ofList(values), renderer, createStringResource(labelKey), + tooltipKey, false, "col-md-4", "col-md-8", required){ + private static final long serialVersionUID = 1L; + + @Override + protected String getNullValidDisplayValue(){ + return getString("ObjectTypes.all"); + } + }; + + listSelect.getInput().add(new OnChangeAjaxBehavior() { + private static final long serialVersionUID = 1L; + + @Override + protected void onUpdate(AjaxRequestTarget target) { + refreshAll(target); + } + }); + listSelect.setOutputMarkupId(true); + return listSelect; + } + + protected void refreshAll(AjaxRequestTarget target) { + updateMembersPanelSessionStorage(); + + DropDownFormGroup typeChoice = (DropDownFormGroup) get(createComponentPath(ID_FORM, ID_OBJECT_TYPE)); + QName type = getMemberPanelStorage() != null ? getMemberPanelStorage().getType().getTypeQName() : typeChoice.getModelObject(); + getMemberTable().clearCache(); + getMemberTable().refreshTable(WebComponentUtil.qnameToClass(getPrismContext(), type, FocusType.class), target); + target.add(this); + } + + + private MainObjectListPanel getMemberTable() { + return (MainObjectListPanel) get(createComponentPath(ID_FORM, ID_CONTAINER_MEMBER, ID_MEMBER_TABLE)); + } + + protected QueryScope getQueryScope(boolean isRecompute) { + if (CollectionUtils.isNotEmpty(MemberOperationsHelper.getFocusOidToRecompute(getMemberTable().getSelectedObjects()))) { + return QueryScope.SELECTED; + } + + if (getIndirectmembersPanel().getValue()) { + return QueryScope.ALL; + } + + return QueryScope.ALL_DIRECT; + } + + private CheckFormGroup getIndirectmembersPanel() { + return (CheckFormGroup) get(createComponentPath(ID_FORM, ID_INDIRECT_MEMBERS)); + } + + protected void recomputeMembersPerformed(AjaxRequestTarget target) { + + StringResourceModel confirmModel = getMemberTable().getSelectedObjectsCount() > 0 ? + createStringResource("abstractRoleMemberPanel.recomputeSelectedMembersConfirmationLabel") + : createStringResource("abstractRoleMemberPanel.recomputeAllMembersConfirmationLabel"); + + ConfigureTaskConfirmationPanel dialog = new ConfigureTaskConfirmationPanel(((PageBase)getPage()).getMainPopupBodyId(), + confirmModel) { + + private static final long serialVersionUID = 1L; + + @Override + protected PrismObject getTask(AjaxRequestTarget target) { + Task task = MemberOperationsHelper.createRecomputeMembersTask(getPageBase(), getQueryScope(true), + getActionQuery(getQueryScope(true), getSupportedRelations().getAvailableRelationList()), target); + if (task == null) { + return null; + } + PrismObject recomputeTask = task.getClonedTaskObject(); + TaskType recomputeTaskType = recomputeTask.asObjectable(); + recomputeTaskType.getAssignment().add(ObjectTypeUtil.createAssignmentTo(SystemObjectsType.ARCHETYPE_RECOMPUTATION_TASK.value(), ObjectTypes.ARCHETYPE, getPrismContext())); + return recomputeTask; + } + + @Override + public StringResourceModel getTitle() { + return createStringResource("pageUsers.message.confirmActionPopupTitle"); + } + + @Override + public void yesPerformed(AjaxRequestTarget target) { + MemberOperationsHelper.recomputeMembersPerformed(getPageBase(), getQueryScope(true), + getActionQuery(getQueryScope(true), getSupportedRelations().getAvailableRelationList()), target); + } + }; + ((PageBase)getPage()).showMainPopup(dialog, target); + } + + protected ObjectQuery createContentQuery() { + CheckFormGroup isIndirect = getIndirectmembersPanel(); + List relations = QNameUtil.match(getSelectedRelation(), PrismConstants.Q_ANY) ? getSupportedRelations().getAvailableRelationList() : Arrays.asList(getSelectedRelation()); + return createMemberQuery(isIndirect != null ? isIndirect.getValue() : false, relations); + + } + + protected QName getSelectedRelation(){ + MemberPanelStorage storage = getMemberPanelStorage(); + if (storage != null){ + return storage.getRelation(); + } + RelationDropDownChoicePanel relationDropDown = (RelationDropDownChoicePanel) get(createComponentPath(ID_FORM, ID_SEARCH_BY_RELATION)); + return relationDropDown.getRelationValue(); + } + + private SearchBoxScopeType getSearchScopeValue(){ + if (getMemberPanelStorage() != null){ + return getMemberPanelStorage().getOrgSearchScope(); + } + DropDownFormGroup searchScopeComponent = (DropDownFormGroup)get(createComponentPath(ID_FORM, ID_SEARCH_SCOPE)); + return searchScopeComponent.getModelObject(); + } + + protected ObjectTypes getSearchType() { + DropDownFormGroup searchByTypeChoice = (DropDownFormGroup) get( + createComponentPath(ID_FORM, ID_OBJECT_TYPE)); + QName typeName = searchByTypeChoice.getModelObject(); + return ObjectTypes.getObjectTypeFromTypeQName(typeName); + } + + protected ObjectQuery createMemberQuery(boolean indirect, Collection relations) { + if (indirect) { + return createAllMemberQuery(relations); + } + + return MemberOperationsHelper.createDirectMemberQuery(getModelObject(), getSearchType().getTypeQName(), relations, getParameter(ID_TENANT), getParameter(ID_PROJECT), getPrismContext()); + } + + + protected ObjectQuery createAllMemberQuery(Collection relations) { + return getPrismContext().queryFor(FocusType.class) + .item(FocusType.F_ROLE_MEMBERSHIP_REF).ref(MemberOperationsHelper.createReferenceValuesList(getModelObject(), relations)) + .build(); + } + + + protected ObjectReferenceType createReference() { + ObjectReferenceType ref = ObjectTypeUtil.createObjectRef(getModelObject(), getPageBase().getPrismContext()); + return ref; + } + + protected void detailsPerformed(AjaxRequestTarget target, ObjectType object) { + if (WebComponentUtil.hasDetailsPage(object.getClass())) { + WebComponentUtil.dispatchToObjectDetailsPage(object.getClass(), object.getOid(), this, true); + } else { + error("Could not find proper response page"); + throw new RestartResponseException(getPageBase()); + } + } + + protected List, String>> createMembersColumns() { + List, String>> columns = new ArrayList<>(); + + IColumn, String> column = new AbstractExportableColumn, String>( + createStringResource("TreeTablePanel.fullName.displayName")) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(Item>> cellItem, + String componentId, IModel> rowModel) { + SelectableBean bean = rowModel.getObject(); + ObjectType object = bean.getValue(); + cellItem.add(new Label(componentId, + getMemberObjectDisplayName(object, true))); + } + + @Override + public IModel getDataModel(IModel> rowModel) { + return Model.of(getMemberObjectDisplayName(rowModel.getObject().getValue(), true)); + } + + }; + columns.add(column); + + column = new AbstractExportableColumn, String>( + createStringResource("TreeTablePanel.identifier.description")) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(Item>> cellItem, + String componentId, IModel> rowModel) { + SelectableBean bean = rowModel.getObject(); + ObjectType object = bean.getValue(); + cellItem.add(new Label(componentId, getMemberObjectIdentifier(object))); + } + + @Override + public IModel getDataModel(IModel> rowModel) { + return Model.of(getMemberObjectIdentifier(rowModel.getObject().getValue())); + } + + }; + columns.add(column); +// if (isRelationColumnVisible()){ + columns.add(createRelationColumn()); +// } + return columns; + } + + protected IColumn, String> createRelationColumn() { + return new AbstractExportableColumn, String>( + createStringResource("roleMemberPanel.relation")) { + private static final long serialVersionUID = 1L; + + @Override + public void populateItem(Item>> cellItem, + String componentId, IModel> rowModel) { + cellItem.add(new Label(componentId, + getRelationValue(rowModel.getObject().getValue()))); + } + + @Override + public IModel getDataModel(IModel> rowModel) { + return Model.of(getRelationValue(rowModel.getObject().getValue())); + } + + }; + } + + protected boolean isRelationColumnVisible(){ + return false; + } + + + private String getMemberObjectDisplayName(ObjectType object){ + return getMemberObjectDisplayName(object, false); + } + + private String getMemberObjectDisplayName(ObjectType object, boolean translate){ + if (object == null){ + return ""; + } + if (object instanceof UserType) { + return WebComponentUtil.getTranslatedPolyString(((UserType) object).getFullName()); + } else if (object instanceof AbstractRoleType) { + return WebComponentUtil.getTranslatedPolyString(((AbstractRoleType) object).getDisplayName()); + } else { + return ""; + } + } + + private String getMemberObjectIdentifier(ObjectType object){ + if (object == null){ + return ""; + } + if (object instanceof UserType) { + return ((UserType) object).getEmailAddress(); + } else if (object instanceof AbstractRoleType) { + return ((AbstractRoleType) object).getIdentifier(); + } else { + return object.getDescription(); + } + } + + private Collection> getSearchOptions(){ + return SelectorOptions + .createCollection(GetOperationOptions.createDistinct()); + } + + protected Class getDefaultObjectType(){ + return (Class) FocusType.class; + } + + protected Form getFormComponent(){ + return (Form) get(ID_FORM); + } + + + private String getRelationValue(ObjectType focusObject){ + String relation = ""; + if (FocusType.class.isAssignableFrom(focusObject.getClass())) { + // Do NOT take relation from an assignment. Use roleMembershipRef instead. Reasons: + // 1. Authorizations (MID-4893). User may be authorized just for roleMemberhsipRef and not for assignment + // Authorization for roleMembershipRef is enough to display member panel. + // 2. There may be assignments that are not valid. We do not want to display relation for those. + for (ObjectReferenceType roleMembershipRef : getMembershipReferenceList((FocusType) focusObject)) { + relation = buildRelation(roleMembershipRef, relation); + } + + } + return relation; + + } + + protected List getMembershipReferenceList(FocusType focusObject){ + return focusObject.getRoleMembershipRef(); + } + + private String buildRelation(ObjectReferenceType roleMembershipRef, String relation) { + if (roleMembershipRef.getOid().equals(getModelObject().getOid())) { + QName assignmentRelation = roleMembershipRef.getRelation(); + if (getSupportedRelations().getAvailableRelationList().stream().anyMatch(r -> QNameUtil.match(r, assignmentRelation))) { + if (!StringUtils.isBlank(relation)) { + relation += ","; + } + relation += assignmentRelation.getLocalPart(); + } + } + return relation; + } + + protected void updateMembersPanelSessionStorage(){ + MemberPanelStorage storage = getMemberPanelStorage(); + if (storage != null){ + storage.setType(getSearchType()); + + RelationDropDownChoicePanel relationDropDown = (RelationDropDownChoicePanel) get(createComponentPath(ID_FORM, ID_SEARCH_BY_RELATION)); + storage.setRelation(relationDropDown.getRelationValue()); + + CheckFormGroup indirectPanel = getIndirectmembersPanel(); + if (indirectPanel != null){ + storage.setIndirect(indirectPanel.getValue()); + } + + DropDownFormGroup searchScopeComponent = + (DropDownFormGroup)get(createComponentPath(ID_FORM, ID_SEARCH_SCOPE)); + storage.setOrgSearchScope(searchScopeComponent.getModelObject()); + } + } + + protected MemberPanelStorage getMemberPanelStorage(){ + return null; + } +} From 8192fbf38c109353d2a87e56bddb1a004db24d70 Mon Sep 17 00:00:00 2001 From: kate Date: Tue, 21 Apr 2020 23:28:23 +0200 Subject: [PATCH 18/27] MID-6165 fixing query for subtree scope --- .../admin/roles/AbstractRoleMemberPanel.java | 14 ++++---- .../admin/users/component/OrgMemberPanel.java | 32 +++++++++++++++++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java index 2a7b6c647da..0724e094b6b 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java @@ -584,7 +584,7 @@ protected void assignMembers(AjaxRequestTarget target, AvailableRelationDto avai } private void unassignMembersPerformed(AjaxRequestTarget target) { - QueryScope scope = getQueryScope(false); + QueryScope scope = getQueryScope(); ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel(getPageBase().getMainPopupBodyId(), createStringResource("abstractRoleMemberPanel.unassignAllMembersConfirmationLabel")) { @@ -621,7 +621,7 @@ protected QName getDefaultObjectType() { } private void deleteMembersPerformed(AjaxRequestTarget target) { - QueryScope scope = getQueryScope(false); + QueryScope scope = getQueryScope(); ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel(getPageBase().getMainPopupBodyId(), createStringResource("abstractRoleMemberPanel.deleteAllMembersConfirmationLabel")) { private static final long serialVersionUID = 1L; @@ -957,7 +957,7 @@ private MainObjectListPanel getMemberTable() { return (MainObjectListPanel) get(createComponentPath(ID_FORM, ID_CONTAINER_MEMBER, ID_MEMBER_TABLE)); } - protected QueryScope getQueryScope(boolean isRecompute) { + protected QueryScope getQueryScope() { if (CollectionUtils.isNotEmpty(MemberOperationsHelper.getFocusOidToRecompute(getMemberTable().getSelectedObjects()))) { return QueryScope.SELECTED; } @@ -986,8 +986,8 @@ protected void recomputeMembersPerformed(AjaxRequestTarget target) { @Override protected PrismObject getTask(AjaxRequestTarget target) { - Task task = MemberOperationsHelper.createRecomputeMembersTask(getPageBase(), getQueryScope(true), - getActionQuery(getQueryScope(true), getSupportedRelations().getAvailableRelationList()), target); + Task task = MemberOperationsHelper.createRecomputeMembersTask(getPageBase(), getQueryScope(), + getActionQuery(getQueryScope(), getSupportedRelations().getAvailableRelationList()), target); if (task == null) { return null; } @@ -1004,8 +1004,8 @@ public StringResourceModel getTitle() { @Override public void yesPerformed(AjaxRequestTarget target) { - MemberOperationsHelper.recomputeMembersPerformed(getPageBase(), getQueryScope(true), - getActionQuery(getQueryScope(true), getSupportedRelations().getAvailableRelationList()), target); + MemberOperationsHelper.recomputeMembersPerformed(getPageBase(), getQueryScope(), + getActionQuery(getQueryScope(), getSupportedRelations().getAvailableRelationList()), target); } }; ((PageBase)getPage()).showMainPopup(dialog, target); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java index 84851a43de9..63d59d0606e 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java @@ -58,7 +58,7 @@ protected void initLayout() { protected ObjectQuery createMemberQuery(boolean indirect, Collection relations) { ObjectTypes searchType = getSearchType(); if (SearchBoxScopeType.ONE_LEVEL.equals(getOrgSearchScope())) { - if (FocusType.class.isAssignableFrom(searchType.getClassDefinition())) { + if (AssignmentHolderType.class.isAssignableFrom(searchType.getClassDefinition())) { return super.createMemberQuery(indirect, relations); } else { @@ -68,8 +68,37 @@ protected ObjectQuery createMemberQuery(boolean indirect, Collection rela .isDirectChildOf(ref.asReferenceValue()).build(); } } + return getSubtreeScopeMembersQuery(); + } + + @Override + protected ObjectQuery getActionQuery(QueryScope scope, Collection relations) { + if (SearchBoxScopeType.ONE_LEVEL.equals(getOrgSearchScope())) { + return super.getActionQuery(scope, relations); + } else { + return getSubtreeScopeMembersQuery(); + } + } + @Override + protected QueryScope getQueryScope() { + if (SearchBoxScopeType.SUBTREE.equals(getOrgSearchScope())) { + return QueryScope.ALL; + } else { + return super.getQueryScope(); + } + } + + @Override + protected ObjectQuery createAllMemberQuery(Collection relations) { + return getPrismContext().queryFor(AssignmentHolderType.class) + .item(AssignmentHolderType.F_ROLE_MEMBERSHIP_REF).ref(MemberOperationsHelper.createReferenceValuesList(getModelObject(), relations)) + .build(); + } + + private ObjectQuery getSubtreeScopeMembersQuery(){ String oid = getModelObject().getOid(); + ObjectTypes searchType = getSearchType(); ObjectReferenceType ref = MemberOperationsHelper.createReference(getModelObject(), getSelectedRelation()); ObjectQuery query = getPageBase().getPrismContext().queryFor(searchType.getClassDefinition()) @@ -80,7 +109,6 @@ protected ObjectQuery createMemberQuery(boolean indirect, Collection rela LOGGER.trace("Searching members of org {} with query:\n{}", oid, query.debugDump()); } return query; - } protected SearchBoxScopeType getOrgSearchScope() { From 5acf95111060e3184d121e19ea2fa3184e019527 Mon Sep 17 00:00:00 2001 From: kate Date: Wed, 22 Apr 2020 13:34:41 +0200 Subject: [PATCH 19/27] more changes for members actions --- .../admin/roles/AbstractRoleMemberPanel.java | 35 ++++++++++++++----- .../admin/users/component/OrgMemberPanel.java | 21 ++--------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java index 0724e094b6b..9220024ad29 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/roles/AbstractRoleMemberPanel.java @@ -607,7 +607,8 @@ protected boolean isFocusTypeSelectorVisible() { protected void okPerformed(QName type, Collection relations, AjaxRequestTarget target) { - unassignMembersPerformed(type, scope, relations, target); + unassignMembersPerformed(type, SearchBoxScopeType.SUBTREE.equals(getSearchScope()) && QueryScope.ALL.equals(scope) ? + QueryScope.ALL_DIRECT : scope, relations, target); } @Override @@ -622,8 +623,16 @@ protected QName getDefaultObjectType() { private void deleteMembersPerformed(AjaxRequestTarget target) { QueryScope scope = getQueryScope(); + StringResourceModel confirmModel; + if (SearchBoxScopeType.SUBTREE.equals(getSearchScope())) { + confirmModel = createStringResource("abstractRoleMemberPanel.deleteAllSubtreeMembersConfirmationLabel"); + } else { + confirmModel = getMemberTable().getSelectedObjectsCount() > 0 ? + createStringResource("abstractRoleMemberPanel.deleteSelectedMembersConfirmationLabel") + : createStringResource("abstractRoleMemberPanel.deleteAllMembersConfirmationLabel"); + } ChooseFocusTypeAndRelationDialogPanel chooseTypePopupContent = new ChooseFocusTypeAndRelationDialogPanel(getPageBase().getMainPopupBodyId(), - createStringResource("abstractRoleMemberPanel.deleteAllMembersConfirmationLabel")) { + confirmModel) { private static final long serialVersionUID = 1L; @Override @@ -953,7 +962,7 @@ protected void refreshAll(AjaxRequestTarget target) { } - private MainObjectListPanel getMemberTable() { + protected MainObjectListPanel getMemberTable() { return (MainObjectListPanel) get(createComponentPath(ID_FORM, ID_CONTAINER_MEMBER, ID_MEMBER_TABLE)); } @@ -962,7 +971,7 @@ protected QueryScope getQueryScope() { return QueryScope.SELECTED; } - if (getIndirectmembersPanel().getValue()) { + if (getIndirectmembersPanel().getValue() || SearchBoxScopeType.SUBTREE.equals(getSearchScope())) { return QueryScope.ALL; } @@ -975,10 +984,14 @@ private CheckFormGroup getIndirectmembersPanel() { protected void recomputeMembersPerformed(AjaxRequestTarget target) { - StringResourceModel confirmModel = getMemberTable().getSelectedObjectsCount() > 0 ? - createStringResource("abstractRoleMemberPanel.recomputeSelectedMembersConfirmationLabel") - : createStringResource("abstractRoleMemberPanel.recomputeAllMembersConfirmationLabel"); - + StringResourceModel confirmModel; + if (SearchBoxScopeType.SUBTREE.equals(getSearchScope())) { + confirmModel = createStringResource("abstractRoleMemberPanel.recomputeAllSubtreeMembersConfirmationLabel"); + } else { + confirmModel = getMemberTable().getSelectedObjectsCount() > 0 ? + createStringResource("abstractRoleMemberPanel.recomputeSelectedMembersConfirmationLabel") + : createStringResource("abstractRoleMemberPanel.recomputeAllMembersConfirmationLabel"); + } ConfigureTaskConfirmationPanel dialog = new ConfigureTaskConfirmationPanel(((PageBase)getPage()).getMainPopupBodyId(), confirmModel) { @@ -1245,4 +1258,10 @@ protected void updateMembersPanelSessionStorage(){ protected MemberPanelStorage getMemberPanelStorage(){ return null; } + + protected SearchBoxScopeType getSearchScope() { + DropDownFormGroup searchorgScope = (DropDownFormGroup) get( + createComponentPath(ID_FORM, ID_SEARCH_SCOPE)); + return searchorgScope.getModelObject(); + } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java index 63d59d0606e..e9d2c534e2f 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/users/component/OrgMemberPanel.java @@ -24,7 +24,6 @@ import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.web.component.form.DropDownFormGroup; import com.evolveum.midpoint.web.page.admin.roles.AbstractRoleMemberPanel; import com.evolveum.midpoint.web.page.admin.roles.AvailableRelationDto; import com.evolveum.midpoint.web.page.admin.roles.MemberOperationsHelper; @@ -57,7 +56,7 @@ protected void initLayout() { @Override protected ObjectQuery createMemberQuery(boolean indirect, Collection relations) { ObjectTypes searchType = getSearchType(); - if (SearchBoxScopeType.ONE_LEVEL.equals(getOrgSearchScope())) { + if (SearchBoxScopeType.ONE_LEVEL.equals(getSearchScope())) { if (AssignmentHolderType.class.isAssignableFrom(searchType.getClassDefinition())) { return super.createMemberQuery(indirect, relations); } @@ -73,22 +72,14 @@ protected ObjectQuery createMemberQuery(boolean indirect, Collection rela @Override protected ObjectQuery getActionQuery(QueryScope scope, Collection relations) { - if (SearchBoxScopeType.ONE_LEVEL.equals(getOrgSearchScope())) { + if (SearchBoxScopeType.ONE_LEVEL.equals(getSearchScope()) || + (SearchBoxScopeType.SUBTREE.equals(getSearchScope()) && !QueryScope.ALL.equals(scope))) { return super.getActionQuery(scope, relations); } else { return getSubtreeScopeMembersQuery(); } } - @Override - protected QueryScope getQueryScope() { - if (SearchBoxScopeType.SUBTREE.equals(getOrgSearchScope())) { - return QueryScope.ALL; - } else { - return super.getQueryScope(); - } - } - @Override protected ObjectQuery createAllMemberQuery(Collection relations) { return getPrismContext().queryFor(AssignmentHolderType.class) @@ -111,12 +102,6 @@ private ObjectQuery getSubtreeScopeMembersQuery(){ return query; } - protected SearchBoxScopeType getOrgSearchScope() { - DropDownFormGroup searchorgScope = (DropDownFormGroup) get( - createComponentPath(ID_FORM, ID_SEARCH_SCOPE)); - return searchorgScope.getModelObject(); - } - @Override protected void assignMembers(AjaxRequestTarget target, AvailableRelationDto availableRelationList, List objectTypes, List archetypeRefList, boolean isOrgTreePanelVisible) { From 534386a636f3b95a65edec0bfcdb330beaa9a73d Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Wed, 22 Apr 2020 17:14:09 +0200 Subject: [PATCH 20/27] Removing useless from schema --- .../xml/ns/public/common/common-core-3.xsd | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index ead8e6ac656..3d7be668652 100755 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -14327,40 +14327,36 @@ - - - - - Kind of resource object that should be created on the resource. - - This element is optional. If not specified defaults to account kind. - - - ConstructionType.kind - ConstructionType.kind.help - - - - - - - - - Intent of resource object that should be created on the resource. - - This must point to the a valid definition in - the resource object (in schemaHandling section). - - This element is optional. If not specified then the definition - marked as default in the resource definition should be used. - - - ConstructionType.intent - ConstructionType.intent.help - - - - + + + + Kind of resource object that should be created on the resource. + + This element is optional. If not specified defaults to account kind. + + + ConstructionType.kind + ConstructionType.kind.help + + + + + + + Intent of resource object that should be created on the resource. + + This must point to the a valid definition in + the resource object (in schemaHandling section). + + This element is optional. If not specified then the definition + marked as default in the resource definition should be used. + + + ConstructionType.intent + ConstructionType.intent.help + + + From 6fefdd41339e4c0164aef691b864dbd33cf60112 Mon Sep 17 00:00:00 2001 From: kate Date: Thu, 23 Apr 2020 00:33:41 +0200 Subject: [PATCH 21/27] MID-6034 Add to Cart confirmation messaging --- .../midpoint/web/page/self/AbstractShoppingCartTabPanel.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/AbstractShoppingCartTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/AbstractShoppingCartTabPanel.java index e20c1c0ed07..dd67484e21a 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/AbstractShoppingCartTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/AbstractShoppingCartTabPanel.java @@ -499,8 +499,11 @@ private UserType getTargetUser(){ } protected void assignmentAddedToShoppingCartPerformed(AjaxRequestTarget target){ + getPage().success(getPageBase().createStringResource("AbstractShoppingCartTabPanel.itemIsAddedToShoppingCart", + getRoleCatalogStorage().getAssignmentShoppingCart().size()).getString()); getPageBase().reloadShoppingCartIcon(target); target.add(AbstractShoppingCartTabPanel.this); + target.add(getPageBase().getFeedbackPanel()); } protected QName getNewAssignmentRelation() { From 2c98457f9f5d49ccede487d57c8fdb954d9f9343 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Thu, 23 Apr 2020 13:42:58 +0200 Subject: [PATCH 22/27] Improve Clockwork and Projector code a bit The goal of this work is to make the structure of the processing in Projector (and partially also in Clockwork) more obvious from the source code. 1. Eliminated the wave loop from Projector.projectInternal; moving it into Projector.projectAllWaves. (Note that the projector wave loop is used only for previewing changes.) 2. AssignmentHolderProcessor was cleaned up significantly, factoring out e.g. iteration functionality into IterationHelper. 3. Common entry code in processors (checking focus type and existence, casting LensContext) was factored out to ClockworkMedic, using experimental ProcessorExecution annotation. 4. Common post-processing in processors (integrity checking, cross-component updating, diag logging) was moved to processors themselves, simplifying the orchestration code. 5. PolicyRuleEnforcer.execute and policyRuleSuspendTaskExecutor.execute calls were moved to more appropriate places. 6. FocusProcessor was renamed to FocusActivationProcessor. A couple of other minor changes were done as well, with (presumably) negligible impact on the functionality. --- .../component/progress/ProgressReporter.java | 2 +- .../schema/internals/InternalsConfig.java | 14 +- .../model/api/util/ClockworkInspector.java | 2 +- .../midpoint/model/impl/lens/Clockwork.java | 181 +- .../model/impl/lens/ClockworkMedic.java | 136 +- .../midpoint/model/impl/lens/LensContext.java | 56 +- .../model/impl/lens/LensFocusContext.java | 9 +- .../impl/lens/LensProjectionContext.java | 3024 +++++++++-------- .../midpoint/model/impl/lens/LensUtil.java | 2336 ++++++------- .../lens/projector/ActivationProcessor.java | 116 +- .../model/impl/lens/projector/Components.java | 36 + .../projector/ConsolidationProcessor.java | 17 +- .../impl/lens/projector/ContextLoader.java | 41 +- .../lens/projector/DependencyProcessor.java | 65 +- .../lens/projector/OutboundProcessor.java | 3 + .../projector/ProjectionValuesProcessor.java | 205 +- .../model/impl/lens/projector/Projector.java | 315 +- .../lens/projector/ProjectorProcessor.java | 22 + .../projector/ReconciliationProcessor.java | 54 +- .../credentials/CredentialsProcessor.java | 11 +- .../ProjectionCredentialsProcessor.java | 41 +- .../focus/AssignmentHolderProcessor.java | 867 +---- .../projector/focus/AssignmentProcessor.java | 102 +- ...sor.java => FocusActivationProcessor.java} | 115 +- .../focus/FocusLifecycleProcessor.java | 370 +- .../projector/focus/InboundProcessor.java | 61 +- .../focus/ItemLimitationsChecker.java | 81 + .../lens/projector/focus/IterationHelper.java | 360 ++ .../focus/ObjectTemplateProcessor.java | 35 +- .../projector/policy/PolicyRuleEnforcer.java | 4 +- .../projector/policy/PolicyRuleProcessor.java | 15 +- .../projector/util/ProcessorExecution.java | 52 + .../lens/projector/util/ProcessorMethod.java | 31 + .../projector/util/ProcessorMethodRef.java | 32 + .../ProjectionAwareProcessorMethodRef.java | 33 + .../util/SimplifiedProcessorMethodRef.java | 32 + .../projector/util/SkipWhenFocusDeleted.java | 31 + .../intest/security/AbstractSecurityTest.java | 2 +- .../src/test/resources/logback-test.xml | 2 +- .../report/impl/ReportManagerImpl.java | 25 +- .../midpoint/wf/impl/hook/WfHook.java | 1 - .../enforcer/impl/SecurityEnforcerImpl.java | 4 +- .../story/src/test/resources/logback-test.xml | 2 +- 43 files changed, 4469 insertions(+), 4474 deletions(-) create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java rename model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/{FocusProcessor.java => FocusActivationProcessor.java} (82%) create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java index f2d276f3dec..ff16cd4f5c2 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java @@ -53,7 +53,7 @@ public class ProgressReporter implements ProgressListener { private Map nameCache = new HashMap<>(); private ProgressDto progress = new ProgressDto(); - private boolean abortRequested; + private volatile boolean abortRequested; // Operation result got from the asynchronous operation (null if async op not yet finished) private OperationResult asyncOperationResult; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java index 8c7f87e861f..fd700e2f872 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java @@ -54,7 +54,7 @@ public class InternalsConfig { */ private static TestingPaths testingPaths = null; - private static boolean detailedAuhotizationLog = false; + private static boolean detailedAuthorizationLog = false; public static boolean isPrismMonitoring() { return prismMonitoring; @@ -120,12 +120,12 @@ public static void setTestingPaths(TestingPaths testingPaths) { InternalsConfig.testingPaths = testingPaths; } - public static boolean isDetailedAuhotizationLog() { - return detailedAuhotizationLog; + public static boolean isDetailedAuthorizationLog() { + return detailedAuthorizationLog; } - public static void setDetailedAuhotizationLog(boolean detailedAuhotizationLog) { - InternalsConfig.detailedAuhotizationLog = detailedAuhotizationLog; + public static void setDetailedAuthorizationLog(boolean detailedAuthorizationLog) { + InternalsConfig.detailedAuthorizationLog = detailedAuthorizationLog; } public static boolean isAllowClearDataLogging() { @@ -159,7 +159,7 @@ public static void set(Configuration internalsConfig) { prismMonitoring = internalsConfig.getBoolean("prismMonitoring", prismMonitoring); modelProfiling = internalsConfig.getBoolean("modelProfiling", modelProfiling); // TODO: testingPaths - detailedAuhotizationLog = internalsConfig.getBoolean("detailedAuhotizationLog", detailedAuhotizationLog); + detailedAuthorizationLog = internalsConfig.getBoolean("detailedAuhotizationLog", detailedAuthorizationLog); } @@ -173,7 +173,7 @@ public static void reset() { prismMonitoring = false; modelProfiling = false; testingPaths = null; - detailedAuhotizationLog = false; + detailedAuthorizationLog = false; } public static void setDevelopmentMode() { diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java index 58bd8aa7986..fed2e2f2827 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java @@ -13,7 +13,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; /** - * Interface used to intercept the SyncContext as it passes through the computation. + * Interface used to intercept the ModelContext as it passes through the computation. * * It is mostly used in tests. * 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 47b24d73c9d..5da53f48e82 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 @@ -16,6 +16,11 @@ import java.util.Collection; import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.model.impl.lens.projector.Components; + +import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEnforcer; +import com.evolveum.midpoint.prism.delta.ReferenceDelta; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -52,7 +57,6 @@ import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; import com.evolveum.midpoint.schema.cache.CacheType; import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultBuilder; import com.evolveum.midpoint.schema.result.OperationResultStatus; @@ -95,6 +99,7 @@ public class Clockwork { @Autowired private Migrator migrator; @Autowired private ClockworkMedic medic; @Autowired private PolicyRuleScriptExecutor policyRuleScriptExecutor; + @Autowired private PolicyRuleEnforcer policyRuleEnforcer; @Autowired private PolicyRuleSuspendTaskExecutor policyRuleSuspendTaskExecutor; @Autowired private ClockworkAuthorizationHelper clockworkAuthorizationHelper; @Autowired private CacheConfigurationManager cacheConfigurationManager; @@ -124,9 +129,7 @@ public HookOperationMode run(LensContext context, Task } LOGGER.trace("Running clockwork for context {}", context); - if (InternalsConfig.consistencyChecks) { - context.checkConsistence(); - } + context.checkConsistenceIfNeeded(); int clicked = 0; ClockworkConflictResolver.Context conflictResolutionContext = new ClockworkConflictResolver.Context(); @@ -141,6 +144,8 @@ public HookOperationMode run(LensContext context, Task //enterDefaultSearchExpressionEvaluatorCache(); provisioningService.enterConstraintsCheckerCache(); + executeInitialChecks(context); + while (context.getState() != ModelState.FINAL) { int maxClicks = getMaxClicks(result); @@ -191,6 +196,46 @@ public HookOperationMode run(LensContext context, Task } } + private void executeInitialChecks(LensContext context) throws PolicyViolationException { + if (context.hasFocusOfType(AssignmentHolderType.class)) { + checkArchetypeRefDelta(context); + } + } + + /** + * This check was originally present in AssignmentHolderProcessor. But it refers to focus primary delta only; + * so it's sufficient to execute it on clockwork entry (unless primary delta is manipulated e.g. in a scripting hook). + */ + private void checkArchetypeRefDelta(LensContext context) throws PolicyViolationException { + ObjectDelta focusPrimaryDelta = context.getFocusContext().getPrimaryDelta(); + if (focusPrimaryDelta != null) { + ReferenceDelta archetypeRefDelta = focusPrimaryDelta.findReferenceModification(AssignmentHolderType.F_ARCHETYPE_REF); + if (archetypeRefDelta != null) { + // We want to allow this under special circumstances. E.g. we want be able to import user with archetypeRef. + // Otherwise we won't be able to export a user and re-import it again. + if (focusPrimaryDelta.isAdd()) { + String archetypeOidFromAssignments = LensUtil.determineExplicitArchetypeOidFromAssignments(focusPrimaryDelta.getObjectToAdd()); + if (archetypeOidFromAssignments == null) { + throw new PolicyViolationException("Attempt add archetypeRef without a matching assignment"); + } else { + boolean match = true; + for (PrismReferenceValue archetypeRefDeltaVal : archetypeRefDelta.getValuesToAdd()) { + if (!archetypeOidFromAssignments.equals(archetypeRefDeltaVal.getOid())) { + match = false; + } + } + if (match) { + return; + } else { + throw new PolicyViolationException("Attempt add archetypeRef that does not match assignment"); + } + } + } + throw new PolicyViolationException("Attempt to modify archetypeRef directly"); + } + } + } + // todo check authorization in this method private boolean startTracingIfRequested(LensContext context, Task task, OperationResultBuilder builder, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, @@ -246,6 +291,7 @@ public LensContext previewChanges(LensContext conte projector.projectAllWaves(context, "preview", task, result); clockworkHookHelper.invokePreview(context, task, result); + policyRuleEnforcer.execute(context); policyRuleSuspendTaskExecutor.execute(context, task, result); } catch (ConfigurationException | SecurityViolationException | ObjectNotFoundException | SchemaException | @@ -365,68 +411,20 @@ public HookOperationMode click(LensContext context, Ta context.generateRequestIdentifierIfNeeded(); // We need to do this BEFORE projection. If we would do that after projection // there will be secondary changes that are not part of the request. - clockworkAuditHelper.audit(context, AuditEventStage.REQUEST, task, result, parentResult); // we need to take the overall ("run" operation result) not the current one + clockworkAuditHelper.audit(context, AuditEventStage.REQUEST, task, result, parentResult); // we need to take the overall ("run" operation result) not the current one } - boolean recompute = false; - if (!context.isFresh()) { - LOGGER.trace("Context is not fresh -- forcing cleanup and recomputation"); - recompute = true; - } else if (context.getExecutionWave() > context.getProjectionWave()) { // should not occur - LOGGER.warn("Execution wave is greater than projection wave -- forcing cleanup and recomputation"); - recompute = true; - } else if (state == ModelState.PRIMARY && ModelExecuteOptions.getInitialPartialProcessing(context.getOptions()) != null) { - LOGGER.trace("Initial phase was run with initialPartialProcessing option -- forcing cleanup and recomputation"); - recompute = true; - } - - if (recompute) { - context.cleanup(); - LOGGER.trace("Running projector with cleaned-up context for execution wave {}", context.getExecutionWave()); - projector.project(context, "PROJECTOR ("+state+")", task, result); - } else if (context.getExecutionWave() == context.getProjectionWave()) { - LOGGER.trace("Resuming projector for execution wave {}", context.getExecutionWave()); - projector.resume(context, "PROJECTOR ("+state+")", task, result); - } else { - LOGGER.trace("Skipping projection because the context is fresh and projection for current wave has already run"); - } + projectIfNeeded(context, task, result); if (!context.isRequestAuthorized()) { clockworkAuthorizationHelper.authorizeContextRequest(context, task, result); } medic.traceContext(LOGGER, "CLOCKWORK (" + state + ")", "before processing", true, context, false); - if (InternalsConfig.consistencyChecks) { - try { - context.checkConsistence(); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage()+" in clockwork, state="+state, e); - } - } - if (InternalsConfig.encryptionChecks && !ModelExecuteOptions.isNoCrypt(context.getOptions())) { - context.checkEncrypted(); - } + context.checkConsistenceIfNeeded(); + context.checkEncryptedIfNeeded(); - switch (state) { - case INITIAL: - processInitialToPrimary(context); - break; - case PRIMARY: - processPrimaryToSecondary(context, task, result); - break; - case SECONDARY: - if (context.getExecutionWave() > context.getMaxWave() + 1) { - processSecondaryToFinal(context, task, result); - } else { - processSecondary(context, task, result, parentResult); - } - break; - case FINAL: - HookOperationMode mode = processFinal(context, task, result, parentResult); - medic.clockworkFinish(context); - return mode; - } - return clockworkHookHelper.invokeHooks(context, task, result); + return moveStateForward(context, task, parentResult, result, state); } catch (CommunicationException | ConfigurationException | ExpressionEvaluationException | ObjectNotFoundException | PolicyViolationException | SchemaException | SecurityViolationException | RuntimeException | Error | @@ -445,21 +443,76 @@ public HookOperationMode click(LensContext context, Ta } } + private void projectIfNeeded(LensContext context, Task task, OperationResult result) + throws SchemaException, ConfigurationException, PolicyViolationException, ExpressionEvaluationException, + ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, SecurityViolationException, + PreconditionViolationException { + boolean recompute = false; + if (!context.isFresh()) { + LOGGER.trace("Context is not fresh -- forcing cleanup and recomputation"); + recompute = true; + } else if (context.getExecutionWave() > context.getProjectionWave()) { // should not occur + LOGGER.warn("Execution wave is greater than projection wave -- forcing cleanup and recomputation"); + recompute = true; + } else if (context.isInPrimary() && ModelExecuteOptions.getInitialPartialProcessing(context.getOptions()) != null) { + LOGGER.trace("Initial phase was run with initialPartialProcessing option -- forcing cleanup and recomputation"); + recompute = true; + } + + if (recompute) { + context.cleanup(); + LOGGER.trace("Running projector with cleaned-up context for execution wave {}", context.getExecutionWave()); + projector.project(context, "PROJECTOR ("+ context.getState() +")", task, result); + } else if (context.getExecutionWave() == context.getProjectionWave()) { + LOGGER.trace("Resuming projector for execution wave {}", context.getExecutionWave()); + projector.resume(context, "PROJECTOR ("+ context.getState() +")", task, result); + } else { + LOGGER.trace("Skipping projection because the context is fresh and projection for current wave has already run"); + } + } + + private HookOperationMode moveStateForward(LensContext context, Task task, OperationResult parentResult, + OperationResult result, ModelState state) throws PolicyViolationException, ObjectNotFoundException, SchemaException, + ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, PreconditionViolationException { + switch (state) { + case INITIAL: + processInitialToPrimary(context); + break; + case PRIMARY: + processPrimaryToSecondary(context, task, result); + break; + case SECONDARY: + if (context.getExecutionWave() > context.getMaxWave() + 1) { + processSecondaryToFinal(context, task, result); + } else { + processSecondary(context, task, result, parentResult); + } + break; + case FINAL: + HookOperationMode mode = processFinal(context, task, result, parentResult); + medic.clockworkFinish(context); + return mode; + } + return clockworkHookHelper.invokeHooks(context, task, result); + } + private void switchState(LensContext context, ModelState newState) { medic.clockworkStateSwitch(context, newState); context.setState(newState); } - private void processInitialToPrimary(LensContext context) { - // Context loaded, nothing special do. Bump state to PRIMARY. + private void processInitialToPrimary(LensContext context) throws PolicyViolationException { + // To mimic operation of the original enforcer hook, we execute the following only in the initial state. + policyRuleEnforcer.execute(context); + switchState(context, ModelState.PRIMARY); } private void processPrimaryToSecondary(LensContext context, Task task, OperationResult result) throws PolicyViolationException, ObjectNotFoundException, SchemaException { - // Nothing to do now. The context is already recomputed. - switchState(context, ModelState.SECONDARY); - policyRuleSuspendTaskExecutor.execute(context, task, result); + + switchState(context, ModelState.SECONDARY); } private void processSecondary(LensContext context, Task task, OperationResult result, OperationResult overallResult) @@ -468,13 +521,13 @@ private void processSecondary(LensContext context, Tas Holder restartRequestedHolder = new Holder<>(false); - medic.partialExecute("execution", + medic.partialExecute(Components.EXECUTION, (result1) -> { boolean restartRequested = changeExecutor.executeChanges(context, task, result1); restartRequestedHolder.setValue(restartRequested); }, context.getPartialProcessingOptions()::getExecution, - Clockwork.class, context, result); + Clockwork.class, context, null, result); clockworkAuditHelper.audit(context, AuditEventStage.EXECUTION, task, result, overallResult); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java index aba54f85b4c..aaef8cbbc39 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java @@ -8,8 +8,12 @@ import java.util.function.Supplier; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.*; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; +import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectorComponentTraceType; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -37,6 +41,8 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingTypeType; +import javax.xml.datatype.XMLGregorianCalendar; + import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; /** @@ -146,12 +152,132 @@ public void afterMappingEvaluation(LensContext context } } - public void partialExecute(String componentName, ProjectorComponentRunnable runnable, - Supplier optionSupplier, - Class executingClass, LensContext context, OperationResult parentResult) + @SuppressWarnings({ "rawtypes", "UnusedReturnValue" }) + public boolean partialExecute(String componentName, ProjectorProcessor processor, + ProjectionAwareProcessorMethodRef method, Supplier optionSupplier, + Class executingClass, LensContext context, LensProjectionContext projectionContext, String activityDescription, + XMLGregorianCalendar now, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { - partialExecute(componentName, runnable, optionSupplier, executingClass, context, null, parentResult); + if (shouldExecute(componentName, processor, context, projectionContext)) { + partialExecute(componentName, (result1) -> { + //noinspection unchecked + method.run(context, projectionContext, activityDescription, now, task, result1); + }, optionSupplier, executingClass, context, projectionContext, parentResult); + return true; + } else { + return false; + } + } + + @SuppressWarnings("rawtypes") + public boolean partialExecute(String componentName, ProjectorProcessor processor, + ProcessorMethodRef method, Supplier optionSupplier, + Class executingClass, LensContext context, String activityDescription, + XMLGregorianCalendar now, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, + PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { + if (shouldExecute(componentName, processor, context, null)) { + partialExecute(componentName, (result1) -> { + //noinspection unchecked + method.run(context, activityDescription, now, task, result1); + }, optionSupplier, executingClass, context, null, parentResult); + return true; + } else { + return false; + } + } + + @SuppressWarnings({ "UnusedReturnValue", "rawtypes" }) + public boolean partialExecute(String componentName, ProjectorProcessor processor, + SimplifiedProcessorMethodRef method, Supplier optionSupplier, + Class executingClass, LensContext context, + XMLGregorianCalendar now, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, + PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { + if (shouldExecute(componentName, processor, context, null)) { + partialExecute(componentName, (result1) -> { + //noinspection unchecked + method.run(context, now, task, result1); + }, optionSupplier, executingClass, context, null, parentResult); + return true; + } else { + return false; + } + } + + private boolean shouldExecute(String componentName, ProjectorProcessor processor, LensContext context, LensProjectionContext projectionContext) throws SchemaException { + ProcessorExecution processorExecution = processor.getClass().getAnnotation(ProcessorExecution.class); + return processorExecution == null || focusPresenceAndTypeCheckPasses(componentName, context, processorExecution) + && focusDeletionCheckPasses(componentName, context.getFocusContext(), processorExecution) + && projectionDeletionCheckPasses(componentName, projectionContext, processorExecution); + } + + private boolean focusPresenceAndTypeCheckPasses(String componentName, LensContext context, + ProcessorExecution processorExecution) { + if (!processorExecution.focusRequired()) { + // intentionally skipping focus type check + return true; + } + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + LOGGER.trace("Skipping {} processing because focus context is null", componentName); + return false; + } + Class requiredFocusType = processorExecution.focusType(); + if (!focusContext.isOfType(requiredFocusType)) { + LOGGER.trace("Skipping {} processing because focus context is not of required focus class ({})", + componentName, requiredFocusType.getSimpleName()); + return false; + } + return true; + } + + private boolean focusDeletionCheckPasses(String componentName, LensFocusContext focusContext, + ProcessorExecution processorExecution) throws SchemaException { + if (focusContext == null) { + // If we are OK with no focus context, then the deletion is irrelevant + return true; + } + SkipWhenFocusDeleted skipWhenFocusDeleted = processorExecution.skipWhenFocusDeleted(); + switch (skipWhenFocusDeleted) { + case NONE: + return true; + case PRIMARY_OR_SECONDARY: + return focusPrimaryDeletionCheckPasses(focusContext, componentName) + && focusSecondaryDeletionCheckPasses(focusContext, componentName); + case PRIMARY: + return focusPrimaryDeletionCheckPasses(focusContext, componentName); + default: + throw new AssertionError(skipWhenFocusDeleted); + } + } + + private boolean focusPrimaryDeletionCheckPasses(LensFocusContext focusContext, String componentName) { + if (focusContext.isDelete()) { + LOGGER.trace("Skipping '{}' because focus is being deleted (primary delta)", componentName); + return false; + } else { + return true; + } + } + + private boolean focusSecondaryDeletionCheckPasses(LensFocusContext focusContext, String componentName) throws SchemaException { + if (focusContext.isSecondaryDelete()) { + LOGGER.trace("Skipping '{}' because focus is being deleted (secondary delta)", componentName); + return false; + } else { + return true; + } + } + + private boolean projectionDeletionCheckPasses(String componentName, LensProjectionContext projectionContext, ProcessorExecution processorExecution) { + if (processorExecution.skipWhenProjectionDeleted() && projectionContext != null && projectionContext.isDelete()) { + LOGGER.trace("Skipping '{}' because projection is being deleted", componentName); + return false; + } else { + return true; + } } public void partialExecute(String baseComponentName, ProjectorComponentRunnable runnable, @@ -160,6 +286,8 @@ public void partialExecute(String baseComponentName, ProjectorComponentRunnable throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { + context.checkAbortRequested(); + OperationResult parentResult; if (initialParentResult == null) { LOGGER.warn("No parentResult in ClockworkMedic.partialExecute! Creating dummy one"); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java index 49678098608..ab0954ef30d 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java @@ -27,6 +27,7 @@ import com.evolveum.midpoint.schema.ObjectTreeDeltas; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ObjectDeltaSchemaLevelUtil; @@ -193,7 +194,7 @@ public enum ExportType { * Moved from ProjectionValuesProcessor TODO consider if necessary to * serialize to XML */ - private List conflictingProjectionContexts = new ArrayList<>(); + @NotNull private final List conflictingProjectionContexts = new ArrayList<>(); transient private boolean preview; @@ -432,6 +433,17 @@ public int getMaxWave() { return maxWave; } + // TODO This method is currently used only when previewing changes. Is that OK? + public int computeMaxWaves() { + if (getPartialProcessingOptions().getInbound() != PartialProcessingTypeType.SKIP) { + // Let's do one extra wave with no accounts in it. This time we expect to get the results of the execution to the user + // via inbound, e.g. identifiers generated by the resource, DNs and similar things. Hence the +2 instead of +1 + return getMaxWave() + 2; + } else { + return getMaxWave() + 1; + } + } + public boolean isFresh() { return isFresh; } @@ -787,10 +799,19 @@ public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { public void checkAbortRequested() { if (isAbortRequested()) { - throw new RuntimeException("Aborted on user request"); // TODO more - // meaningful - // exception - // + message + throw new RuntimeException("Aborted on user request"); // TODO more meaningful exception + message + } + } + + public void checkConsistenceIfNeeded() { + if (InternalsConfig.consistencyChecks) { + try { + checkConsistence(); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage()+" in clockwork, state="+state, e); + } + } else { + checkAbortRequested(); // useful to check as often as realistically possible } } @@ -805,6 +826,12 @@ public void checkConsistence() { } } + public void checkEncryptedIfNeeded() { + if (InternalsConfig.encryptionChecks && !ModelExecuteOptions.isNoCrypt(options)) { + checkEncrypted(); + } + } + public void checkEncrypted() { if (focusContext != null && !focusContext.isDelete()) { focusContext.checkEncrypted(); @@ -904,10 +931,8 @@ public void normalize() { if (focusContext != null) { focusContext.normalize(); } - if (projectionContexts != null) { - for (LensProjectionContext projectionContext : projectionContexts) { - projectionContext.normalize(); - } + for (LensProjectionContext projectionContext : projectionContexts) { + projectionContext.normalize(); } } @@ -1415,6 +1440,7 @@ public void setSequenceCounter(String sequenceOid, long counter) { sequences.put(sequenceOid, counter); } + @NotNull public List getConflictingProjectionContexts() { return conflictingProjectionContexts; } @@ -1648,4 +1674,16 @@ ObjectDeltaSchemaLevelUtil.NameResolver getNameResolver() { return null; }; } + + public void removeIgnoredContexts() { + projectionContexts.removeIf(projCtx -> projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE); + } + + public boolean isInPrimary() { + return state == ModelState.PRIMARY; + } + + public boolean hasFocusOfType(Class type) { + return focusContext != null && focusContext.isOfType(type); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java index 58a13b23884..dd0050e839f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java @@ -60,10 +60,6 @@ private int getProjectionWave() { return getLensContext().getProjectionWave(); } - private int getExecutionWave() { - return getLensContext().getProjectionWave(); - } - public ArchetypePolicyType getArchetypePolicyType() { return archetypePolicyType; } @@ -105,6 +101,11 @@ public boolean isDelete() { return ObjectDelta.isDelete(getPrimaryDelta()); } + boolean isSecondaryDelete() throws SchemaException { + ObjectDelta userSecondaryDelta = getProjectionWaveSecondaryDelta(); + return userSecondaryDelta != null && ChangeType.DELETE.equals(userSecondaryDelta.getChangeType()); + } + public boolean isAdd() { return ObjectDelta.isAdd(getPrimaryDelta()); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java index 8b8f4ef0e58..0f8da857c39 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java @@ -1,1507 +1,1517 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.function.Consumer; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.refinery.*; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.schema.DeltaConvertor; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.jvnet.jaxb2_commons.lang.Validate; - -import com.evolveum.midpoint.common.crypto.CryptoUtil; -import com.evolveum.midpoint.model.api.context.ModelProjectionContext; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.prism.delta.ChangeType; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ReferenceDelta; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.processor.ResourceAttribute; -import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; -import com.evolveum.midpoint.schema.processor.ResourceSchema; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.schema.util.SchemaDebugUtil; -import com.evolveum.midpoint.util.Cloner; -import com.evolveum.midpoint.util.DebugUtil; - -/** - * @author semancik - * - */ -public class LensProjectionContext extends LensElementContext implements ModelProjectionContext { - - private static final Trace LOGGER = TraceManager.getTrace(LensProjectionContext.class); - - private ObjectDelta syncDelta; - - /** - * Is this projection the source of the synchronization? (The syncDelta attribute could be used for this but in - * reality it is not always present.) We need this information e.g. when it's not possible to record a clockwork - * exception to focus (e.g. as in MID-5801). The alternate way is to record it into shadow representing the synchronization - * source, e.g. the object being imported, reconciled, or live-synced. - */ - private boolean synchronizationSource; - - private ObjectDelta secondaryDelta; - - /** - * If set to true: absolute state of this projection was detected by the synchronization. - * This is mostly for debugging and visibility. It is not used by projection logic. - */ - private boolean syncAbsoluteTrigger = false; - - /** - * The wave in which this resource should be processed. Initial value of -1 means "undetermined". - */ - private int wave = -1; - - /** - * Indicates that the wave computation is still in progress. - */ - private transient boolean waveIncomplete = false; - - /** - * Definition of account type. - */ - private ResourceShadowDiscriminator resourceShadowDiscriminator; - - private boolean fullShadow = false; - - /** - * True if the account is assigned to the user by a valid assignment. It may be false for accounts that are either - * found to be illegal by live sync, were unassigned from user, etc. - * If set to null the situation is not yet known. Null is a typical value when the context is constructed. - */ - private boolean isAssigned; - private boolean isAssignedOld; - - /** - * True if the account should be part of the synchronization. E.g. outbound expression should be applied to it. - */ - private boolean isActive; - - /** - * True if there is a valid assignment for this projection and/or the policy allows such projection to exist. - */ - private Boolean isLegal = null; - private Boolean isLegalOld = null; - - /** - * True if the projection exists (or will exist) on resource. False if it does not exist. - * NOTE: entire projection is loaded with pointInTime=future. Therefore this does NOT - * reflect actual situation. If there is a pending operation to create the object then - * isExists will in fact be true. - */ - private boolean isExists; - - /** - * True if shadow exists in the repo. It is set to false after projector discovers that a shadow is gone. - * This is a corner case, but it may happen: if shadow is unintentionally deleted, if the shadow is - * cleaned up by another thread and so on. - */ - private transient boolean shadowExistsInRepo = true; - - /** - * Decision regarding the account. It indicated what the engine has DECIDED TO DO with the context. - * If set to null no decision was made yet. Null is also a typical value when the context is created. - */ - private SynchronizationPolicyDecision synchronizationPolicyDecision; - - /** - * True if we want to reconcile account in this context. - */ - private boolean doReconciliation; - - /** - * false if the context should be not taken into the account while synchronizing changes from other resource - */ - private boolean canProject = true; - - /** - * Synchronization situation as it was originally detected by the synchronization code (SynchronizationService). - * This is mostly for debug purposes. Projector and clockwork do not need to care about this. - * The synchronization intent is used instead. - */ - private SynchronizationSituationType synchronizationSituationDetected = null; - /** - * Synchronization situation which was the result of synchronization reaction (projector and clockwork run). - * This is mostly for debug purposes. Projector and clockwork do not care about this (except for setting it). - * The synchronization decision is used instead. - */ - private SynchronizationSituationType synchronizationSituationResolved = null; - - /** - * Delta set triple for accounts. Specifies which accounts should be added, removed or stay as they are. - * It tells almost nothing about attributes directly although the information about attributes are inside - * each account construction (in a form of ValueConstruction that contains attribute delta triples). - * - * Intermediary computation result. It is stored to allow re-computing of account constructions during - * iterative computations. - * - * Source: AssignmentProcessor - * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) - */ - private transient PrismValueDeltaSetTriple> constructionDeltaSetTriple; - - /** - * Triples for outbound mappings; similar to the above. - * Source: OutboundProcessor - * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) - */ - private transient Construction outboundConstruction; - - /** - * Postprocessed triples from the above two properties. - * Source: ConsolidationProcessor - * Target: ReconciliationProcessor - */ - private transient Map,PrismPropertyDefinition>>> squeezedAttributes; - private transient Map,PrismContainerDefinition>>> squeezedAssociations; - private transient Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses; - - private transient Collection dependencies = null; - - // Cached copy, to avoid constructing it over and over again - private transient PrismObjectDefinition shadowDefinition = null; - - private transient RefinedObjectClassDefinition structuralObjectClassDefinition; - private transient Collection auxiliaryObjectClassDefinitions; - private transient CompositeRefinedObjectClassDefinition compositeObjectClassDefinition; - - private SecurityPolicyType projectionSecurityPolicy; - - /** - * Resource that hosts this projection. - */ - transient private ResourceType resource; - - /** - * EXPERIMENTAL. A flag that this projection context has to be put into 'history archive'. - * Necessary to evaluate old state of hasLinkedAccount. - * - * TODO implement as non-transient. - */ - transient private boolean toBeArchived; - - transient private String humanReadableName; - - private Map> entitlementMap = new HashMap<>(); - - transient private String humanReadableString; - - LensProjectionContext(LensContext lensContext, ResourceShadowDiscriminator resourceAccountType) { - super(ShadowType.class, lensContext); - this.resourceShadowDiscriminator = resourceAccountType; - this.isAssigned = false; - this.isAssignedOld = false; - } - - public ObjectDelta getSyncDelta() { - return syncDelta; - } - - public void setSyncDelta(ObjectDelta syncDelta) { - this.syncDelta = syncDelta; - } - - @Override - public ObjectDelta getSecondaryDelta() { - return secondaryDelta; - } - - @Override - public Collection> getAllDeltas() { - List> deltas = new ArrayList<>(2); - ObjectDelta primaryDelta = getPrimaryDelta(); - if (primaryDelta != null) { - deltas.add(primaryDelta); - } - if (secondaryDelta != null) { - deltas.add(secondaryDelta); - } - return deltas; - } - - @Override - public ObjectDeltaObject getObjectDeltaObject() throws SchemaException { - return new ObjectDeltaObject<>(getObjectCurrent(), getDelta(), getObjectNew(), getObjectDefinition()); - } - - public boolean hasSecondaryDelta() { - return secondaryDelta != null && !secondaryDelta.isEmpty(); - } - - @Override - public void setSecondaryDelta(ObjectDelta secondaryDelta) { - this.secondaryDelta = secondaryDelta; - } - - public void addSecondaryDelta(ObjectDelta delta) throws SchemaException { - if (secondaryDelta == null) { - secondaryDelta = delta; - } else { - secondaryDelta.merge(delta); - } - } - - @Override - public void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException { - if (secondaryDelta == null) { - secondaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); - secondaryDelta.setOid(getOid()); - } - LensUtil.setDeltaOldValue(this, itemDelta); - secondaryDelta.swallow(itemDelta); - } - - @Override - public void deleteSecondaryDeltas() { - secondaryDelta = null; - } - - @Override - public void setOid(String oid) { - super.setOid(oid); - if (secondaryDelta != null) { - secondaryDelta.setOid(oid); - } - } - - public boolean isSyncAbsoluteTrigger() { - return syncAbsoluteTrigger; - } - - public void setSyncAbsoluteTrigger(boolean syncAbsoluteTrigger) { - this.syncAbsoluteTrigger = syncAbsoluteTrigger; - } - - public int getWave() { - return wave; - } - - public void setWave(int wave) { - this.wave = wave; - } - - public boolean isWaveIncomplete() { - return waveIncomplete; - } - - public void setWaveIncomplete(boolean waveIncomplete) { - this.waveIncomplete = waveIncomplete; - } - - public boolean isDoReconciliation() { - return doReconciliation; - } - - public void setDoReconciliation(boolean doReconciliation) { - this.doReconciliation = doReconciliation; - } - - @Override - public ResourceShadowDiscriminator getResourceShadowDiscriminator() { - return resourceShadowDiscriminator; - } - - public void markTombstone() { - if (resourceShadowDiscriminator != null) { - resourceShadowDiscriminator.setTombstone(true); - } - setExists(false); - setFullShadow(false); - humanReadableName = null; - } - - public void setResourceShadowDiscriminator(ResourceShadowDiscriminator resourceShadowDiscriminator) { - this.resourceShadowDiscriminator = resourceShadowDiscriminator; - } - - public boolean compareResourceShadowDiscriminator(ResourceShadowDiscriminator rsd, boolean compareOrder) { - Validate.notNull(rsd.getResourceOid()); - if (resourceShadowDiscriminator == null) { - // This may be valid case e.g. in case of broken contexts or if a context is just loading - return false; - } - if (!rsd.getResourceOid().equals(resourceShadowDiscriminator.getResourceOid())) { - return false; - } - if (!rsd.getKind().equals(resourceShadowDiscriminator.getKind())) { - return false; - } - if (rsd.isTombstone() != resourceShadowDiscriminator.isTombstone()) { - return false; - } - if (rsd.getIntent() == null) { - try { - if (!getStructuralObjectClassDefinition().isDefaultInAKind()) { - return false; - } - } catch (SchemaException e) { - throw new SystemException("Internal error: "+e.getMessage(), e); - } - } else if (!rsd.getIntent().equals(resourceShadowDiscriminator.getIntent())) { - return false; - } - if (!Objects.equals(rsd.getTag(), resourceShadowDiscriminator.getTag())) { - return false; - } - - if (compareOrder && rsd.getOrder() != resourceShadowDiscriminator.getOrder()) { - return false; - } - - return true; - } - - public boolean isTombstone() { - if (resourceShadowDiscriminator == null) { - return false; - } - return resourceShadowDiscriminator.isTombstone(); - } - - public void addAccountSyncDelta(ObjectDelta delta) throws SchemaException { - if (syncDelta == null) { - syncDelta = delta; - } else { - syncDelta.merge(delta); - } - } - - public boolean isAdd() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return ObjectDelta.isAdd(getPrimaryDelta()) || ObjectDelta.isAdd(getSecondaryDelta()); - } - } - - public boolean isModify() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.KEEP) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return super.isModify(); - } - } - - public boolean isDelete() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return ObjectDelta.isDelete(syncDelta) || ObjectDelta.isDelete(getPrimaryDelta()) || ObjectDelta.isDelete(getSecondaryDelta()); - } - } - - @Override - public ArchetypeType getArchetype() { - throw new UnsupportedOperationException("Archetypes are not supported for projections."); - } - - public ResourceType getResource() { - return resource; - } - - public void setResource(ResourceType resource) { - this.resource = resource; - } - - public Map> getEntitlementMap() { - return entitlementMap; - } - - public void setEntitlementMap(Map> entitlementMap) { - this.entitlementMap = entitlementMap; - } - - @Override - public PrismObjectDefinition getObjectDefinition() { - if (shadowDefinition == null) { - try { - shadowDefinition = ShadowUtil.applyObjectClass(super.getObjectDefinition(), getCompositeObjectClassDefinition()); - } catch (SchemaException e) { - // This should not happen - throw new SystemException(e.getMessage(), e); - } - } - return shadowDefinition; - } - - public boolean isAssigned() { - return isAssigned; - } - - public void setAssigned(boolean isAssigned) { - this.isAssigned = isAssigned; - } - - public boolean isAssignedOld() { - return isAssignedOld; - } - - public void setAssignedOld(boolean isAssignedOld) { - this.isAssignedOld = isAssignedOld; - } - - public boolean isActive() { - return isActive; - } - - public void setActive(boolean isActive) { - this.isActive = isActive; - } - - public Boolean isLegal() { - return isLegal; - } - - public void setLegal(Boolean isLegal) { - this.isLegal = isLegal; - } - - public Boolean isLegalOld() { - return isLegalOld; - } - - public void setLegalOld(Boolean isLegalOld) { - this.isLegalOld = isLegalOld; - } - - public boolean isExists() { - return isExists; - } - - public void setExists(boolean exists) { - this.isExists = exists; - } - - public boolean isShadowExistsInRepo() { - return shadowExistsInRepo; - } - - public void setShadowExistsInRepo(boolean shadowExistsInRepo) { - this.shadowExistsInRepo = shadowExistsInRepo; - } - - public SynchronizationPolicyDecision getSynchronizationPolicyDecision() { - return synchronizationPolicyDecision; - } - - public void setSynchronizationPolicyDecision(SynchronizationPolicyDecision policyDecision) { - this.synchronizationPolicyDecision = policyDecision; - } - - public SynchronizationSituationType getSynchronizationSituationDetected() { - return synchronizationSituationDetected; - } - - public void setSynchronizationSituationDetected( - SynchronizationSituationType synchronizationSituationDetected) { - this.synchronizationSituationDetected = synchronizationSituationDetected; - } - - public SynchronizationSituationType getSynchronizationSituationResolved() { - return synchronizationSituationResolved; - } - - void setSynchronizationSituationResolved(SynchronizationSituationType synchronizationSituationResolved) { - this.synchronizationSituationResolved = synchronizationSituationResolved; - } - - public boolean isFullShadow() { - return fullShadow; - } - - /** - * Returns true if full shadow is available, either loaded or in a create delta. - */ - public boolean hasFullShadow() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { - return true; - } - return isFullShadow(); - } - - public void setFullShadow(boolean fullShadow) { - this.fullShadow = fullShadow; - } - - public ShadowKindType getKind() { - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr != null) { - return discr.getKind(); - } - if (getObjectOld()!=null) { - return getObjectOld().asObjectable().getKind(); - } - if (getObjectCurrent()!=null) { - return getObjectCurrent().asObjectable().getKind(); - } - if (getObjectNew()!=null) { - return getObjectNew().asObjectable().getKind(); - } - return ShadowKindType.ACCOUNT; - } - - public PrismValueDeltaSetTriple> getConstructionDeltaSetTriple() { - return constructionDeltaSetTriple; - } - - public void setConstructionDeltaSetTriple( - PrismValueDeltaSetTriple> constructionDeltaSetTriple) { - this.constructionDeltaSetTriple = constructionDeltaSetTriple; - } - - public Construction getOutboundConstruction() { - return outboundConstruction; - } - - public void setOutboundConstruction(Construction outboundConstruction) { - this.outboundConstruction = outboundConstruction; - } - - public Map,PrismPropertyDefinition>>> getSqueezedAttributes() { - return squeezedAttributes; - } - - public void setSqueezedAttributes(Map,PrismPropertyDefinition>>> squeezedAttributes) { - this.squeezedAttributes = squeezedAttributes; - } - - public Map,PrismContainerDefinition>>> getSqueezedAssociations() { - return squeezedAssociations; - } - - public void setSqueezedAssociations( - Map,PrismContainerDefinition>>> squeezedAssociations) { - this.squeezedAssociations = squeezedAssociations; - } - - public Map, PrismPropertyDefinition>>> getSqueezedAuxiliaryObjectClasses() { - return squeezedAuxiliaryObjectClasses; - } - - public void setSqueezedAuxiliaryObjectClasses( - Map, PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses) { - this.squeezedAuxiliaryObjectClasses = squeezedAuxiliaryObjectClasses; - } - - public ResourceObjectTypeDefinitionType getResourceObjectTypeDefinitionType() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { - return null; - } - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr == null) { - return null; // maybe when an account is deleted - } - if (resource == null) { - return null; - } - ResourceObjectTypeDefinitionType def = ResourceTypeUtil.getResourceObjectTypeDefinitionType(resource, discr.getKind(), discr.getIntent()); - return def; - } - - private ResourceSchema getResourceSchema() throws SchemaException { - return RefinedResourceSchemaImpl.getResourceSchema(resource, getNotNullPrismContext()); - } - - public RefinedResourceSchema getRefinedResourceSchema() throws SchemaException { - if (resource == null) { - return null; - } - return RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, getNotNullPrismContext()); - } - - public RefinedObjectClassDefinition getStructuralObjectClassDefinition() throws SchemaException { - if (structuralObjectClassDefinition == null) { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return null; - } - structuralObjectClassDefinition = refinedSchema.getRefinedDefinition(getResourceShadowDiscriminator().getKind(), getResourceShadowDiscriminator().getIntent()); - } - return structuralObjectClassDefinition; - } - - public Collection getAuxiliaryObjectClassDefinitions() throws SchemaException { - if (auxiliaryObjectClassDefinitions == null) { - refreshAuxiliaryObjectClassDefinitions(); - } - return auxiliaryObjectClassDefinitions; - } - - public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return; - } - List auxiliaryObjectClassQNames = new ArrayList<>(); - addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectOld()); - addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectNew()); - auxiliaryObjectClassDefinitions = new ArrayList<>(auxiliaryObjectClassQNames.size()); - for (QName auxiliaryObjectClassQName: auxiliaryObjectClassQNames) { - RefinedObjectClassDefinition auxiliaryObjectClassDef = refinedSchema.getRefinedDefinition(auxiliaryObjectClassQName); - if (auxiliaryObjectClassDef == null) { - throw new SchemaException("Auxiliary object class "+auxiliaryObjectClassQName+" specified in "+this+" does not exist"); - } - auxiliaryObjectClassDefinitions.add(auxiliaryObjectClassDef); - } - compositeObjectClassDefinition = null; - } - - public CompositeRefinedObjectClassDefinition getCompositeObjectClassDefinition() throws SchemaException { - if (compositeObjectClassDefinition == null) { - RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); - if (structuralObjectClassDefinition != null) { - compositeObjectClassDefinition = new CompositeRefinedObjectClassDefinitionImpl( - structuralObjectClassDefinition, getAuxiliaryObjectClassDefinitions()); - } - } - return compositeObjectClassDefinition; - } - - private void addAuxiliaryObjectClassNames(List auxiliaryObjectClassQNames, - PrismObject shadow) { - if (shadow == null) { - return; - } - for (QName aux: shadow.asObjectable().getAuxiliaryObjectClass()) { - if (!auxiliaryObjectClassQNames.contains(aux)) { - auxiliaryObjectClassQNames.add(aux); - } - } - } - - public RefinedAttributeDefinition findAttributeDefinition(QName attrName) throws SchemaException { - RefinedAttributeDefinition attrDef = getStructuralObjectClassDefinition().findAttributeDefinition(attrName); - if (attrDef != null) { - return attrDef; - } - for (RefinedObjectClassDefinition auxOcDef: getAuxiliaryObjectClassDefinitions()) { - attrDef = auxOcDef.findAttributeDefinition(attrName); - if (attrDef != null) { - return attrDef; - } - } - return null; - } - - public Collection getDependencies() { - if (dependencies == null) { - ResourceObjectTypeDefinitionType resourceAccountTypeDefinitionType = getResourceObjectTypeDefinitionType(); - if (resourceAccountTypeDefinitionType == null) { - // No dependencies. But we cannot set null as that means "unknown". So let's set empty collection instead. - dependencies = new ArrayList<>(); - } else { - dependencies = resourceAccountTypeDefinitionType.getDependency(); - } - } - return dependencies; - } - - public SecurityPolicyType getProjectionSecurityPolicy() { - return projectionSecurityPolicy; - } - - public void setProjectionSecurityPolicy(SecurityPolicyType projectionSecurityPolicy) { - this.projectionSecurityPolicy = projectionSecurityPolicy; - } - - public void setCanProject(boolean canProject) { - this.canProject = canProject; - } - - public boolean isCanProject() { - return canProject; - } - - public AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType() throws SchemaException { - // TODO: per-resource assignment enforcement - ResourceType resource = getResource(); - ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); - - if (objectClassProjectionPolicy != null && objectClassProjectionPolicy.getAssignmentPolicyEnforcement() != null) { - return MiscSchemaUtil.getAssignmentPolicyEnforcementType(objectClassProjectionPolicy); - } - - ProjectionPolicyType globalAccountSynchronizationSettings = null; - if (resource != null){ - globalAccountSynchronizationSettings = resource.getProjection(); - } - - if (globalAccountSynchronizationSettings == null) { - globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); - } - AssignmentPolicyEnforcementType globalAssignmentPolicyEnforcement = MiscSchemaUtil.getAssignmentPolicyEnforcementType(globalAccountSynchronizationSettings); - return globalAssignmentPolicyEnforcement; - } - - public boolean isLegalize() throws SchemaException { - ResourceType resource = getResource(); - - ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); - if (objectClassProjectionPolicy != null) { - return BooleanUtils.isTrue(objectClassProjectionPolicy.isLegalize()); - } - ProjectionPolicyType globalAccountSynchronizationSettings = null; - if (resource != null){ - globalAccountSynchronizationSettings = resource.getProjection(); - } - - if (globalAccountSynchronizationSettings == null) { - globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); - } - - if (globalAccountSynchronizationSettings == null){ - return false; - } - - return BooleanUtils.isTrue(globalAccountSynchronizationSettings.isLegalize()); - } - - private ProjectionPolicyType determineObjectClassProjectionPolicy() throws SchemaException { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return null; - } - - RefinedObjectClassDefinition objectClassDef = refinedSchema.getRefinedDefinition(resourceShadowDiscriminator.getKind(), - resourceShadowDiscriminator.getIntent()); - - if (objectClassDef == null) { - return null; - } - return objectClassDef.getProjection(); - } - - /** - * Recomputes the new state of account (accountNew). It is computed by applying deltas to the old state (accountOld). - * Assuming that oldAccount is already set (or is null if it does not exist) - */ - public void recompute() throws SchemaException { - ObjectDelta accDelta = getDelta(); - - PrismObject base = getObjectCurrent(); - if (base == null) { - base = getObjectOld(); - } - ObjectDelta syncDelta = getSyncDelta(); - if (base == null && syncDelta != null - && ChangeType.ADD.equals(syncDelta.getChangeType())) { - PrismObject objectToAdd = syncDelta.getObjectToAdd(); - if (objectToAdd != null) { - PrismObjectDefinition objectDefinition = objectToAdd.getDefinition(); - // TODO: remove constructor, use some factory method instead - base = getNotNullPrismContext().itemFactory().createObject(objectToAdd.getElementName(), objectDefinition); - base = syncDelta.computeChangedObject(base); - } - } - - if (accDelta == null) { - // No change - setObjectNew(base); - return; - } - - if (base == null && accDelta.isModify()) { - RefinedObjectClassDefinition rOCD = getCompositeObjectClassDefinition(); - if (rOCD != null) { - base = rOCD.createBlankShadow(); - } - } - - setObjectNew(accDelta.computeChangedObject(base)); - } - - public void clearIntermediateResults() { - //constructionDeltaSetTriple = null; - outboundConstruction = null; - squeezedAttributes = null; - } - - /** - * Returns delta suitable for execution. The primary and secondary deltas may not make complete sense all by themselves. - * E.g. they may both be MODIFY deltas even in case that the account should be created. The deltas begin to make sense - * only if combined with sync decision. This method provides the deltas all combined and ready for execution. - */ - @Override - public ObjectDelta getExecutableDelta() throws SchemaException { - SynchronizationPolicyDecision policyDecision = getSynchronizationPolicyDecision(); - ObjectDelta origDelta = getFixedDelta(); - if (policyDecision == SynchronizationPolicyDecision.ADD) { - // let's try to retrieve original (non-fixed) delta. Maybe it's ADD delta so we spare fixing it. - origDelta = getDelta(); - if (origDelta == null || origDelta.isModify()) { - // We need to convert modify delta to ADD - ObjectDelta addDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), - ChangeType.ADD); - RefinedObjectClassDefinition rObjectClassDef = getCompositeObjectClassDefinition(); - - if (rObjectClassDef == null) { - throw new IllegalStateException("Definition for account type " + getResourceShadowDiscriminator() - + " not found in the context, but it should be there"); - } - PrismObject newAccount = rObjectClassDef.createBlankShadow(); - addDelta.setObjectToAdd(newAccount); - - if (origDelta != null) { - addDelta.merge(origDelta); - } - return addDelta; - } - } else if (policyDecision == SynchronizationPolicyDecision.KEEP) { - // Any delta is OK - } else if (policyDecision == SynchronizationPolicyDecision.DELETE) { - ObjectDelta deleteDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), - ChangeType.DELETE); - String oid = getOid(); - if (oid == null) { - throw new IllegalStateException( - "Internal error: account context OID is null during attempt to create delete secondary delta; context=" - +this); - } - deleteDelta.setOid(oid); - return deleteDelta; - } else { - // This is either UNLINK or null, both are in fact the same as KEEP - // Any delta is OK - } - if (origDelta != null && origDelta.isImmutable()) { - // E.g. locked primary delta. - // We need modifiable delta for execution, e.g. to set metadata, oid and so on. - return origDelta.clone(); - } else { - return origDelta; - } - } - - public void checkConsistence() { - checkConsistence(null, true, false); - } - - @Override - public void checkConsistence(String contextDesc) { - super.checkConsistence(contextDesc); - if (secondaryDelta != null) { - boolean requireOid = isRequireSecondaryDeltaOid(); - // Secondary delta may not have OID yet (as it may relate to ADD primary delta that doesn't have OID yet) - checkConsistence(secondaryDelta, requireOid, getElementDesc() + " secondary delta in " + this + (contextDesc == null ? "" : " in " + contextDesc)); - } - } - - public void checkConsistence(String contextDesc, boolean fresh, boolean force) { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { - // No not check these. they may be quite wild. - return; - } - super.checkConsistence(contextDesc); - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { - return; - } - if (fresh && !force && resourceShadowDiscriminator != null && !resourceShadowDiscriminator.isTombstone()) { - if (resource == null) { - throw new IllegalStateException("Null resource in "+this + (contextDesc == null ? "" : " in " +contextDesc)); - } - if (resourceShadowDiscriminator == null) { - throw new IllegalStateException("Null resource account type in "+this + (contextDesc == null ? "" : " in " +contextDesc)); - } - } - if (syncDelta != null) { - try { - syncDelta.checkConsistence(true, true, true, ConsistencyCheckScope.THOROUGH); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); - } - } - } - - @Override - protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { - super.checkConsistence(object, elementDesc, contextDesc); - ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(object); - if (attributesContainer != null) { - ResourceType resource = getResource(); - if (resource != null) { - String resourceNamespace = ResourceTypeUtil.getResourceNamespace(resource); - for(ResourceAttribute attribute: attributesContainer.getAttributes()) { - QName attrName = attribute.getElementName(); - if (SchemaConstants.NS_ICF_SCHEMA.equals(attrName.getNamespaceURI())) { - continue; - } - if (resourceNamespace.equals(attrName.getNamespaceURI())) { - continue; - } - String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); - throw new IllegalStateException("Invalid namespace for attribute "+attrName+" in "+desc); - } - } - } - } - - protected boolean isRequireSecondaryDeltaOid() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD || - synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN || - synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { - return false; - } - if (getResourceShadowDiscriminator() != null && getResourceShadowDiscriminator().getOrder() > 0) { - // These may not have the OID yet - return false; - } - return super.isRequireSecondaryDeltaOid(); - } - - @Override - public void cleanup() { - secondaryDelta = null; - resetSynchronizationPolicyDecision(); -// isLegal = null; -// isLegalOld = null; - isAssigned = false; - isAssignedOld = false; // ??? [med] - isActive = false; - } - - @Override - public void normalize() { - super.normalize(); - if (secondaryDelta != null) { - secondaryDelta.normalize(); - } - if (syncDelta != null) { - syncDelta.normalize(); - } - } - -// @Override -// public void reset() { -// super.reset(); -// wave = -1; -// fullShadow = false; -// isAssigned = false; -// isAssignedOld = false; -// isActive = false; -// resetSynchronizationPolicyDecision(); -// constructionDeltaSetTriple = null; -// outboundConstruction = null; -// dependencies = null; -// squeezedAttributes = null; -// accountPasswordPolicy = null; -// } - - protected void resetSynchronizationPolicyDecision() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) { - toBeArchived = true; - } else if (synchronizationPolicyDecision != null) { - toBeArchived = false; - } - synchronizationPolicyDecision = null; - } - - @Override - public void adopt(PrismContext prismContext) throws SchemaException { - super.adopt(prismContext); - if (syncDelta != null) { - prismContext.adopt(syncDelta); - } - if (secondaryDelta != null) { - prismContext.adopt(secondaryDelta); - } - } - - @Override - public LensProjectionContext clone(LensContext lensContext) { - LensProjectionContext clone = new LensProjectionContext(lensContext, resourceShadowDiscriminator); - copyValues(clone, lensContext); - return clone; - } - - protected void copyValues(LensProjectionContext clone, LensContext lensContext) { - super.copyValues(clone, lensContext); - // do NOT clone transient values such as accountConstructionDeltaSetTriple - // these are not meant to be cloned and they are also not directly cloneable - clone.dependencies = this.dependencies; - clone.doReconciliation = this.doReconciliation; - clone.fullShadow = this.fullShadow; - clone.isAssigned = this.isAssigned; - clone.isAssignedOld = this.isAssignedOld; - clone.outboundConstruction = this.outboundConstruction; - clone.synchronizationPolicyDecision = this.synchronizationPolicyDecision; - clone.resource = this.resource; - clone.resourceShadowDiscriminator = this.resourceShadowDiscriminator; - clone.squeezedAttributes = cloneSqueezedAttributes(); - if (this.syncDelta != null) { - clone.syncDelta = this.syncDelta.clone(); - } - clone.secondaryDelta = cloneDelta(this.secondaryDelta); - clone.wave = this.wave; - clone.synchronizationSource = this.synchronizationSource; - } - - private Map,PrismPropertyDefinition>>> cloneSqueezedAttributes() { - if (squeezedAttributes == null) { - return null; - } - Map,PrismPropertyDefinition>>> clonedMap = new HashMap<>(); - for (Entry,PrismPropertyDefinition>>> entry: squeezedAttributes.entrySet()) { - clonedMap.put(entry.getKey(), entry.getValue().clone(ItemValueWithOrigin::clone)); - } - return clonedMap; - } - - /** - * Returns true if the projection has any value for specified attribute. - */ - public boolean hasValueForAttribute(QName attributeName) { - ItemPath attrPath = ItemPath.create(ShadowType.F_ATTRIBUTES, attributeName); - if (getObjectNew() != null) { - PrismProperty attrNew = getObjectNew().findProperty(attrPath); - if (attrNew != null && !attrNew.isEmpty()) { - return true; - } - } - return false; - } - - private boolean hasValueForAttribute(QName attributeName, Collection> acPpvSet) { - if (acPpvSet == null) { - return false; - } - for (PrismPropertyValue acPpv: acPpvSet) { - Construction ac = acPpv.getValue(); - if (ac.hasValueForAttribute(attributeName)) { - return true; - } - } - return false; - } - - @Override - public void checkEncrypted() { - super.checkEncrypted(); - if (syncDelta != null) { - CryptoUtil.checkEncrypted(syncDelta); - } - if (secondaryDelta != null) { - CryptoUtil.checkEncrypted(secondaryDelta); - } - } - - @Override - public String getHumanReadableName() { - if (humanReadableName == null) { - StringBuilder sb = new StringBuilder(); - sb.append("account("); - String humanReadableAccountIdentifier = getHumanReadableIdentifier(); - if (StringUtils.isEmpty(humanReadableAccountIdentifier)) { - sb.append("no ID"); - } else { - sb.append("ID "); - sb.append(humanReadableAccountIdentifier); - } - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr != null) { - sb.append(", type '"); - sb.append(discr.getIntent()); - sb.append("', "); - if (discr.getOrder() != 0) { - sb.append("order ").append(discr.getOrder()).append(", "); - } - } else { - sb.append(" (no discriminator) "); - } - sb.append(getResource()); - sb.append(")"); - humanReadableName = sb.toString(); - } - return humanReadableName; - } - - private String getHumanReadableIdentifier() { - PrismObject object = getObjectNew(); - if (object == null) { - object = getObjectOld(); - } - if (object == null) { - object = getObjectCurrent(); - } - if (object == null) { - return null; - } - if (object.canRepresent(ShadowType.class)) { - PrismObject shadow = (PrismObject)object; - Collection> identifiers = ShadowUtil.getPrimaryIdentifiers(shadow); - if (identifiers == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - Iterator> iterator = identifiers.iterator(); - while (iterator.hasNext()) { - ResourceAttribute id = iterator.next(); - sb.append(id.toHumanReadableString()); - if (iterator.hasNext()) { - sb.append(","); - } - } - return sb.toString(); - } else { - return object.toString(); - } - } - - @Override - public String debugDump(int indent) { - return debugDump(indent, true); - } - - public String debugDump(int indent, boolean showTriples) { - StringBuilder sb = new StringBuilder(); - SchemaDebugUtil.indentDebugDump(sb, indent); - sb.append("PROJECTION "); - sb.append(getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()); - sb.append(" "); - sb.append(getResourceShadowDiscriminator()); - if (resource != null) { - sb.append(" : "); - sb.append(resource.getName().getOrig()); - } - sb.append("\n"); - SchemaDebugUtil.indentDebugDump(sb, indent + 1); - sb.append("OID: ").append(getOid()); - sb.append(", wave ").append(wave); - if (fullShadow) { - sb.append(", full"); - } else { - sb.append(", shadow"); - } - sb.append(", exists=").append(isExists); - if (!shadowExistsInRepo) { - sb.append(" (shadow not in repo)"); - } - sb.append(", assigned=").append(isAssignedOld).append("->").append(isAssigned); - sb.append(", active=").append(isActive); - sb.append(", legal=").append(isLegalOld).append("->").append(isLegal); - sb.append(", recon=").append(doReconciliation); - sb.append(", canProject=").append(canProject); - sb.append(", syncIntent=").append(getSynchronizationIntent()); - sb.append(", decision=").append(synchronizationPolicyDecision); - if (!isFresh()) { - sb.append(", NOT FRESH"); - } - if (resourceShadowDiscriminator != null && resourceShadowDiscriminator.isTombstone()) { - sb.append(", TOMBSTONE"); - } - if (syncAbsoluteTrigger) { - sb.append(", SYNC TRIGGER"); - } - if (getIteration() != 0) { - sb.append(", iteration=").append(getIteration()).append(" (").append(getIterationToken()).append(")"); - } - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("old"), getObjectOld(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("current"), getObjectCurrent(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("new"), getObjectNew(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("primary delta"), getPrimaryDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("secondary delta"), getSecondaryDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("sync delta"), getSyncDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("executed deltas"), getExecutedDeltas(), indent+1); - - if (showTriples) { - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("constructionDeltaSetTriple"), constructionDeltaSetTriple, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("outbound account construction"), outboundConstruction, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed attributes"), squeezedAttributes, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed associations"), squeezedAssociations, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed auxiliary object classes"), squeezedAuxiliaryObjectClasses, indent + 1); - - // This is just a debug thing -// sb.append("\n"); -// DebugUtil.indentDebugDump(sb, indent); -// sb.append("ACCOUNT dependencies\n"); -// sb.append(DebugUtil.debugDump(dependencies, indent + 1)); - } - - return sb.toString(); - } - - @Override - protected String getElementDefaultDesc() { - return "projection"; - } - - @Override - public String toString() { - return "LensProjectionContext(" + (getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()) + ":" + getOid() + - ( resource == null ? "" : " on " + resource ) + ")"; - } - - /** - * Return a human readable name of the projection object suitable for logs. - */ - public String toHumanReadableString() { - if (humanReadableString == null) { - if (resourceShadowDiscriminator == null) { - humanReadableString = "(null" + resource + ")"; - } else if (resource != null) { - humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resource + ")"; - } else { - humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resourceShadowDiscriminator.getResourceOid() + ")"; - } - } - return humanReadableString; - } - - public String getHumanReadableKind() { - if (resourceShadowDiscriminator == null) { - return "resource object"; - } - return getKindValue(resourceShadowDiscriminator.getKind()); - } - - private String getKindValue(ShadowKindType kind) { - if (kind == null) { - return "null"; - } - return kind.value(); - } - - @Override - protected String getElementDesc() { - if (resourceShadowDiscriminator == null) { - return "shadow"; - } - return getKindValue(resourceShadowDiscriminator.getKind()); - } - - void addToPrismContainer(PrismContainer lensProjectionContextTypeContainer, LensContext.ExportType exportType) throws SchemaException { - LensProjectionContextType lensProjectionContextType = lensProjectionContextTypeContainer.createNewValue().asContainerable(); - super.storeIntoLensElementContextType(lensProjectionContextType, exportType); - lensProjectionContextType.setWave(wave); - lensProjectionContextType.setResourceShadowDiscriminator(resourceShadowDiscriminator != null ? - resourceShadowDiscriminator.toResourceShadowDiscriminatorType() : null); - lensProjectionContextType.setFullShadow(fullShadow); - lensProjectionContextType.setIsExists(isExists); - lensProjectionContextType.setSynchronizationPolicyDecision(synchronizationPolicyDecision != null ? synchronizationPolicyDecision.toSynchronizationPolicyDecisionType() : null); - lensProjectionContextType.setDoReconciliation(doReconciliation); - lensProjectionContextType.setSynchronizationSituationDetected(synchronizationSituationDetected); - lensProjectionContextType.setSynchronizationSituationResolved(synchronizationSituationResolved); - if (exportType != LensContext.ExportType.MINIMAL) { - lensProjectionContextType.setSyncDelta(syncDelta != null ? DeltaConvertor.toObjectDeltaType(syncDelta) : null); - lensProjectionContextType - .setSecondaryDelta(secondaryDelta != null ? DeltaConvertor.toObjectDeltaType(secondaryDelta) : null); - lensProjectionContextType.setIsAssigned(isAssigned); - lensProjectionContextType.setIsAssignedOld(isAssignedOld); - lensProjectionContextType.setIsActive(isActive); - lensProjectionContextType.setIsLegal(isLegal); - lensProjectionContextType.setIsLegalOld(isLegalOld); - if (exportType != LensContext.ExportType.REDUCED && projectionSecurityPolicy != null) { - ObjectReferenceType secRef = new ObjectReferenceType(); - secRef.asReferenceValue().setObject(projectionSecurityPolicy.asPrismObject()); - lensProjectionContextType.setProjectionSecurityPolicyRef(secRef); - } - lensProjectionContextType.setSyncAbsoluteTrigger(syncAbsoluteTrigger); - } - } - - public static LensProjectionContext fromLensProjectionContextType(LensProjectionContextType projectionContextType, LensContext lensContext, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { - - String objectTypeClassString = projectionContextType.getObjectTypeClass(); - if (StringUtils.isEmpty(objectTypeClassString)) { - throw new SystemException("Object type class is undefined in LensProjectionContextType"); - } - ResourceShadowDiscriminator resourceShadowDiscriminator = ResourceShadowDiscriminator.fromResourceShadowDiscriminatorType( - projectionContextType.getResourceShadowDiscriminator(), false); - - LensProjectionContext projectionContext = new LensProjectionContext(lensContext, resourceShadowDiscriminator); - - projectionContext.retrieveFromLensElementContextType(projectionContextType, task, result); - if (projectionContextType.getSyncDelta() != null) { - projectionContext.syncDelta = DeltaConvertor.createObjectDelta(projectionContextType.getSyncDelta(), lensContext.getPrismContext()); - } else { - projectionContext.syncDelta = null; - } - ObjectDeltaType secondaryDeltaType = projectionContextType.getSecondaryDelta(); - projectionContext.secondaryDelta = secondaryDeltaType != null ? - DeltaConvertor.createObjectDelta(secondaryDeltaType, lensContext.getPrismContext()) : null; - ObjectType object = projectionContextType.getObjectNew() != null ? projectionContextType.getObjectNew() : projectionContextType.getObjectOld(); - projectionContext.fixProvisioningTypeInDelta(projectionContext.secondaryDelta, object, task, result); - - projectionContext.wave = projectionContextType.getWave() != null ? projectionContextType.getWave() : 0; - projectionContext.fullShadow = projectionContextType.isFullShadow() != null ? projectionContextType.isFullShadow() : false; - projectionContext.isAssigned = projectionContextType.isIsAssigned() != null ? projectionContextType.isIsAssigned() : false; - projectionContext.isAssignedOld = projectionContextType.isIsAssignedOld() != null ? projectionContextType.isIsAssignedOld() : false; - projectionContext.isActive = projectionContextType.isIsActive() != null ? projectionContextType.isIsActive() : false; - projectionContext.isLegal = projectionContextType.isIsLegal(); - projectionContext.isLegalOld = projectionContextType.isIsLegalOld(); - projectionContext.isExists = projectionContextType.isIsExists() != null ? projectionContextType.isIsExists() : false; - projectionContext.synchronizationPolicyDecision = SynchronizationPolicyDecision.fromSynchronizationPolicyDecisionType(projectionContextType.getSynchronizationPolicyDecision()); - projectionContext.doReconciliation = projectionContextType.isDoReconciliation() != null ? projectionContextType.isDoReconciliation() : false; - projectionContext.synchronizationSituationDetected = projectionContextType.getSynchronizationSituationDetected(); - projectionContext.synchronizationSituationResolved = projectionContextType.getSynchronizationSituationResolved(); - ObjectReferenceType projectionSecurityPolicyRef = projectionContextType.getProjectionSecurityPolicyRef(); - if (projectionSecurityPolicyRef != null) { - projectionContext.projectionSecurityPolicy = (SecurityPolicyType) projectionSecurityPolicyRef.getObjectable(); - } - projectionContext.syncAbsoluteTrigger = projectionContextType.isSyncAbsoluteTrigger(); - - return projectionContext; - } - - // determines whether full shadow is present, based on operation result got from provisioning - public void determineFullShadowFlag(PrismObject loadedShadow) { - ShadowType shadowType = loadedShadow.asObjectable(); - if (ShadowUtil.isDead(shadowType) || !ShadowUtil.isExists(shadowType)) { - setFullShadow(false); - return; - } - OperationResultType fetchResult = shadowType.getFetchResult(); - if (fetchResult != null - && (fetchResult.getStatus() == OperationResultStatusType.PARTIAL_ERROR - || fetchResult.getStatus() == OperationResultStatusType.FATAL_ERROR)) { // todo what about other kinds of status? [e.g. in-progress] - setFullShadow(false); - } else { - setFullShadow(true); - } - } - - public boolean isToBeArchived() { - return toBeArchived; - } - - public void setToBeArchived(boolean toBeArchived) { - this.toBeArchived = toBeArchived; - } - - public String getResourceOid() { - if (resource != null) { - return resource.getOid(); - } else if (resourceShadowDiscriminator != null) { - return resourceShadowDiscriminator.getResourceOid(); - } else { - return null; - } - } - - public ResourceObjectVolatilityType getVolatility() throws SchemaException { - RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); - if (structuralObjectClassDefinition == null) { - return null; - } - return structuralObjectClassDefinition.getVolatility(); - } - - public boolean hasPendingOperations() { - PrismObject current = getObjectCurrent(); - if (current == null) { - return false; - } - return !current.asObjectable().getPendingOperation().isEmpty(); - } - - @Override - public void forEachDelta(Consumer> consumer) { - super.forEachDelta(consumer); - if (secondaryDelta != null) { - consumer.accept(secondaryDelta); - } - } - - PolyString resolveNameIfKnown(Class objectClass, String oid) { - if (ResourceType.class.equals(objectClass)) { - if (resource != null && oid.equals(resource.getOid())) { - return PolyString.toPolyString(resource.getName()); - } - } else if (ShadowType.class.equals(objectClass)) { - PrismObject object = getObjectAny(); - if (object != null && oid.equals(object.getOid())) { - if (object.getName() != null) { - return object.getName(); - } else { - try { - return ShadowUtil.determineShadowName(object); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine shadow name for {}", e, object); - return null; - } - } - } - } - return null; - } - - public String getResourceName() { - ResourceType resource = getResource(); - return resource != null ? PolyString.getOrig(resource.getName()) : getResourceOid(); - } - - public boolean isSynchronizationSource() { - return synchronizationSource; - } - - public void setSynchronizationSource(boolean synchronizationSource) { - this.synchronizationSource = synchronizationSource; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Consumer; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.refinery.*; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.jvnet.jaxb2_commons.lang.Validate; + +import com.evolveum.midpoint.common.crypto.CryptoUtil; +import com.evolveum.midpoint.model.api.context.ModelProjectionContext; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.prism.delta.ChangeType; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.processor.ResourceAttribute; +import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; +import com.evolveum.midpoint.schema.processor.ResourceSchema; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.schema.util.SchemaDebugUtil; +import com.evolveum.midpoint.util.DebugUtil; + +/** + * @author semancik + * + */ +public class LensProjectionContext extends LensElementContext implements ModelProjectionContext { + + private static final Trace LOGGER = TraceManager.getTrace(LensProjectionContext.class); + + private ObjectDelta syncDelta; + + /** + * Is this projection the source of the synchronization? (The syncDelta attribute could be used for this but in + * reality it is not always present.) We need this information e.g. when it's not possible to record a clockwork + * exception to focus (e.g. as in MID-5801). The alternate way is to record it into shadow representing the synchronization + * source, e.g. the object being imported, reconciled, or live-synced. + */ + private boolean synchronizationSource; + + private ObjectDelta secondaryDelta; + + /** + * If set to true: absolute state of this projection was detected by the synchronization. + * This is mostly for debugging and visibility. It is not used by projection logic. + */ + private boolean syncAbsoluteTrigger = false; + + /** + * The wave in which this resource should be processed. Initial value of -1 means "undetermined". + */ + private int wave = -1; + + /** + * Indicates that the wave computation is still in progress. + */ + private transient boolean waveIncomplete = false; + + /** + * Definition of account type. + */ + private ResourceShadowDiscriminator resourceShadowDiscriminator; + + private boolean fullShadow = false; + + /** + * True if the account is assigned to the user by a valid assignment. It may be false for accounts that are either + * found to be illegal by live sync, were unassigned from user, etc. + * If set to null the situation is not yet known. Null is a typical value when the context is constructed. + */ + private boolean isAssigned; + private boolean isAssignedOld; + + /** + * True if the account should be part of the synchronization. E.g. outbound expression should be applied to it. + */ + private boolean isActive; + + /** + * True if there is a valid assignment for this projection and/or the policy allows such projection to exist. + */ + private Boolean isLegal = null; + private Boolean isLegalOld = null; + + /** + * True if the projection exists (or will exist) on resource. False if it does not exist. + * NOTE: entire projection is loaded with pointInTime=future. Therefore this does NOT + * reflect actual situation. If there is a pending operation to create the object then + * isExists will in fact be true. + */ + private boolean isExists; + + /** + * True if shadow exists in the repo. It is set to false after projector discovers that a shadow is gone. + * This is a corner case, but it may happen: if shadow is unintentionally deleted, if the shadow is + * cleaned up by another thread and so on. + */ + private transient boolean shadowExistsInRepo = true; + + /** + * Decision regarding the account. It indicated what the engine has DECIDED TO DO with the context. + * If set to null no decision was made yet. Null is also a typical value when the context is created. + */ + private SynchronizationPolicyDecision synchronizationPolicyDecision; + + /** + * True if we want to reconcile account in this context. + */ + private boolean doReconciliation; + + /** + * false if the context should be not taken into the account while synchronizing changes from other resource + */ + private boolean canProject = true; + + /** + * Synchronization situation as it was originally detected by the synchronization code (SynchronizationService). + * This is mostly for debug purposes. Projector and clockwork do not need to care about this. + * The synchronization intent is used instead. + */ + private SynchronizationSituationType synchronizationSituationDetected = null; + /** + * Synchronization situation which was the result of synchronization reaction (projector and clockwork run). + * This is mostly for debug purposes. Projector and clockwork do not care about this (except for setting it). + * The synchronization decision is used instead. + */ + private SynchronizationSituationType synchronizationSituationResolved = null; + + /** + * Delta set triple for accounts. Specifies which accounts should be added, removed or stay as they are. + * It tells almost nothing about attributes directly although the information about attributes are inside + * each account construction (in a form of ValueConstruction that contains attribute delta triples). + * + * Intermediary computation result. It is stored to allow re-computing of account constructions during + * iterative computations. + * + * Source: AssignmentProcessor + * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) + */ + private transient PrismValueDeltaSetTriple> constructionDeltaSetTriple; + + /** + * Triples for outbound mappings; similar to the above. + * Source: OutboundProcessor + * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) + */ + private transient Construction outboundConstruction; + + /** + * Postprocessed triples from the above two properties. + * Source: ConsolidationProcessor + * Target: ReconciliationProcessor + */ + private transient Map,PrismPropertyDefinition>>> squeezedAttributes; + private transient Map,PrismContainerDefinition>>> squeezedAssociations; + private transient Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses; + + private transient Collection dependencies = null; + + // Cached copy, to avoid constructing it over and over again + private transient PrismObjectDefinition shadowDefinition = null; + + private transient RefinedObjectClassDefinition structuralObjectClassDefinition; + private transient Collection auxiliaryObjectClassDefinitions; + private transient CompositeRefinedObjectClassDefinition compositeObjectClassDefinition; + + private SecurityPolicyType projectionSecurityPolicy; + + /** + * Resource that hosts this projection. + */ + transient private ResourceType resource; + + /** + * EXPERIMENTAL. A flag that this projection context has to be put into 'history archive'. + * Necessary to evaluate old state of hasLinkedAccount. + * + * TODO implement as non-transient. + */ + transient private boolean toBeArchived; + + transient private String humanReadableName; + + private Map> entitlementMap = new HashMap<>(); + + transient private String humanReadableString; + + LensProjectionContext(LensContext lensContext, ResourceShadowDiscriminator resourceAccountType) { + super(ShadowType.class, lensContext); + this.resourceShadowDiscriminator = resourceAccountType; + this.isAssigned = false; + this.isAssignedOld = false; + } + + public ObjectDelta getSyncDelta() { + return syncDelta; + } + + public void setSyncDelta(ObjectDelta syncDelta) { + this.syncDelta = syncDelta; + } + + @Override + public ObjectDelta getSecondaryDelta() { + return secondaryDelta; + } + + @Override + public Collection> getAllDeltas() { + List> deltas = new ArrayList<>(2); + ObjectDelta primaryDelta = getPrimaryDelta(); + if (primaryDelta != null) { + deltas.add(primaryDelta); + } + if (secondaryDelta != null) { + deltas.add(secondaryDelta); + } + return deltas; + } + + @Override + public ObjectDeltaObject getObjectDeltaObject() throws SchemaException { + return new ObjectDeltaObject<>(getObjectCurrent(), getDelta(), getObjectNew(), getObjectDefinition()); + } + + public boolean hasSecondaryDelta() { + return secondaryDelta != null && !secondaryDelta.isEmpty(); + } + + @Override + public void setSecondaryDelta(ObjectDelta secondaryDelta) { + this.secondaryDelta = secondaryDelta; + } + + public void addSecondaryDelta(ObjectDelta delta) throws SchemaException { + if (secondaryDelta == null) { + secondaryDelta = delta; + } else { + secondaryDelta.merge(delta); + } + } + + @Override + public void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException { + if (secondaryDelta == null) { + secondaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); + secondaryDelta.setOid(getOid()); + } + LensUtil.setDeltaOldValue(this, itemDelta); + secondaryDelta.swallow(itemDelta); + } + + @Override + public void deleteSecondaryDeltas() { + secondaryDelta = null; + } + + @Override + public void setOid(String oid) { + super.setOid(oid); + if (secondaryDelta != null) { + secondaryDelta.setOid(oid); + } + } + + public boolean isSyncAbsoluteTrigger() { + return syncAbsoluteTrigger; + } + + public void setSyncAbsoluteTrigger(boolean syncAbsoluteTrigger) { + this.syncAbsoluteTrigger = syncAbsoluteTrigger; + } + + public int getWave() { + return wave; + } + + public void setWave(int wave) { + this.wave = wave; + } + + public boolean isWaveIncomplete() { + return waveIncomplete; + } + + public void setWaveIncomplete(boolean waveIncomplete) { + this.waveIncomplete = waveIncomplete; + } + + public boolean isDoReconciliation() { + return doReconciliation; + } + + public void setDoReconciliation(boolean doReconciliation) { + this.doReconciliation = doReconciliation; + } + + @Override + public ResourceShadowDiscriminator getResourceShadowDiscriminator() { + return resourceShadowDiscriminator; + } + + public void markTombstone() { + if (resourceShadowDiscriminator != null) { + resourceShadowDiscriminator.setTombstone(true); + } + setExists(false); + setFullShadow(false); + humanReadableName = null; + } + + public void setResourceShadowDiscriminator(ResourceShadowDiscriminator resourceShadowDiscriminator) { + this.resourceShadowDiscriminator = resourceShadowDiscriminator; + } + + public boolean compareResourceShadowDiscriminator(ResourceShadowDiscriminator rsd, boolean compareOrder) { + Validate.notNull(rsd.getResourceOid()); + if (resourceShadowDiscriminator == null) { + // This may be valid case e.g. in case of broken contexts or if a context is just loading + return false; + } + if (!rsd.getResourceOid().equals(resourceShadowDiscriminator.getResourceOid())) { + return false; + } + if (!rsd.getKind().equals(resourceShadowDiscriminator.getKind())) { + return false; + } + if (rsd.isTombstone() != resourceShadowDiscriminator.isTombstone()) { + return false; + } + if (rsd.getIntent() == null) { + try { + if (!getStructuralObjectClassDefinition().isDefaultInAKind()) { + return false; + } + } catch (SchemaException e) { + throw new SystemException("Internal error: "+e.getMessage(), e); + } + } else if (!rsd.getIntent().equals(resourceShadowDiscriminator.getIntent())) { + return false; + } + if (!Objects.equals(rsd.getTag(), resourceShadowDiscriminator.getTag())) { + return false; + } + + if (compareOrder && rsd.getOrder() != resourceShadowDiscriminator.getOrder()) { + return false; + } + + return true; + } + + public boolean isTombstone() { + if (resourceShadowDiscriminator == null) { + return false; + } + return resourceShadowDiscriminator.isTombstone(); + } + + public void addAccountSyncDelta(ObjectDelta delta) throws SchemaException { + if (syncDelta == null) { + syncDelta = delta; + } else { + syncDelta.merge(delta); + } + } + + public boolean isAdd() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return ObjectDelta.isAdd(getPrimaryDelta()) || ObjectDelta.isAdd(getSecondaryDelta()); + } + } + + public boolean isModify() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.KEEP) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return super.isModify(); + } + } + + public boolean isDelete() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return ObjectDelta.isDelete(syncDelta) || ObjectDelta.isDelete(getPrimaryDelta()) || ObjectDelta.isDelete(getSecondaryDelta()); + } + } + + @Override + public ArchetypeType getArchetype() { + throw new UnsupportedOperationException("Archetypes are not supported for projections."); + } + + public ResourceType getResource() { + return resource; + } + + public void setResource(ResourceType resource) { + this.resource = resource; + } + + public Map> getEntitlementMap() { + return entitlementMap; + } + + public void setEntitlementMap(Map> entitlementMap) { + this.entitlementMap = entitlementMap; + } + + @Override + public PrismObjectDefinition getObjectDefinition() { + if (shadowDefinition == null) { + try { + shadowDefinition = ShadowUtil.applyObjectClass(super.getObjectDefinition(), getCompositeObjectClassDefinition()); + } catch (SchemaException e) { + // This should not happen + throw new SystemException(e.getMessage(), e); + } + } + return shadowDefinition; + } + + public boolean isAssigned() { + return isAssigned; + } + + public void setAssigned(boolean isAssigned) { + this.isAssigned = isAssigned; + } + + public boolean isAssignedOld() { + return isAssignedOld; + } + + public void setAssignedOld(boolean isAssignedOld) { + this.isAssignedOld = isAssignedOld; + } + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean isActive) { + this.isActive = isActive; + } + + public Boolean isLegal() { + return isLegal; + } + + public void setLegal(Boolean isLegal) { + this.isLegal = isLegal; + } + + public Boolean isLegalOld() { + return isLegalOld; + } + + public void setLegalOld(Boolean isLegalOld) { + this.isLegalOld = isLegalOld; + } + + public boolean isExists() { + return isExists; + } + + public void setExists(boolean exists) { + this.isExists = exists; + } + + public boolean isShadowExistsInRepo() { + return shadowExistsInRepo; + } + + public void setShadowExistsInRepo(boolean shadowExistsInRepo) { + this.shadowExistsInRepo = shadowExistsInRepo; + } + + public SynchronizationPolicyDecision getSynchronizationPolicyDecision() { + return synchronizationPolicyDecision; + } + + public void setSynchronizationPolicyDecision(SynchronizationPolicyDecision policyDecision) { + this.synchronizationPolicyDecision = policyDecision; + } + + public SynchronizationSituationType getSynchronizationSituationDetected() { + return synchronizationSituationDetected; + } + + public void setSynchronizationSituationDetected( + SynchronizationSituationType synchronizationSituationDetected) { + this.synchronizationSituationDetected = synchronizationSituationDetected; + } + + public SynchronizationSituationType getSynchronizationSituationResolved() { + return synchronizationSituationResolved; + } + + void setSynchronizationSituationResolved(SynchronizationSituationType synchronizationSituationResolved) { + this.synchronizationSituationResolved = synchronizationSituationResolved; + } + + public boolean isFullShadow() { + return fullShadow; + } + + /** + * Returns true if full shadow is available, either loaded or in a create delta. + */ + public boolean hasFullShadow() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { + return true; + } + return isFullShadow(); + } + + public void setFullShadow(boolean fullShadow) { + this.fullShadow = fullShadow; + } + + public ShadowKindType getKind() { + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr != null) { + return discr.getKind(); + } + if (getObjectOld()!=null) { + return getObjectOld().asObjectable().getKind(); + } + if (getObjectCurrent()!=null) { + return getObjectCurrent().asObjectable().getKind(); + } + if (getObjectNew()!=null) { + return getObjectNew().asObjectable().getKind(); + } + return ShadowKindType.ACCOUNT; + } + + public PrismValueDeltaSetTriple> getConstructionDeltaSetTriple() { + return constructionDeltaSetTriple; + } + + public void setConstructionDeltaSetTriple( + PrismValueDeltaSetTriple> constructionDeltaSetTriple) { + this.constructionDeltaSetTriple = constructionDeltaSetTriple; + } + + public Construction getOutboundConstruction() { + return outboundConstruction; + } + + public void setOutboundConstruction(Construction outboundConstruction) { + this.outboundConstruction = outboundConstruction; + } + + public Map,PrismPropertyDefinition>>> getSqueezedAttributes() { + return squeezedAttributes; + } + + public void setSqueezedAttributes(Map,PrismPropertyDefinition>>> squeezedAttributes) { + this.squeezedAttributes = squeezedAttributes; + } + + public Map,PrismContainerDefinition>>> getSqueezedAssociations() { + return squeezedAssociations; + } + + public void setSqueezedAssociations( + Map,PrismContainerDefinition>>> squeezedAssociations) { + this.squeezedAssociations = squeezedAssociations; + } + + public Map, PrismPropertyDefinition>>> getSqueezedAuxiliaryObjectClasses() { + return squeezedAuxiliaryObjectClasses; + } + + public void setSqueezedAuxiliaryObjectClasses( + Map, PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses) { + this.squeezedAuxiliaryObjectClasses = squeezedAuxiliaryObjectClasses; + } + + public ResourceObjectTypeDefinitionType getResourceObjectTypeDefinitionType() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { + return null; + } + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr == null) { + return null; // maybe when an account is deleted + } + if (resource == null) { + return null; + } + ResourceObjectTypeDefinitionType def = ResourceTypeUtil.getResourceObjectTypeDefinitionType(resource, discr.getKind(), discr.getIntent()); + return def; + } + + private ResourceSchema getResourceSchema() throws SchemaException { + return RefinedResourceSchemaImpl.getResourceSchema(resource, getNotNullPrismContext()); + } + + public RefinedResourceSchema getRefinedResourceSchema() throws SchemaException { + if (resource == null) { + return null; + } + return RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, getNotNullPrismContext()); + } + + public RefinedObjectClassDefinition getStructuralObjectClassDefinition() throws SchemaException { + if (structuralObjectClassDefinition == null) { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return null; + } + structuralObjectClassDefinition = refinedSchema.getRefinedDefinition(getResourceShadowDiscriminator().getKind(), getResourceShadowDiscriminator().getIntent()); + } + return structuralObjectClassDefinition; + } + + public Collection getAuxiliaryObjectClassDefinitions() throws SchemaException { + if (auxiliaryObjectClassDefinitions == null) { + refreshAuxiliaryObjectClassDefinitions(); + } + return auxiliaryObjectClassDefinitions; + } + + public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return; + } + List auxiliaryObjectClassQNames = new ArrayList<>(); + addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectOld()); + addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectNew()); + auxiliaryObjectClassDefinitions = new ArrayList<>(auxiliaryObjectClassQNames.size()); + for (QName auxiliaryObjectClassQName: auxiliaryObjectClassQNames) { + RefinedObjectClassDefinition auxiliaryObjectClassDef = refinedSchema.getRefinedDefinition(auxiliaryObjectClassQName); + if (auxiliaryObjectClassDef == null) { + throw new SchemaException("Auxiliary object class "+auxiliaryObjectClassQName+" specified in "+this+" does not exist"); + } + auxiliaryObjectClassDefinitions.add(auxiliaryObjectClassDef); + } + compositeObjectClassDefinition = null; + } + + public CompositeRefinedObjectClassDefinition getCompositeObjectClassDefinition() throws SchemaException { + if (compositeObjectClassDefinition == null) { + RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); + if (structuralObjectClassDefinition != null) { + compositeObjectClassDefinition = new CompositeRefinedObjectClassDefinitionImpl( + structuralObjectClassDefinition, getAuxiliaryObjectClassDefinitions()); + } + } + return compositeObjectClassDefinition; + } + + private void addAuxiliaryObjectClassNames(List auxiliaryObjectClassQNames, + PrismObject shadow) { + if (shadow == null) { + return; + } + for (QName aux: shadow.asObjectable().getAuxiliaryObjectClass()) { + if (!auxiliaryObjectClassQNames.contains(aux)) { + auxiliaryObjectClassQNames.add(aux); + } + } + } + + public RefinedAttributeDefinition findAttributeDefinition(QName attrName) throws SchemaException { + RefinedAttributeDefinition attrDef = getStructuralObjectClassDefinition().findAttributeDefinition(attrName); + if (attrDef != null) { + return attrDef; + } + for (RefinedObjectClassDefinition auxOcDef: getAuxiliaryObjectClassDefinitions()) { + attrDef = auxOcDef.findAttributeDefinition(attrName); + if (attrDef != null) { + return attrDef; + } + } + return null; + } + + public Collection getDependencies() { + if (dependencies == null) { + ResourceObjectTypeDefinitionType resourceAccountTypeDefinitionType = getResourceObjectTypeDefinitionType(); + if (resourceAccountTypeDefinitionType == null) { + // No dependencies. But we cannot set null as that means "unknown". So let's set empty collection instead. + dependencies = new ArrayList<>(); + } else { + dependencies = resourceAccountTypeDefinitionType.getDependency(); + } + } + return dependencies; + } + + public SecurityPolicyType getProjectionSecurityPolicy() { + return projectionSecurityPolicy; + } + + public void setProjectionSecurityPolicy(SecurityPolicyType projectionSecurityPolicy) { + this.projectionSecurityPolicy = projectionSecurityPolicy; + } + + public void setCanProject(boolean canProject) { + this.canProject = canProject; + } + + public boolean isCanProject() { + return canProject; + } + + public AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType() throws SchemaException { + // TODO: per-resource assignment enforcement + ResourceType resource = getResource(); + ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); + + if (objectClassProjectionPolicy != null && objectClassProjectionPolicy.getAssignmentPolicyEnforcement() != null) { + return MiscSchemaUtil.getAssignmentPolicyEnforcementType(objectClassProjectionPolicy); + } + + ProjectionPolicyType globalAccountSynchronizationSettings = null; + if (resource != null){ + globalAccountSynchronizationSettings = resource.getProjection(); + } + + if (globalAccountSynchronizationSettings == null) { + globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); + } + AssignmentPolicyEnforcementType globalAssignmentPolicyEnforcement = MiscSchemaUtil.getAssignmentPolicyEnforcementType(globalAccountSynchronizationSettings); + return globalAssignmentPolicyEnforcement; + } + + public boolean isLegalize() throws SchemaException { + ResourceType resource = getResource(); + + ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); + if (objectClassProjectionPolicy != null) { + return BooleanUtils.isTrue(objectClassProjectionPolicy.isLegalize()); + } + ProjectionPolicyType globalAccountSynchronizationSettings = null; + if (resource != null){ + globalAccountSynchronizationSettings = resource.getProjection(); + } + + if (globalAccountSynchronizationSettings == null) { + globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); + } + + if (globalAccountSynchronizationSettings == null){ + return false; + } + + return BooleanUtils.isTrue(globalAccountSynchronizationSettings.isLegalize()); + } + + private ProjectionPolicyType determineObjectClassProjectionPolicy() throws SchemaException { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return null; + } + + RefinedObjectClassDefinition objectClassDef = refinedSchema.getRefinedDefinition(resourceShadowDiscriminator.getKind(), + resourceShadowDiscriminator.getIntent()); + + if (objectClassDef == null) { + return null; + } + return objectClassDef.getProjection(); + } + + /** + * Recomputes the new state of account (accountNew). It is computed by applying deltas to the old state (accountOld). + * Assuming that oldAccount is already set (or is null if it does not exist) + */ + public void recompute() throws SchemaException { + ObjectDelta accDelta = getDelta(); + + PrismObject base = getObjectCurrent(); + if (base == null) { + base = getObjectOld(); + } + ObjectDelta syncDelta = getSyncDelta(); + if (base == null && syncDelta != null + && ChangeType.ADD.equals(syncDelta.getChangeType())) { + PrismObject objectToAdd = syncDelta.getObjectToAdd(); + if (objectToAdd != null) { + PrismObjectDefinition objectDefinition = objectToAdd.getDefinition(); + // TODO: remove constructor, use some factory method instead + base = getNotNullPrismContext().itemFactory().createObject(objectToAdd.getElementName(), objectDefinition); + base = syncDelta.computeChangedObject(base); + } + } + + if (accDelta == null) { + // No change + setObjectNew(base); + return; + } + + if (base == null && accDelta.isModify()) { + RefinedObjectClassDefinition rOCD = getCompositeObjectClassDefinition(); + if (rOCD != null) { + base = rOCD.createBlankShadow(); + } + } + + setObjectNew(accDelta.computeChangedObject(base)); + } + + public void clearIntermediateResults() { + //constructionDeltaSetTriple = null; + outboundConstruction = null; + squeezedAttributes = null; + } + + /** + * Returns delta suitable for execution. The primary and secondary deltas may not make complete sense all by themselves. + * E.g. they may both be MODIFY deltas even in case that the account should be created. The deltas begin to make sense + * only if combined with sync decision. This method provides the deltas all combined and ready for execution. + */ + @Override + public ObjectDelta getExecutableDelta() throws SchemaException { + SynchronizationPolicyDecision policyDecision = getSynchronizationPolicyDecision(); + ObjectDelta origDelta = getFixedDelta(); + if (policyDecision == SynchronizationPolicyDecision.ADD) { + // let's try to retrieve original (non-fixed) delta. Maybe it's ADD delta so we spare fixing it. + origDelta = getDelta(); + if (origDelta == null || origDelta.isModify()) { + // We need to convert modify delta to ADD + ObjectDelta addDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), + ChangeType.ADD); + RefinedObjectClassDefinition rObjectClassDef = getCompositeObjectClassDefinition(); + + if (rObjectClassDef == null) { + throw new IllegalStateException("Definition for account type " + getResourceShadowDiscriminator() + + " not found in the context, but it should be there"); + } + PrismObject newAccount = rObjectClassDef.createBlankShadow(); + addDelta.setObjectToAdd(newAccount); + + if (origDelta != null) { + addDelta.merge(origDelta); + } + return addDelta; + } + } else if (policyDecision == SynchronizationPolicyDecision.KEEP) { + // Any delta is OK + } else if (policyDecision == SynchronizationPolicyDecision.DELETE) { + ObjectDelta deleteDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), + ChangeType.DELETE); + String oid = getOid(); + if (oid == null) { + throw new IllegalStateException( + "Internal error: account context OID is null during attempt to create delete secondary delta; context=" + +this); + } + deleteDelta.setOid(oid); + return deleteDelta; + } else { + // This is either UNLINK or null, both are in fact the same as KEEP + // Any delta is OK + } + if (origDelta != null && origDelta.isImmutable()) { + // E.g. locked primary delta. + // We need modifiable delta for execution, e.g. to set metadata, oid and so on. + return origDelta.clone(); + } else { + return origDelta; + } + } + + public void checkConsistence() { + checkConsistence(null, true, false); + } + + @Override + public void checkConsistence(String contextDesc) { + super.checkConsistence(contextDesc); + if (secondaryDelta != null) { + boolean requireOid = isRequireSecondaryDeltaOid(); + // Secondary delta may not have OID yet (as it may relate to ADD primary delta that doesn't have OID yet) + checkConsistence(secondaryDelta, requireOid, getElementDesc() + " secondary delta in " + this + (contextDesc == null ? "" : " in " + contextDesc)); + } + } + + public void checkConsistence(String contextDesc, boolean fresh, boolean force) { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { + // No not check these. they may be quite wild. + return; + } + super.checkConsistence(contextDesc); + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { + return; + } + if (fresh && !force && resourceShadowDiscriminator != null && !resourceShadowDiscriminator.isTombstone()) { + if (resource == null) { + throw new IllegalStateException("Null resource in "+this + (contextDesc == null ? "" : " in " +contextDesc)); + } + if (resourceShadowDiscriminator == null) { + throw new IllegalStateException("Null resource account type in "+this + (contextDesc == null ? "" : " in " +contextDesc)); + } + } + if (syncDelta != null) { + try { + syncDelta.checkConsistence(true, true, true, ConsistencyCheckScope.THOROUGH); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); + } + } + } + + @Override + protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { + super.checkConsistence(object, elementDesc, contextDesc); + ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(object); + if (attributesContainer != null) { + ResourceType resource = getResource(); + if (resource != null) { + String resourceNamespace = ResourceTypeUtil.getResourceNamespace(resource); + for(ResourceAttribute attribute: attributesContainer.getAttributes()) { + QName attrName = attribute.getElementName(); + if (SchemaConstants.NS_ICF_SCHEMA.equals(attrName.getNamespaceURI())) { + continue; + } + if (resourceNamespace.equals(attrName.getNamespaceURI())) { + continue; + } + String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); + throw new IllegalStateException("Invalid namespace for attribute "+attrName+" in "+desc); + } + } + } + } + + protected boolean isRequireSecondaryDeltaOid() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD || + synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN || + synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { + return false; + } + if (getResourceShadowDiscriminator() != null && getResourceShadowDiscriminator().getOrder() > 0) { + // These may not have the OID yet + return false; + } + return super.isRequireSecondaryDeltaOid(); + } + + @Override + public void cleanup() { + secondaryDelta = null; + resetSynchronizationPolicyDecision(); +// isLegal = null; +// isLegalOld = null; + isAssigned = false; + isAssignedOld = false; // ??? [med] + isActive = false; + } + + @Override + public void normalize() { + super.normalize(); + if (secondaryDelta != null) { + secondaryDelta.normalize(); + } + if (syncDelta != null) { + syncDelta.normalize(); + } + } + +// @Override +// public void reset() { +// super.reset(); +// wave = -1; +// fullShadow = false; +// isAssigned = false; +// isAssignedOld = false; +// isActive = false; +// resetSynchronizationPolicyDecision(); +// constructionDeltaSetTriple = null; +// outboundConstruction = null; +// dependencies = null; +// squeezedAttributes = null; +// accountPasswordPolicy = null; +// } + + protected void resetSynchronizationPolicyDecision() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) { + toBeArchived = true; + } else if (synchronizationPolicyDecision != null) { + toBeArchived = false; + } + synchronizationPolicyDecision = null; + } + + @Override + public void adopt(PrismContext prismContext) throws SchemaException { + super.adopt(prismContext); + if (syncDelta != null) { + prismContext.adopt(syncDelta); + } + if (secondaryDelta != null) { + prismContext.adopt(secondaryDelta); + } + } + + @Override + public LensProjectionContext clone(LensContext lensContext) { + LensProjectionContext clone = new LensProjectionContext(lensContext, resourceShadowDiscriminator); + copyValues(clone, lensContext); + return clone; + } + + protected void copyValues(LensProjectionContext clone, LensContext lensContext) { + super.copyValues(clone, lensContext); + // do NOT clone transient values such as accountConstructionDeltaSetTriple + // these are not meant to be cloned and they are also not directly cloneable + clone.dependencies = this.dependencies; + clone.doReconciliation = this.doReconciliation; + clone.fullShadow = this.fullShadow; + clone.isAssigned = this.isAssigned; + clone.isAssignedOld = this.isAssignedOld; + clone.outboundConstruction = this.outboundConstruction; + clone.synchronizationPolicyDecision = this.synchronizationPolicyDecision; + clone.resource = this.resource; + clone.resourceShadowDiscriminator = this.resourceShadowDiscriminator; + clone.squeezedAttributes = cloneSqueezedAttributes(); + if (this.syncDelta != null) { + clone.syncDelta = this.syncDelta.clone(); + } + clone.secondaryDelta = cloneDelta(this.secondaryDelta); + clone.wave = this.wave; + clone.synchronizationSource = this.synchronizationSource; + } + + private Map,PrismPropertyDefinition>>> cloneSqueezedAttributes() { + if (squeezedAttributes == null) { + return null; + } + Map,PrismPropertyDefinition>>> clonedMap = new HashMap<>(); + for (Entry,PrismPropertyDefinition>>> entry: squeezedAttributes.entrySet()) { + clonedMap.put(entry.getKey(), entry.getValue().clone(ItemValueWithOrigin::clone)); + } + return clonedMap; + } + + /** + * Returns true if the projection has any value for specified attribute. + */ + public boolean hasValueForAttribute(QName attributeName) { + ItemPath attrPath = ItemPath.create(ShadowType.F_ATTRIBUTES, attributeName); + if (getObjectNew() != null) { + PrismProperty attrNew = getObjectNew().findProperty(attrPath); + if (attrNew != null && !attrNew.isEmpty()) { + return true; + } + } + return false; + } + + private boolean hasValueForAttribute(QName attributeName, Collection> acPpvSet) { + if (acPpvSet == null) { + return false; + } + for (PrismPropertyValue acPpv: acPpvSet) { + Construction ac = acPpv.getValue(); + if (ac.hasValueForAttribute(attributeName)) { + return true; + } + } + return false; + } + + @Override + public void checkEncrypted() { + super.checkEncrypted(); + if (syncDelta != null) { + CryptoUtil.checkEncrypted(syncDelta); + } + if (secondaryDelta != null) { + CryptoUtil.checkEncrypted(secondaryDelta); + } + } + + @Override + public String getHumanReadableName() { + if (humanReadableName == null) { + StringBuilder sb = new StringBuilder(); + sb.append("account("); + String humanReadableAccountIdentifier = getHumanReadableIdentifier(); + if (StringUtils.isEmpty(humanReadableAccountIdentifier)) { + sb.append("no ID"); + } else { + sb.append("ID "); + sb.append(humanReadableAccountIdentifier); + } + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr != null) { + sb.append(", type '"); + sb.append(discr.getIntent()); + sb.append("', "); + if (discr.getOrder() != 0) { + sb.append("order ").append(discr.getOrder()).append(", "); + } + } else { + sb.append(" (no discriminator) "); + } + sb.append(getResource()); + sb.append(")"); + humanReadableName = sb.toString(); + } + return humanReadableName; + } + + private String getHumanReadableIdentifier() { + PrismObject object = getObjectNew(); + if (object == null) { + object = getObjectOld(); + } + if (object == null) { + object = getObjectCurrent(); + } + if (object == null) { + return null; + } + if (object.canRepresent(ShadowType.class)) { + PrismObject shadow = (PrismObject)object; + Collection> identifiers = ShadowUtil.getPrimaryIdentifiers(shadow); + if (identifiers == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + Iterator> iterator = identifiers.iterator(); + while (iterator.hasNext()) { + ResourceAttribute id = iterator.next(); + sb.append(id.toHumanReadableString()); + if (iterator.hasNext()) { + sb.append(","); + } + } + return sb.toString(); + } else { + return object.toString(); + } + } + + @Override + public String debugDump(int indent) { + return debugDump(indent, true); + } + + public String debugDump(int indent, boolean showTriples) { + StringBuilder sb = new StringBuilder(); + SchemaDebugUtil.indentDebugDump(sb, indent); + sb.append("PROJECTION "); + sb.append(getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()); + sb.append(" "); + sb.append(getResourceShadowDiscriminator()); + if (resource != null) { + sb.append(" : "); + sb.append(resource.getName().getOrig()); + } + sb.append("\n"); + SchemaDebugUtil.indentDebugDump(sb, indent + 1); + sb.append("OID: ").append(getOid()); + sb.append(", wave ").append(wave); + if (fullShadow) { + sb.append(", full"); + } else { + sb.append(", shadow"); + } + sb.append(", exists=").append(isExists); + if (!shadowExistsInRepo) { + sb.append(" (shadow not in repo)"); + } + sb.append(", assigned=").append(isAssignedOld).append("->").append(isAssigned); + sb.append(", active=").append(isActive); + sb.append(", legal=").append(isLegalOld).append("->").append(isLegal); + sb.append(", recon=").append(doReconciliation); + sb.append(", canProject=").append(canProject); + sb.append(", syncIntent=").append(getSynchronizationIntent()); + sb.append(", decision=").append(synchronizationPolicyDecision); + if (!isFresh()) { + sb.append(", NOT FRESH"); + } + if (resourceShadowDiscriminator != null && resourceShadowDiscriminator.isTombstone()) { + sb.append(", TOMBSTONE"); + } + if (syncAbsoluteTrigger) { + sb.append(", SYNC TRIGGER"); + } + if (getIteration() != 0) { + sb.append(", iteration=").append(getIteration()).append(" (").append(getIterationToken()).append(")"); + } + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("old"), getObjectOld(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("current"), getObjectCurrent(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("new"), getObjectNew(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("primary delta"), getPrimaryDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("secondary delta"), getSecondaryDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("sync delta"), getSyncDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("executed deltas"), getExecutedDeltas(), indent+1); + + if (showTriples) { + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("constructionDeltaSetTriple"), constructionDeltaSetTriple, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("outbound account construction"), outboundConstruction, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed attributes"), squeezedAttributes, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed associations"), squeezedAssociations, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed auxiliary object classes"), squeezedAuxiliaryObjectClasses, indent + 1); + + // This is just a debug thing +// sb.append("\n"); +// DebugUtil.indentDebugDump(sb, indent); +// sb.append("ACCOUNT dependencies\n"); +// sb.append(DebugUtil.debugDump(dependencies, indent + 1)); + } + + return sb.toString(); + } + + @Override + protected String getElementDefaultDesc() { + return "projection"; + } + + @Override + public String toString() { + return "LensProjectionContext(" + (getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()) + ":" + getOid() + + ( resource == null ? "" : " on " + resource ) + ")"; + } + + /** + * Return a human readable name of the projection object suitable for logs. + */ + public String toHumanReadableString() { + if (humanReadableString == null) { + if (resourceShadowDiscriminator == null) { + humanReadableString = "(null" + resource + ")"; + } else if (resource != null) { + humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resource + ")"; + } else { + humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resourceShadowDiscriminator.getResourceOid() + ")"; + } + } + return humanReadableString; + } + + public String getHumanReadableKind() { + if (resourceShadowDiscriminator == null) { + return "resource object"; + } + return getKindValue(resourceShadowDiscriminator.getKind()); + } + + private String getKindValue(ShadowKindType kind) { + if (kind == null) { + return "null"; + } + return kind.value(); + } + + @Override + protected String getElementDesc() { + if (resourceShadowDiscriminator == null) { + return "shadow"; + } + return getKindValue(resourceShadowDiscriminator.getKind()); + } + + void addToPrismContainer(PrismContainer lensProjectionContextTypeContainer, LensContext.ExportType exportType) throws SchemaException { + LensProjectionContextType lensProjectionContextType = lensProjectionContextTypeContainer.createNewValue().asContainerable(); + super.storeIntoLensElementContextType(lensProjectionContextType, exportType); + lensProjectionContextType.setWave(wave); + lensProjectionContextType.setResourceShadowDiscriminator(resourceShadowDiscriminator != null ? + resourceShadowDiscriminator.toResourceShadowDiscriminatorType() : null); + lensProjectionContextType.setFullShadow(fullShadow); + lensProjectionContextType.setIsExists(isExists); + lensProjectionContextType.setSynchronizationPolicyDecision(synchronizationPolicyDecision != null ? synchronizationPolicyDecision.toSynchronizationPolicyDecisionType() : null); + lensProjectionContextType.setDoReconciliation(doReconciliation); + lensProjectionContextType.setSynchronizationSituationDetected(synchronizationSituationDetected); + lensProjectionContextType.setSynchronizationSituationResolved(synchronizationSituationResolved); + if (exportType != LensContext.ExportType.MINIMAL) { + lensProjectionContextType.setSyncDelta(syncDelta != null ? DeltaConvertor.toObjectDeltaType(syncDelta) : null); + lensProjectionContextType + .setSecondaryDelta(secondaryDelta != null ? DeltaConvertor.toObjectDeltaType(secondaryDelta) : null); + lensProjectionContextType.setIsAssigned(isAssigned); + lensProjectionContextType.setIsAssignedOld(isAssignedOld); + lensProjectionContextType.setIsActive(isActive); + lensProjectionContextType.setIsLegal(isLegal); + lensProjectionContextType.setIsLegalOld(isLegalOld); + if (exportType != LensContext.ExportType.REDUCED && projectionSecurityPolicy != null) { + ObjectReferenceType secRef = new ObjectReferenceType(); + secRef.asReferenceValue().setObject(projectionSecurityPolicy.asPrismObject()); + lensProjectionContextType.setProjectionSecurityPolicyRef(secRef); + } + lensProjectionContextType.setSyncAbsoluteTrigger(syncAbsoluteTrigger); + } + } + + public static LensProjectionContext fromLensProjectionContextType(LensProjectionContextType projectionContextType, LensContext lensContext, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { + + String objectTypeClassString = projectionContextType.getObjectTypeClass(); + if (StringUtils.isEmpty(objectTypeClassString)) { + throw new SystemException("Object type class is undefined in LensProjectionContextType"); + } + ResourceShadowDiscriminator resourceShadowDiscriminator = ResourceShadowDiscriminator.fromResourceShadowDiscriminatorType( + projectionContextType.getResourceShadowDiscriminator(), false); + + LensProjectionContext projectionContext = new LensProjectionContext(lensContext, resourceShadowDiscriminator); + + projectionContext.retrieveFromLensElementContextType(projectionContextType, task, result); + if (projectionContextType.getSyncDelta() != null) { + projectionContext.syncDelta = DeltaConvertor.createObjectDelta(projectionContextType.getSyncDelta(), lensContext.getPrismContext()); + } else { + projectionContext.syncDelta = null; + } + ObjectDeltaType secondaryDeltaType = projectionContextType.getSecondaryDelta(); + projectionContext.secondaryDelta = secondaryDeltaType != null ? + DeltaConvertor.createObjectDelta(secondaryDeltaType, lensContext.getPrismContext()) : null; + ObjectType object = projectionContextType.getObjectNew() != null ? projectionContextType.getObjectNew() : projectionContextType.getObjectOld(); + projectionContext.fixProvisioningTypeInDelta(projectionContext.secondaryDelta, object, task, result); + + projectionContext.wave = projectionContextType.getWave() != null ? projectionContextType.getWave() : 0; + projectionContext.fullShadow = projectionContextType.isFullShadow() != null ? projectionContextType.isFullShadow() : false; + projectionContext.isAssigned = projectionContextType.isIsAssigned() != null ? projectionContextType.isIsAssigned() : false; + projectionContext.isAssignedOld = projectionContextType.isIsAssignedOld() != null ? projectionContextType.isIsAssignedOld() : false; + projectionContext.isActive = projectionContextType.isIsActive() != null ? projectionContextType.isIsActive() : false; + projectionContext.isLegal = projectionContextType.isIsLegal(); + projectionContext.isLegalOld = projectionContextType.isIsLegalOld(); + projectionContext.isExists = projectionContextType.isIsExists() != null ? projectionContextType.isIsExists() : false; + projectionContext.synchronizationPolicyDecision = SynchronizationPolicyDecision.fromSynchronizationPolicyDecisionType(projectionContextType.getSynchronizationPolicyDecision()); + projectionContext.doReconciliation = projectionContextType.isDoReconciliation() != null ? projectionContextType.isDoReconciliation() : false; + projectionContext.synchronizationSituationDetected = projectionContextType.getSynchronizationSituationDetected(); + projectionContext.synchronizationSituationResolved = projectionContextType.getSynchronizationSituationResolved(); + ObjectReferenceType projectionSecurityPolicyRef = projectionContextType.getProjectionSecurityPolicyRef(); + if (projectionSecurityPolicyRef != null) { + projectionContext.projectionSecurityPolicy = (SecurityPolicyType) projectionSecurityPolicyRef.getObjectable(); + } + projectionContext.syncAbsoluteTrigger = projectionContextType.isSyncAbsoluteTrigger(); + + return projectionContext; + } + + // determines whether full shadow is present, based on operation result got from provisioning + public void determineFullShadowFlag(PrismObject loadedShadow) { + ShadowType shadowType = loadedShadow.asObjectable(); + if (ShadowUtil.isDead(shadowType) || !ShadowUtil.isExists(shadowType)) { + setFullShadow(false); + return; + } + OperationResultType fetchResult = shadowType.getFetchResult(); + if (fetchResult != null + && (fetchResult.getStatus() == OperationResultStatusType.PARTIAL_ERROR + || fetchResult.getStatus() == OperationResultStatusType.FATAL_ERROR)) { // todo what about other kinds of status? [e.g. in-progress] + setFullShadow(false); + } else { + setFullShadow(true); + } + } + + public boolean isToBeArchived() { + return toBeArchived; + } + + public void setToBeArchived(boolean toBeArchived) { + this.toBeArchived = toBeArchived; + } + + public String getResourceOid() { + if (resource != null) { + return resource.getOid(); + } else if (resourceShadowDiscriminator != null) { + return resourceShadowDiscriminator.getResourceOid(); + } else { + return null; + } + } + + public ResourceObjectVolatilityType getVolatility() throws SchemaException { + RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); + if (structuralObjectClassDefinition == null) { + return null; + } + return structuralObjectClassDefinition.getVolatility(); + } + + public boolean hasPendingOperations() { + PrismObject current = getObjectCurrent(); + if (current == null) { + return false; + } + return !current.asObjectable().getPendingOperation().isEmpty(); + } + + @Override + public void forEachDelta(Consumer> consumer) { + super.forEachDelta(consumer); + if (secondaryDelta != null) { + consumer.accept(secondaryDelta); + } + } + + PolyString resolveNameIfKnown(Class objectClass, String oid) { + if (ResourceType.class.equals(objectClass)) { + if (resource != null && oid.equals(resource.getOid())) { + return PolyString.toPolyString(resource.getName()); + } + } else if (ShadowType.class.equals(objectClass)) { + PrismObject object = getObjectAny(); + if (object != null && oid.equals(object.getOid())) { + if (object.getName() != null) { + return object.getName(); + } else { + try { + return ShadowUtil.determineShadowName(object); + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine shadow name for {}", e, object); + return null; + } + } + } + } + return null; + } + + public String getResourceName() { + ResourceType resource = getResource(); + return resource != null ? PolyString.getOrig(resource.getName()) : getResourceOid(); + } + + public boolean isSynchronizationSource() { + return synchronizationSource; + } + + public void setSynchronizationSource(boolean synchronizationSource) { + this.synchronizationSource = synchronizationSource; + } + + public String getDescription() { + if (resource != null) { + return resource + "("+ resourceShadowDiscriminator.getIntent()+")"; + } else { + if (resourceShadowDiscriminator != null) { + return resourceShadowDiscriminator.toString(); + } else { + return "(UNKNOWN)"; + } + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 02e25894415..d6c01110bba 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -1,1168 +1,1168 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens; - -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; -import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; -import com.evolveum.midpoint.prism.path.ItemName; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.SchemaConstantsGenerated; -import com.evolveum.midpoint.schema.util.*; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang.BooleanUtils; - -import com.evolveum.midpoint.common.ActivationComputer; -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.delta.PropertyDelta; -import com.evolveum.midpoint.provisioning.api.ProvisioningService; -import com.evolveum.midpoint.repo.common.ObjectResolver; -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.repo.common.expression.Source; -import com.evolveum.midpoint.schema.CapabilityUtil; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.ResultHandler; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.VirtualAssignmenetSpecification; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; -import org.jetbrains.annotations.NotNull; - -import static com.evolveum.midpoint.util.MiscUtil.getSingleValue; -import static java.util.Collections.emptySet; - -/** - * @author semancik - * - */ -public class LensUtil { - - private static final Trace LOGGER = TraceManager.getTrace(LensUtil.class); - - public static ResourceType getResourceReadOnly(LensContext context, - String resourceOid, ProvisioningService provisioningService, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ResourceType resourceType = context.getResource(resourceOid); - if (resourceType == null) { - // Fetching from provisioning to take advantage of caching and - // pre-parsed schema - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); - resourceType = provisioningService.getObject(ResourceType.class, resourceOid, options, task, result) - .asObjectable(); - context.rememberResource(resourceType); - } - return resourceType; - } - - @NotNull - static ResourceType getResourceReadOnly(LensContext context, String resourceOid, ObjectResolver objectResolver, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ResourceType resourceFromContext = context.getResource(resourceOid); - if (resourceFromContext != null) { - return resourceFromContext; - } else { - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); - ResourceType resourceFetched = objectResolver.getObject(ResourceType.class, resourceOid, options, task, result); - context.rememberResource(resourceFetched); - return resourceFetched; - } - } - - public static String refineProjectionIntent(ShadowKindType kind, String intent, ResourceType resource, PrismContext prismContext) throws SchemaException { - RefinedResourceSchema refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, prismContext); - RefinedObjectClassDefinition rObjClassDef = refinedSchema.getRefinedDefinition(kind, intent); - if (rObjClassDef == null) { - LOGGER.error("No projection definition for kind={}, intent={} in {}", kind, intent, resource); - LOGGER.error("Diagnostic output follows:\n\nResource:\n{}\n\nRefined resource schema:\n{}", - resource.asPrismObject().debugDump(), refinedSchema.debugDump()); - throw new SchemaException("No projection definition for kind="+kind+" intent="+intent+" in "+resource); - } - return rObjClassDef.getIntent(); - } - - public static LensProjectionContext getProjectionContext(LensContext context, PrismObject equivalentAccount, - ProvisioningService provisioningService, PrismContext prismContext, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ShadowType equivalentAccountType = equivalentAccount.asObjectable(); - ShadowKindType kind = ShadowUtil.getKind(equivalentAccountType); - return getProjectionContext(context, ShadowUtil.getResourceOid(equivalentAccountType), - kind, equivalentAccountType.getIntent(), equivalentAccountType.getTag(), provisioningService, - prismContext, task, result); - } - - private static LensProjectionContext getProjectionContext(LensContext context, String resourceOid, - ShadowKindType kind, String intent, String tag, - ProvisioningService provisioningService, PrismContext prismContext, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ResourceType resource = getResourceReadOnly(context, resourceOid, provisioningService, task, result); - String refinedIntent = refineProjectionIntent(kind, intent, resource, prismContext); - ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, false); - return context.findProjectionContext(rsd); - } - - public static LensProjectionContext getOrCreateProjectionContext(LensContext context, - ResourceShadowDiscriminator rsd) { - LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); - if (accountSyncContext == null) { - accountSyncContext = context.createProjectionContext(rsd); - ResourceType resource = context.getResource(rsd.getResourceOid()); - accountSyncContext.setResource(resource); - } - accountSyncContext.setDoReconciliation(context.isDoReconciliationForAllProjections()); - return accountSyncContext; - } - - public static LensProjectionContext createAccountContext(LensContext context, ResourceShadowDiscriminator rsd){ - return new LensProjectionContext(context, rsd); - } - - public static V cloneAndApplyMetadata(V value, boolean isAssignment, - Collection> origins) throws SchemaException { - return cloneAndApplyMetadata(value, isAssignment, () -> getAutoCreationIdentifier(origins)); - } - -// public static Collection cloneAndApplyMetadata(Collection values, boolean isAssignment, -// MappingType mapping) throws SchemaException { -// List rv = new ArrayList<>(); -// for (V value : values) { -// rv.add(cloneAndApplyMetadata(value, isAssignment, mapping::getName)); -// } -// return rv; -// } - - public static V cloneAndApplyMetadata(V value, boolean isAssignment, - PrismValueDeltaSetTripleProducer mapping) throws SchemaException { - return cloneAndApplyMetadata(value, isAssignment, mapping::getIdentifier); - } - - public static V cloneAndApplyMetadata(V value, boolean isAssignment, - MappingType mapping) throws SchemaException { - return cloneAndApplyMetadata(value, isAssignment, mapping::getName); - } - - private static V cloneAndApplyMetadata(V value, boolean isAssignment, - Supplier originMappingNameSupplier) throws SchemaException { - //noinspection unchecked - V cloned = (V) value.clone(); - if (isAssignment && cloned instanceof PrismContainerValue) { - ((PrismContainerValue) cloned).setId(null); - String originMappingName = originMappingNameSupplier.get(); - LOGGER.trace("cloneAndApplyMetadata: originMappingName = {}", originMappingName); - if (originMappingName != null) { - //noinspection unchecked - PrismContainer metadataContainer = ((PrismContainerValue) cloned).findOrCreateContainer(AssignmentType.F_METADATA); - metadataContainer.getValue().asContainerable().setOriginMappingName(originMappingName); - } - } - return cloned; - } - - private static String getAutoCreationIdentifier(Collection> origins) { - // let's ignore conflicts (name1 vs name2, named vs unnamed) for now - for (ItemValueWithOrigin origin : origins) { - if (origin.getMapping() != null && origin.getMapping().getIdentifier() != null) { - return origin.getMapping().getIdentifier(); - } - } - return null; - } - - public static PropertyDelta createActivationTimestampDelta(ActivationStatusType status, - XMLGregorianCalendar now, - PrismContainerDefinition activationDefinition, OriginType origin, - PrismContext prismContext) { - ItemName timestampPropertyName; - if (status == null || status == ActivationStatusType.ENABLED) { - timestampPropertyName = ActivationType.F_ENABLE_TIMESTAMP; - } else if (status == ActivationStatusType.DISABLED) { - timestampPropertyName = ActivationType.F_DISABLE_TIMESTAMP; - } else if (status == ActivationStatusType.ARCHIVED) { - timestampPropertyName = ActivationType.F_ARCHIVE_TIMESTAMP; - } else { - throw new IllegalArgumentException("Unknown activation status "+status); - } - - PrismPropertyDefinition timestampDef = activationDefinition.findPropertyDefinition(timestampPropertyName); - PropertyDelta timestampDelta - = timestampDef.createEmptyDelta(FocusType.F_ACTIVATION.append(timestampPropertyName)); - timestampDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(now, origin, null)); - return timestampDelta; - } - - public static void moveTriggers(LensProjectionContext projCtx, LensFocusContext focusCtx) throws SchemaException { - ObjectDelta projSecondaryDelta = projCtx.getSecondaryDelta(); - if (projSecondaryDelta == null) { - return; - } - Collection modifications = projSecondaryDelta.getModifications(); - Iterator iterator = modifications.iterator(); - while (iterator.hasNext()) { - ItemDelta projModification = iterator.next(); - LOGGER.trace("MOD: {}\n{}", projModification.getPath(), projModification.debugDumpLazily()); - if (projModification.getPath().equivalent(SchemaConstants.PATH_TRIGGER)) { - focusCtx.swallowToProjectionWaveSecondaryDelta(projModification); - iterator.remove(); - } - } - } - - public static Object getIterationVariableValue(LensProjectionContext accCtx) { - Integer iterationOld = null; - PrismObject shadowCurrent = accCtx.getObjectCurrent(); - if (shadowCurrent != null) { - iterationOld = shadowCurrent.asObjectable().getIteration(); - } - if (iterationOld == null) { - return accCtx.getIteration(); - } - PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, - DOMUtil.XSD_INT); - PrismProperty propOld = propDef.instantiate(); - propOld.setRealValue(iterationOld); - PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_QNAME); - propDelta.setRealValuesToReplace(accCtx.getIteration()); - PrismProperty propNew = propDef.instantiate(); - propNew.setRealValue(accCtx.getIteration()); - ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); - return idi; - } - - public static Object getIterationTokenVariableValue(LensProjectionContext accCtx) { - String iterationTokenOld = null; - PrismObject shadowCurrent = accCtx.getObjectCurrent(); - if (shadowCurrent != null) { - iterationTokenOld = shadowCurrent.asObjectable().getIterationToken(); - } - if (iterationTokenOld == null) { - return accCtx.getIterationToken(); - } - PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition( - ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, DOMUtil.XSD_STRING); - PrismProperty propOld = propDef.instantiate(); - propOld.setRealValue(iterationTokenOld); - PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME); - propDelta.setRealValuesToReplace(accCtx.getIterationToken()); - PrismProperty propNew = propDef.instantiate(); - propNew.setRealValue(accCtx.getIterationToken()); - ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); - return idi; - } - - /** - * Extracts the delta from this projection context and also from all other projection contexts that have - * equivalent discriminator. - */ - public static PropertyDelta findAPrioriDelta(LensContext context, - LensProjectionContext projCtx, ItemPath projectionPropertyPath) throws SchemaException { - PropertyDelta aPrioriDelta = null; - for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { - ObjectDelta aProjDelta = aProjCtx.getDelta(); - if (aProjDelta != null) { - PropertyDelta aPropProjDelta = aProjDelta.findPropertyDelta(projectionPropertyPath); - if (aPropProjDelta != null) { - if (aPrioriDelta == null) { - aPrioriDelta = aPropProjDelta.clone(); - } else { - aPrioriDelta.merge(aPropProjDelta); - } - } - } - } - return aPrioriDelta; - } - - /** - * Extracts the delta from this projection context and also from all other projection contexts that have - * equivalent discriminator. - */ - public static ObjectDelta findAPrioriDelta(LensContext context, - LensProjectionContext projCtx) throws SchemaException { - ObjectDelta aPrioriDelta = null; - for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { - ObjectDelta aProjDelta = aProjCtx.getDelta(); - if (aProjDelta != null) { - if (aPrioriDelta == null) { - aPrioriDelta = aProjDelta.clone(); - } else { - aPrioriDelta.merge(aProjDelta); - } - } - } - return aPrioriDelta; - } - - /** - * Returns a list of context that have equivalent discriminator with the reference context. Ordered by "order" in the - * discriminator. - */ - public static List findRelatedContexts( - LensContext context, LensProjectionContext refProjCtx) { - List projCtxs = new ArrayList<>(); - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - if (refDiscr == null) { - return projCtxs; - } - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (refDiscr.equivalent(aDiscr)) { - projCtxs.add(aProjCtx); - } - } - Comparator orderComparator = new Comparator() { - @Override - public int compare(LensProjectionContext ctx1, LensProjectionContext ctx2) { - int order1 = ctx1.getResourceShadowDiscriminator().getOrder(); - int order2 = ctx2.getResourceShadowDiscriminator().getOrder(); - return Integer.compare(order1, order2); - } - }; - Collections.sort(projCtxs, orderComparator); - return projCtxs; - } - - public static boolean hasLowerOrderContext(LensContext context, - LensProjectionContext refProjCtx) { - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { - return true; - } - } - return false; - } - - public static boolean hasDependentContext(LensContext context, - LensProjectionContext targetProjectionContext) { - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - for (ResourceObjectTypeDependencyType dependency: projectionContext.getDependencies()) { - if (isDependencyTargetContext(projectionContext, targetProjectionContext, dependency)) { - return true; - } - } - } - return false; - } - - public static boolean isDependencyTargetContext(LensProjectionContext sourceProjContext, LensProjectionContext targetProjectionContext, ResourceObjectTypeDependencyType dependency) { - ResourceShadowDiscriminator refDiscr = new ResourceShadowDiscriminator(dependency, - sourceProjContext.getResource().getOid(), sourceProjContext.getKind()); - return targetProjectionContext.compareResourceShadowDiscriminator(refDiscr, false); - } - - public static LensProjectionContext findLowerOrderContext(LensContext context, - LensProjectionContext refProjCtx) { - int minOrder = -1; - LensProjectionContext foundCtx = null; - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { - if (minOrder < 0 || (aDiscr.getOrder() < minOrder)) { - minOrder = aDiscr.getOrder(); - foundCtx = aProjCtx; - } - } - } - return foundCtx; - } - - public static void setContextOid(LensContext context, - LensElementContext objectContext, String oid) { - objectContext.setOid(oid); - // Check if we need to propagate this oid also to higher-order contexts - if (!(objectContext instanceof LensProjectionContext)) { - return; - } - LensProjectionContext refProjCtx = (LensProjectionContext)objectContext; - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - if (refDiscr == null) { - return; - } - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (aDiscr != null && refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() < aDiscr.getOrder())) { - aProjCtx.setOid(oid); - } - } - } - - public static PrismObjectDefinition getFocusDefinition(LensContext context) { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return null; - } - Class typeClass = focusContext.getObjectTypeClass(); - return context.getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(typeClass); - } - - public static IterationSpecificationType getIterationSpecification(ObjectTemplateType objectTemplate) { - return objectTemplate != null ? objectTemplate.getIterationSpecification() : null; - } - - public static int determineMaxIterations(IterationSpecificationType iterationSpecType) { - return iterationSpecType != null ? iterationSpecType.getMaxIterations() : 0; - } - - public static String formatIterationToken(LensContext context, - LensElementContext accountContext, IterationSpecificationType iterationType, - int iteration, ExpressionFactory expressionFactory, ExpressionVariables variables, - Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (iterationType == null) { - return formatIterationTokenDefault(iteration); - } - ExpressionType tokenExpressionType = iterationType.getTokenExpression(); - if (tokenExpressionType == null) { - return formatIterationTokenDefault(iteration); - } - PrismContext prismContext = context.getPrismContext(); - PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, - DOMUtil.XSD_STRING); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(tokenExpressionType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), "iteration token expression in "+accountContext.getHumanReadableName(), task, result); - - Collection> sources = new ArrayList<>(); - MutablePrismPropertyDefinition inputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, - DOMUtil.XSD_INT); - inputDefinition.setMaxOccurs(1); - PrismProperty input = inputDefinition.instantiate(); - input.addRealValue(iteration); - ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(input); - Source,PrismPropertyDefinition> iterationSource = new Source<>(idi, ExpressionConstants.VAR_ITERATION_QNAME); - sources.add(iterationSource); - - ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(sources , variables, - "iteration token expression in "+accountContext.getHumanReadableName(), task); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, task, result); - Collection> outputValues = outputTriple.getNonNegativeValues(); - if (outputValues.isEmpty()) { - return ""; - } - if (outputValues.size() > 1) { - throw new ExpressionEvaluationException("Iteration token expression in "+accountContext.getHumanReadableName()+" returned more than one value ("+outputValues.size()+" values)"); - } - String realValue = outputValues.iterator().next().getValue(); - if (realValue == null) { - return ""; - } - return realValue; - } - - public static String formatIterationTokenDefault(int iteration) { - if (iteration == 0) { - return ""; - } - return Integer.toString(iteration); - } - - public static boolean evaluateIterationCondition(LensContext context, - LensElementContext accountContext, IterationSpecificationType iterationType, - int iteration, String iterationToken, boolean beforeIteration, - ExpressionFactory expressionFactory, ExpressionVariables variables, Task task, OperationResult result) - throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - if (iterationType == null) { - return true; - } - ExpressionType expressionType; - String desc; - if (beforeIteration) { - expressionType = iterationType.getPreIterationCondition(); - desc = "pre-iteration expression in "+accountContext.getHumanReadableName(); - } else { - expressionType = iterationType.getPostIterationCondition(); - desc = "post-iteration expression in "+accountContext.getHumanReadableName(); - } - if (expressionType == null) { - return true; - } - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( - expressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), MiscSchemaUtil.getExpressionProfile(), - desc, task, result); - - variables.put(ExpressionConstants.VAR_ITERATION, iteration, Integer.class); - variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken, String.class); - - ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); - ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); - PrismValueDeltaSetTriple> outputTriple = - ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); - Collection> outputValues = outputTriple.getNonNegativeValues(); - if (outputValues.isEmpty()) { - return false; - } - if (outputValues.size() > 1) { - throw new ExpressionEvaluationException(desc+" returned more than one value ("+outputValues.size()+" values)"); - } - Boolean realValue = outputValues.iterator().next().getValue(); - if (realValue == null) { - return false; - } - return realValue; - - } - - /** - * Used for assignments and similar objects that do not have separate lifecycle. - */ - public static boolean isAssignmentValid(AssignmentHolderType focus, AssignmentType assignment, XMLGregorianCalendar now, - ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { - ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { - // Archetype assignments are always valid, even in non-valid lifecycle states. - // The object cannot lose its (arche)type. - return true; - } - String focusLifecycleState = focus.getLifecycleState(); - - if (!activationComputer.lifecycleHasActiveAssignments(focusLifecycleState, focusStateModel)) { - return false; - } - return isValid(assignment.getLifecycleState(), assignment.getActivation(), now, activationComputer, focusStateModel); - } - - public static Collection getForcedAssignments(LifecycleStateModelType lifecycleModel, String targetLifecycle, - ObjectResolver objectResolver, PrismContext prismContext, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - VirtualAssignmenetSpecification virtualAssignmenetSpecification = LifecycleUtil.getForcedAssignmentSpecification(lifecycleModel, targetLifecycle, prismContext); - - Collection forcedAssignments = new HashSet<>(); - if (virtualAssignmenetSpecification != null) { - - ResultHandler handler = (object, parentResult) -> { - AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(object, prismContext); - return forcedAssignments.add(assignment); - }; - - objectResolver.searchIterative(virtualAssignmenetSpecification.getType(), - prismContext.queryFactory().createQuery(virtualAssignmenetSpecification.getFilter()), null, handler, task, result); - - } - - return forcedAssignments; - } - - public static boolean isFocusValid(AssignmentHolderType focus, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { - if (FocusType.class.isAssignableFrom(focus.getClass())) { - return isValid(focus.getLifecycleState(), ((FocusType) focus).getActivation(), now, activationComputer, focusStateModel); - } - return isValid(focus.getLifecycleState(), null, now, activationComputer, focusStateModel); - } - - private static boolean isValid(String lifecycleState, ActivationType activationType, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { - TimeIntervalStatusType validityStatus = activationComputer.getValidityStatus(activationType, now); - ActivationStatusType effectiveStatus = activationComputer.getEffectiveStatus(lifecycleState, activationType, validityStatus, focusStateModel); - return effectiveStatus == ActivationStatusType.ENABLED; - } - - public static AssignmentPathVariables computeAssignmentPathVariables(AssignmentPathImpl assignmentPath) throws SchemaException { - if (assignmentPath == null || assignmentPath.isEmpty()) { - return null; - } - AssignmentPathVariables vars = new AssignmentPathVariables(); - vars.setAssignmentPath(assignmentPath.clone()); - - Iterator iterator = assignmentPath.getSegments().iterator(); - while (iterator.hasNext()) { - AssignmentPathSegmentImpl segment = iterator.next(); - ItemDeltaItem,PrismContainerDefinition> segmentAssignmentIdi = segment.getAssignmentIdi(); - - ItemDeltaItem,PrismContainerDefinition> magicAssignmentIdi; - // Magic assignment - if (vars.getMagicAssignment() == null) { - magicAssignmentIdi = segmentAssignmentIdi.clone(); - vars.setMagicAssignment(magicAssignmentIdi); - } else { - // Collect extension values from the assignment extension - magicAssignmentIdi = vars.getMagicAssignment(); - mergeExtension(magicAssignmentIdi, segmentAssignmentIdi); - } - - // Collect extension values from the source object extension - ObjectType segmentSource = segment.getSource(); - if (segmentSource != null) { - mergeExtension(magicAssignmentIdi, segmentSource.asPrismObject()); - } - - // immediate assignment (use assignment from previous iteration) - vars.setImmediateAssignment(vars.getThisAssignment()); - - // this assignment - ItemDeltaItem,PrismContainerDefinition> thisAssignment = segmentAssignmentIdi.clone(); - vars.setThisAssignment(thisAssignment); - - if (iterator.hasNext() && segmentSource instanceof AbstractRoleType) { - vars.setImmediateRole((PrismObject) segmentSource.asPrismObject()); - } - } - - AssignmentPathSegmentImpl focusAssignmentSegment = assignmentPath.first(); - vars.setFocusAssignment(focusAssignmentSegment.getAssignmentIdi().clone()); - - // a bit of hack -- TODO reconsider in 3.7 - // objects are already cloned - convertToLegacy(vars.getMagicAssignment()); - convertToLegacy(vars.getThisAssignment()); - convertToLegacy(vars.getFocusAssignment()); - convertToLegacy(vars.getImmediateAssignment()); - - return vars; - } - - private static void convertToLegacy( - ItemDeltaItem, PrismContainerDefinition> idi) { - if (idi == null || idi.getDelta() == null || idi.getSubItemDeltas() != null) { - return; - } - // Legacy approach (when adding/removing assignments) was: itemOld+itemNew = value, delta = null - // This was recently changed, to provide precise information (add = null->itemNew, delete = itemOld->null). - // However, to not break scripts before 3.6 release we provide imitation of old behavior here. - // (Moreover, for magic assignment the delta is not correct anyway.) - if (idi.getDelta().isAdd() || idi.getDelta().isReplace()) { - idi.setItemOld(idi.getItemNew().clone()); - } else { - idi.setItemNew(idi.getItemOld().clone()); - } - idi.setDelta(null); - } - - private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, ItemDeltaItem,PrismContainerDefinition> srcIdi) throws SchemaException { - mergeExtension(destIdi.getItemOld(), srcIdi.getItemOld()); - mergeExtension(destIdi.getItemNew(), srcIdi.getItemNew()); - if (srcIdi.getDelta() != null || srcIdi.getSubItemDeltas() != null) { - throw new UnsupportedOperationException("Merge of IDI with deltas not supported"); - } - } - - private static void mergeExtension(Item,PrismContainerDefinition> dstItem, - Item,PrismContainerDefinition> srcItem) throws SchemaException { - if (srcItem == null || dstItem == null) { - return; - } - PrismContainer srcExtension = ((PrismContainer)srcItem).findContainer(AssignmentType.F_EXTENSION); - mergeExtensionContainers(dstItem, srcExtension); - } - - private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, - PrismObject srcObject) throws SchemaException { - if (srcObject == null) { - return; - } - - PrismContainer srcExtension = srcObject.findContainer(ObjectType.F_EXTENSION); - - mergeExtensionContainers(destIdi.getItemNew(), srcExtension); - mergeExtensionContainers(destIdi.getItemOld(), srcExtension); - } - - private static void mergeExtensionContainers(Item,PrismContainerDefinition> dstItem, PrismContainer srcExtension) throws SchemaException { - if (dstItem == null) { - return; - } - PrismContainer dstContainer = (PrismContainer) dstItem; - if (srcExtension != null && !srcExtension.isEmpty()) { - PrismContainer dstExtensionContainer = dstContainer.findOrCreateContainer(AssignmentType.F_EXTENSION); - PrismContainerValue dstExtensionContainerValue = dstExtensionContainer.getValues().isEmpty() - ? dstExtensionContainer.createNewValue() : dstExtensionContainer.getValue(); - ObjectTypeUtil.mergeExtension(dstExtensionContainerValue, srcExtension.getValue()); - } - } - - public static MappingImpl.Builder addAssignmentPathVariables(MappingImpl.Builder builder, AssignmentPathVariables assignmentPathVariables, PrismContext prismContext) { - ExpressionVariables expressionVariables = new ExpressionVariables(); - ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, expressionVariables, prismContext); - return builder.addVariableDefinitions(expressionVariables); - } - - public static void checkContextSanity(LensContext context, String activityDescription, - OperationResult result) throws SchemaException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - PrismObject focusObjectNew = focusContext.getObjectNew(); - if (focusObjectNew != null) { - PolyStringType namePolyType = focusObjectNew.asObjectable().getName(); - if (namePolyType == null) { - throw new SchemaException("Focus "+focusObjectNew+" does not have a name after "+activityDescription); - } - ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); - checkArchetypePolicy(focusContext, archetypePolicy); - } - } - } - - private static void checkArchetypePolicy(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy) throws SchemaException, PolicyViolationException { - if (archetypePolicy == null) { - return; - } - PrismObject focusObjectNew = focusContext.getObjectNew(); - ObjectDelta focusDelta = focusContext.getDelta(); - - for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { - processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); - } - // Deprecated - for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { - processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); - } - - } - - private static void processItemConstraint(LensFocusContext focusContext, ObjectDelta focusDelta, PrismObject focusObjectNew, ItemConstraintType itemConstraintType) throws PolicyViolationException { - ItemPath itemPath = itemConstraintType.getPath().getItemPath(); - if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { - if (focusDelta != null) { - if (focusDelta.isAdd()) { - PrismProperty propNew = focusObjectNew.findProperty(itemPath); - if (propNew != null) { - // prop delta is OK, but it has to match - if (focusObjectNew.getOid() != null) { - if (!focusObjectNew.getOid().equals(propNew.getRealValue().toString())) { - throw new PolicyViolationException("Cannot set "+itemPath+" to a value different than OID in oid bound mode"); - } - } - } - } else { - PropertyDelta nameDelta = focusDelta.findPropertyDelta(itemPath); - if (nameDelta != null) { - if (nameDelta.isReplace()) { - Collection> valuesToReplace = nameDelta.getValuesToReplace(); - if (valuesToReplace.size() == 1) { - String stringValue = valuesToReplace.iterator().next().getValue().toString(); - if (focusContext.getOid().equals(stringValue)) { - // This is OK. It is most likely a correction made by a recompute. - return; - } - } - } - throw new PolicyViolationException("Cannot change "+itemPath+" in oid bound mode"); - } - } - } - } - - } - - public static PrismContainer createAssignmentSingleValueContainer(@NotNull AssignmentType assignmentType) throws SchemaException { - // Make it appear to be single-value. Therefore paths without segment IDs will work. - return assignmentType.asPrismContainerValue().asSingleValuedContainer(SchemaConstantsGenerated.C_ASSIGNMENT); - } - - public static AssignmentType getAssignmentType(ItemDeltaItem,PrismContainerDefinition> assignmentIdi, boolean old) { - return PrismContainerValue.asContainerable(assignmentIdi.getSingleValue(old)); - } - - - public static String getChannel(LensContext context, Task task) { - if (context != null && context.getChannel() != null){ - return context.getChannel(); - } else if (task.getChannel() != null){ - return task.getChannel(); - } - return null; - } - - public static void setDeltaOldValue(LensElementContext ctx, ItemDelta itemDelta) { - if (itemDelta.getEstimatedOldValues() != null) { - return; - } - if (ctx.getObjectOld() == null) { - return; - } - Item itemOld = ctx.getObjectOld().findItem(itemDelta.getPath()); - if (itemOld != null) { - //noinspection unchecked - itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); - return; - } - // Here we need to distinguish whether the item is missing because it is not filled in (e.g. familyName in MID-4237) - // or because it was not loaded (as for attributes or associations). - if (!isItemLoadable(ctx.getObjectOld(), itemDelta.getPath())) { - itemDelta.setEstimatedOldValues(emptySet()); - return; - } - // get the old data from current object. Still better estimate than nothing - if (ctx.getObjectCurrent() != null) { - itemOld = ctx.getObjectCurrent().findItem(itemDelta.getPath()); - if (itemOld != null) { - //noinspection unchecked - itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); - } - } - } - - // a heuristic by now - private static boolean isItemLoadable(PrismObject object, ItemPath path) { - if (!(object.asObjectable() instanceof ShadowType)) { - return false; - } - return path.startsWithName(ShadowType.F_ATTRIBUTES) || path.startsWithName(ShadowType.F_ASSOCIATION); - } - - public static void setDeltaOldValue(LensElementContext ctx, ObjectDelta objectDelta) { - if (objectDelta == null) { - return; - } - if (!objectDelta.isModify()) { - return; - } - for (ItemDelta modification: objectDelta.getModifications()) { - setDeltaOldValue(ctx, modification); - } - } - - public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta focusDelta, OperationResult result, - LensElementContext focusContext, LensProjectionContext projCtx) { - return createObjectDeltaOperation(focusDelta, result, focusContext, projCtx, null); - } - - // projCtx may or may not be present (object itself can be focus or projection) - public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta objectDelta, OperationResult result, - LensElementContext objectContext, - LensProjectionContext projCtx, - ResourceType resource) { - LensObjectDeltaOperation objectDeltaOp = new LensObjectDeltaOperation<>(objectDelta.clone()); - objectDeltaOp.setExecutionResult(result); - PrismObject object = objectContext.getObjectAny(); - if (object != null) { - PolyString name = object.getName(); - if (name == null && object.asObjectable() instanceof ShadowType) { - try { - name = ShadowUtil.determineShadowName((PrismObject) object); - if (name == null) { - LOGGER.debug("No name for shadow:\n{}", object.debugDump()); - } else if (name.getNorm() == null) { - name.recompute(objectContext.getPrismContext().getDefaultPolyStringNormalizer()); - } - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine name for shadow -- continuing with no name; shadow:\n{}", e, object.debugDump()); - } - } - objectDeltaOp.setObjectName(name); - } - if (resource == null && projCtx != null) { - resource = projCtx.getResource(); - } - if (resource != null) { - objectDeltaOp.setResourceOid(resource.getOid()); - objectDeltaOp.setResourceName(PolyString.toPolyString(resource.getName())); - } else if (objectContext instanceof LensProjectionContext) { - objectDeltaOp.setResourceOid(((LensProjectionContext) objectContext).getResourceOid()); - } - return objectDeltaOp; - } - - public static void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers, - Collection policySituations) { - - LOGGER.debug("Policy rule {} triggered: {}", rule.getName(), triggers); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Policy rule {} triggered:\n{}", rule.getName(), DebugUtil.debugDump(triggers, 1)); - } - - ((EvaluatedPolicyRuleImpl) rule).addTriggers(triggers); - CollectionUtils.addIgnoreNull(policySituations, rule.getPolicySituation()); - } - - public static void processRuleWithException(@NotNull EvaluatedPolicyRule rule, Collection> triggers, - PolicyExceptionType policyException) { - - LOGGER.debug("Policy rule {} would be triggered, but there is an exception for it. Not triggering", rule.getName()); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Policy rule {} would be triggered, but there is an exception for it:\nTriggers:\n{}\nException:\n{}", - rule.getName(), DebugUtil.debugDump(triggers, 1), policyException); - } - ((EvaluatedPolicyRuleImpl)rule).addPolicyException(policyException); - } - - - public static void checkMaxIterations(int iteration, int maxIterations, String conflictMessage, String humanReadableName) - throws ObjectAlreadyExistsException { - if (iteration > maxIterations) { - StringBuilder sb = new StringBuilder(); - if (iteration == 1) { - sb.append("Error processing "); - } else { - sb.append("Too many iterations (").append(iteration).append(") for "); - } - sb.append(humanReadableName); - if (iteration == 1) { - sb.append(": constraint violation: "); - } else { - sb.append(": cannot determine values that satisfy constraints: "); - } - if (conflictMessage != null) { - sb.append(conflictMessage); - } - throw new ObjectAlreadyExistsException(sb.toString()); - } - } - - public static boolean needsFullShadowForCredentialProcessing(LensProjectionContext projCtx) throws SchemaException { - RefinedObjectClassDefinition refinedProjDef = projCtx.getStructuralObjectClassDefinition(); - if (refinedProjDef == null) { - return false; - } - - List outboundMappingType = refinedProjDef.getPasswordOutbound(); - if (outboundMappingType == null) { - return false; - } - for (MappingType mappingType: outboundMappingType) { - if (mappingType.getStrength() == MappingStrengthType.STRONG || mappingType.getStrength() == MappingStrengthType.WEAK) { - return true; - } - } - return false; - } - - public static boolean isPasswordReturnedByDefault(LensProjectionContext projCtx) { - CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), CredentialsCapabilityType.class); - return CapabilityUtil.isPasswordReturnedByDefault(credentialsCapabilityType); - } - - public static boolean evaluateBoolean(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, - task, result, - DOMUtil.XSD_BOOLEAN, false, null); - } - - public static String evaluateString(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, - task, result, - DOMUtil.XSD_STRING, null, null); - } - - public static LocalizableMessageType evaluateLocalizableMessageType(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - Function additionalConvertor = (o) -> { - if (o == null || o instanceof LocalizableMessageType) { - return o; - } else if (o instanceof LocalizableMessage) { - return LocalizationUtil.createLocalizableMessageType((LocalizableMessage) o); - } else { - return new SingleLocalizableMessageType().fallbackMessage(String.valueOf(o)); - } - }; - return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, - task, result, LocalizableMessageType.COMPLEX_TYPE, null, additionalConvertor); - } - - public static T evaluateExpressionSingle(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result, QName typeName, - T defaultValue, Function additionalConvertor) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition( - new QName(SchemaConstants.NS_C, "result"), typeName); - Expression,PrismPropertyDefinition> expression = - expressionFactory.makeExpression(expressionBean, resultDef, MiscSchemaUtil.getExpressionProfile(), contextDescription, task, result); - ExpressionEvaluationContext eeContext = new ExpressionEvaluationContext(null, expressionVariables, contextDescription, task); - eeContext.setAdditionalConvertor(additionalConvertor); - PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, eeContext, task, result); - List results = exprResultTriple.getZeroSet().stream() - .map(ppv -> (T) ppv.getRealValue()) - .collect(Collectors.toList()); - return getSingleValue(results, defaultValue, contextDescription); - } - - @NotNull - public static SingleLocalizableMessageType interpretLocalizableMessageTemplate(LocalizableMessageTemplateType template, - ExpressionVariables var, ExpressionFactory expressionFactory, PrismContext prismContext, - Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, - ConfigurationException, SecurityViolationException { - SingleLocalizableMessageType rv = new SingleLocalizableMessageType(); - if (template.getKey() != null) { - rv.setKey(template.getKey()); - } else if (template.getKeyExpression() != null) { - rv.setKey(evaluateString(template.getKeyExpression(), var, "localizable message key expression", expressionFactory, prismContext, task, result)); - } - if (!template.getArgument().isEmpty() && !template.getArgumentExpression().isEmpty()) { - throw new IllegalArgumentException("Both argument and argumentExpression items are non empty"); - } else if (!template.getArgumentExpression().isEmpty()) { - for (ExpressionType argumentExpression : template.getArgumentExpression()) { - LocalizableMessageType argument = evaluateLocalizableMessageType(argumentExpression, var, - "localizable message argument expression", expressionFactory, prismContext, task, result); - rv.getArgument().add(new LocalizableMessageArgumentType().localizable(argument)); - } - } else { - // TODO allow localizable messages templates here - rv.getArgument().addAll(template.getArgument()); - } - if (template.getFallbackMessage() != null) { - rv.setFallbackMessage(template.getFallbackMessage()); - } else if (template.getFallbackMessageExpression() != null) { - rv.setFallbackMessage(evaluateString(template.getFallbackMessageExpression(), var, - "localizable message fallback expression", expressionFactory, prismContext, task, result)); - } - return rv; - } - - public static void reclaimSequences(LensContext context, RepositoryService repositoryService, Task task, OperationResult result) throws SchemaException { - if (context == null) { - return; - } - - Map sequenceMap = context.getSequences(); - LOGGER.trace("Context sequence map: {}", sequenceMap); - for (Map.Entry sequenceMapEntry: sequenceMap.entrySet()) { - Collection unusedValues = new ArrayList<>(1); - unusedValues.add(sequenceMapEntry.getValue()); - try { - LOGGER.trace("Returning value {} to sequence {}", sequenceMapEntry.getValue(), sequenceMapEntry.getKey()); - repositoryService.returnUnusedValuesToSequence(sequenceMapEntry.getKey(), unusedValues, result); - } catch (ObjectNotFoundException e) { - LOGGER.error("Cannot return unused value to sequence {}: it does not exist", sequenceMapEntry.getKey(), e); - // ... but otherwise ignore it and go on - } - } - } - - public static void applyObjectPolicyConstraints(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext) throws SchemaException, ConfigurationException { - if (archetypePolicy == null) { - return; - } - - final PrismObject focusNew = focusContext.getObjectNew(); - if (focusNew == null) { - // This is delete. Nothing to do. - return; - } - - for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { - applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); - } - // Deprecated - for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { - applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); - } - } - - private static void applyObjectPolicyItemConstraint(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext, PrismObject focusNew, ItemConstraintType itemConstraintType) throws SchemaException, ConfigurationException { - if (itemConstraintType.getPath() == null) { - LOGGER.error("Invalid configuration. Path is mandatory for property constraint definition in {} defined in system configuration", archetypePolicy); - throw new SchemaException("Invalid configuration. Path is mandatory for property constraint definition in " + archetypePolicy + " defined in system configuration."); - } - ItemPath itemPath = itemConstraintType.getPath().getItemPath(); - if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { - PrismProperty prop = focusNew.findProperty(itemPath); - if (prop == null || prop.isEmpty()) { - String newValue = focusNew.getOid(); - if (newValue == null) { - newValue = OidUtil.generateOid(); - } - LOGGER.trace("Generating new OID-bound value for {}: {}", itemPath, newValue); - PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); - PrismPropertyDefinition propDef = focusDefinition.findPropertyDefinition(itemPath); - if (propDef == null) { - throw new SchemaException("No definition for property "+itemPath+" in "+focusDefinition+" as specified in object policy"); - } - PropertyDelta propDelta = propDef.createEmptyDelta(itemPath); - if (String.class.isAssignableFrom(propDef.getTypeClass())) { - propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(newValue, OriginType.USER_POLICY, null)); - } else if (PolyString.class.isAssignableFrom(propDef.getTypeClass())) { - propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(new PolyString(newValue), OriginType.USER_POLICY, null)); - } else { - throw new SchemaException("Unsupported type "+propDef.getTypeName()+" for property "+itemPath+" in "+focusDefinition+" as specified in object policy, only string and polystring properties are supported for OID-bound mode"); - } - focusContext.swallowToSecondaryDelta(propDelta); - focusContext.recompute(); - } - } - } - - public static LensContext.ExportType getExportType(TraceType trace, OperationResult result) { - return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.MINIMAL; - } - - public static LensContext.ExportType getExportTypeTraceOrReduced(TraceType trace, OperationResult result) { - return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.REDUCED; - } - - public static ItemDelta getAprioriItemDelta(ObjectDelta focusDelta, ItemPath itemPath) { - return focusDelta != null ? focusDelta.findItemDelta(itemPath) : null; - } - - public static String determineExplicitArchetypeOid(PrismObject object) { - String explicitArchetypeOid = null; - // Used in cases where archetype assignment haven't had the change to be processed yet. - // E.g. in case that we are creating a new object with archetype assignment - if (object.canRepresent(AssignmentHolderType.class)) { - AssignmentHolderType assignmentHolderType = (AssignmentHolderType)object.asObjectable(); - List archetypeRefs = assignmentHolderType.getArchetypeRef(); - if (archetypeRefs.isEmpty()) { - explicitArchetypeOid = determineExplicitArchetypeOidFromAssignments(object); - } - } - return explicitArchetypeOid; - } - - public static String determineExplicitArchetypeOidFromAssignments(PrismObject object) { - String explicitArchetypeOid = null; - if (object.canRepresent(AssignmentHolderType.class)) { - for (AssignmentType assignment : ((AssignmentHolderType)object.asObjectable()).getAssignment()) { - ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { - explicitArchetypeOid = targetRef.getOid(); - } - } - } - return explicitArchetypeOid; - } -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; +import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; +import com.evolveum.midpoint.prism.path.ItemName; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.SchemaConstantsGenerated; +import com.evolveum.midpoint.schema.util.*; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang.BooleanUtils; + +import com.evolveum.midpoint.common.ActivationComputer; +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.delta.PropertyDelta; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.common.ObjectResolver; +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.repo.common.expression.Source; +import com.evolveum.midpoint.schema.CapabilityUtil; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.ResultHandler; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.VirtualAssignmenetSpecification; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import org.jetbrains.annotations.NotNull; + +import static com.evolveum.midpoint.util.MiscUtil.getSingleValue; +import static java.util.Collections.emptySet; + +/** + * @author semancik + * + */ +public class LensUtil { + + private static final Trace LOGGER = TraceManager.getTrace(LensUtil.class); + + public static ResourceType getResourceReadOnly(LensContext context, + String resourceOid, ProvisioningService provisioningService, Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ResourceType resourceType = context.getResource(resourceOid); + if (resourceType == null) { + // Fetching from provisioning to take advantage of caching and + // pre-parsed schema + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); + resourceType = provisioningService.getObject(ResourceType.class, resourceOid, options, task, result) + .asObjectable(); + context.rememberResource(resourceType); + } + return resourceType; + } + + @NotNull + static ResourceType getResourceReadOnly(LensContext context, String resourceOid, ObjectResolver objectResolver, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ResourceType resourceFromContext = context.getResource(resourceOid); + if (resourceFromContext != null) { + return resourceFromContext; + } else { + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); + ResourceType resourceFetched = objectResolver.getObject(ResourceType.class, resourceOid, options, task, result); + context.rememberResource(resourceFetched); + return resourceFetched; + } + } + + public static String refineProjectionIntent(ShadowKindType kind, String intent, ResourceType resource, PrismContext prismContext) throws SchemaException { + RefinedResourceSchema refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, prismContext); + RefinedObjectClassDefinition rObjClassDef = refinedSchema.getRefinedDefinition(kind, intent); + if (rObjClassDef == null) { + LOGGER.error("No projection definition for kind={}, intent={} in {}", kind, intent, resource); + LOGGER.error("Diagnostic output follows:\n\nResource:\n{}\n\nRefined resource schema:\n{}", + resource.asPrismObject().debugDump(), refinedSchema.debugDump()); + throw new SchemaException("No projection definition for kind="+kind+" intent="+intent+" in "+resource); + } + return rObjClassDef.getIntent(); + } + + public static LensProjectionContext getProjectionContext(LensContext context, PrismObject equivalentAccount, + ProvisioningService provisioningService, PrismContext prismContext, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ShadowType equivalentAccountType = equivalentAccount.asObjectable(); + ShadowKindType kind = ShadowUtil.getKind(equivalentAccountType); + return getProjectionContext(context, ShadowUtil.getResourceOid(equivalentAccountType), + kind, equivalentAccountType.getIntent(), equivalentAccountType.getTag(), provisioningService, + prismContext, task, result); + } + + private static LensProjectionContext getProjectionContext(LensContext context, String resourceOid, + ShadowKindType kind, String intent, String tag, + ProvisioningService provisioningService, PrismContext prismContext, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ResourceType resource = getResourceReadOnly(context, resourceOid, provisioningService, task, result); + String refinedIntent = refineProjectionIntent(kind, intent, resource, prismContext); + ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, false); + return context.findProjectionContext(rsd); + } + + public static LensProjectionContext getOrCreateProjectionContext(LensContext context, + ResourceShadowDiscriminator rsd) { + LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); + if (accountSyncContext == null) { + accountSyncContext = context.createProjectionContext(rsd); + ResourceType resource = context.getResource(rsd.getResourceOid()); + accountSyncContext.setResource(resource); + } + accountSyncContext.setDoReconciliation(context.isDoReconciliationForAllProjections()); + return accountSyncContext; + } + + public static LensProjectionContext createAccountContext(LensContext context, ResourceShadowDiscriminator rsd){ + return new LensProjectionContext(context, rsd); + } + + public static V cloneAndApplyMetadata(V value, boolean isAssignment, + Collection> origins) throws SchemaException { + return cloneAndApplyMetadata(value, isAssignment, () -> getAutoCreationIdentifier(origins)); + } + +// public static Collection cloneAndApplyMetadata(Collection values, boolean isAssignment, +// MappingType mapping) throws SchemaException { +// List rv = new ArrayList<>(); +// for (V value : values) { +// rv.add(cloneAndApplyMetadata(value, isAssignment, mapping::getName)); +// } +// return rv; +// } + + public static V cloneAndApplyMetadata(V value, boolean isAssignment, + PrismValueDeltaSetTripleProducer mapping) throws SchemaException { + return cloneAndApplyMetadata(value, isAssignment, mapping::getIdentifier); + } + + public static V cloneAndApplyMetadata(V value, boolean isAssignment, + MappingType mapping) throws SchemaException { + return cloneAndApplyMetadata(value, isAssignment, mapping::getName); + } + + private static V cloneAndApplyMetadata(V value, boolean isAssignment, + Supplier originMappingNameSupplier) throws SchemaException { + //noinspection unchecked + V cloned = (V) value.clone(); + if (isAssignment && cloned instanceof PrismContainerValue) { + ((PrismContainerValue) cloned).setId(null); + String originMappingName = originMappingNameSupplier.get(); + LOGGER.trace("cloneAndApplyMetadata: originMappingName = {}", originMappingName); + if (originMappingName != null) { + //noinspection unchecked + PrismContainer metadataContainer = ((PrismContainerValue) cloned).findOrCreateContainer(AssignmentType.F_METADATA); + metadataContainer.getValue().asContainerable().setOriginMappingName(originMappingName); + } + } + return cloned; + } + + private static String getAutoCreationIdentifier(Collection> origins) { + // let's ignore conflicts (name1 vs name2, named vs unnamed) for now + for (ItemValueWithOrigin origin : origins) { + if (origin.getMapping() != null && origin.getMapping().getIdentifier() != null) { + return origin.getMapping().getIdentifier(); + } + } + return null; + } + + public static PropertyDelta createActivationTimestampDelta(ActivationStatusType status, + XMLGregorianCalendar now, + PrismContainerDefinition activationDefinition, OriginType origin, + PrismContext prismContext) { + ItemName timestampPropertyName; + if (status == null || status == ActivationStatusType.ENABLED) { + timestampPropertyName = ActivationType.F_ENABLE_TIMESTAMP; + } else if (status == ActivationStatusType.DISABLED) { + timestampPropertyName = ActivationType.F_DISABLE_TIMESTAMP; + } else if (status == ActivationStatusType.ARCHIVED) { + timestampPropertyName = ActivationType.F_ARCHIVE_TIMESTAMP; + } else { + throw new IllegalArgumentException("Unknown activation status "+status); + } + + PrismPropertyDefinition timestampDef = activationDefinition.findPropertyDefinition(timestampPropertyName); + PropertyDelta timestampDelta + = timestampDef.createEmptyDelta(FocusType.F_ACTIVATION.append(timestampPropertyName)); + timestampDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(now, origin, null)); + return timestampDelta; + } + + public static void moveTriggers(LensProjectionContext projCtx, LensFocusContext focusCtx) throws SchemaException { + ObjectDelta projSecondaryDelta = projCtx.getSecondaryDelta(); + if (projSecondaryDelta == null) { + return; + } + Collection modifications = projSecondaryDelta.getModifications(); + Iterator iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta projModification = iterator.next(); + LOGGER.trace("MOD: {}\n{}", projModification.getPath(), projModification.debugDumpLazily()); + if (projModification.getPath().equivalent(SchemaConstants.PATH_TRIGGER)) { + focusCtx.swallowToProjectionWaveSecondaryDelta(projModification); + iterator.remove(); + } + } + } + + public static Object getIterationVariableValue(LensProjectionContext accCtx) { + Integer iterationOld = null; + PrismObject shadowCurrent = accCtx.getObjectCurrent(); + if (shadowCurrent != null) { + iterationOld = shadowCurrent.asObjectable().getIteration(); + } + if (iterationOld == null) { + return accCtx.getIteration(); + } + PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, + DOMUtil.XSD_INT); + PrismProperty propOld = propDef.instantiate(); + propOld.setRealValue(iterationOld); + PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_QNAME); + propDelta.setRealValuesToReplace(accCtx.getIteration()); + PrismProperty propNew = propDef.instantiate(); + propNew.setRealValue(accCtx.getIteration()); + ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); + return idi; + } + + public static Object getIterationTokenVariableValue(LensProjectionContext accCtx) { + String iterationTokenOld = null; + PrismObject shadowCurrent = accCtx.getObjectCurrent(); + if (shadowCurrent != null) { + iterationTokenOld = shadowCurrent.asObjectable().getIterationToken(); + } + if (iterationTokenOld == null) { + return accCtx.getIterationToken(); + } + PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition( + ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, DOMUtil.XSD_STRING); + PrismProperty propOld = propDef.instantiate(); + propOld.setRealValue(iterationTokenOld); + PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME); + propDelta.setRealValuesToReplace(accCtx.getIterationToken()); + PrismProperty propNew = propDef.instantiate(); + propNew.setRealValue(accCtx.getIterationToken()); + ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); + return idi; + } + + /** + * Extracts the delta from this projection context and also from all other projection contexts that have + * equivalent discriminator. + */ + public static PropertyDelta findAPrioriDelta(LensContext context, + LensProjectionContext projCtx, ItemPath projectionPropertyPath) throws SchemaException { + PropertyDelta aPrioriDelta = null; + for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { + ObjectDelta aProjDelta = aProjCtx.getDelta(); + if (aProjDelta != null) { + PropertyDelta aPropProjDelta = aProjDelta.findPropertyDelta(projectionPropertyPath); + if (aPropProjDelta != null) { + if (aPrioriDelta == null) { + aPrioriDelta = aPropProjDelta.clone(); + } else { + aPrioriDelta.merge(aPropProjDelta); + } + } + } + } + return aPrioriDelta; + } + + /** + * Extracts the delta from this projection context and also from all other projection contexts that have + * equivalent discriminator. + */ + public static ObjectDelta findAPrioriDelta(LensContext context, + LensProjectionContext projCtx) throws SchemaException { + ObjectDelta aPrioriDelta = null; + for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { + ObjectDelta aProjDelta = aProjCtx.getDelta(); + if (aProjDelta != null) { + if (aPrioriDelta == null) { + aPrioriDelta = aProjDelta.clone(); + } else { + aPrioriDelta.merge(aProjDelta); + } + } + } + return aPrioriDelta; + } + + /** + * Returns a list of context that have equivalent discriminator with the reference context. Ordered by "order" in the + * discriminator. + */ + public static List findRelatedContexts( + LensContext context, LensProjectionContext refProjCtx) { + List projCtxs = new ArrayList<>(); + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + if (refDiscr == null) { + return projCtxs; + } + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (refDiscr.equivalent(aDiscr)) { + projCtxs.add(aProjCtx); + } + } + Comparator orderComparator = new Comparator() { + @Override + public int compare(LensProjectionContext ctx1, LensProjectionContext ctx2) { + int order1 = ctx1.getResourceShadowDiscriminator().getOrder(); + int order2 = ctx2.getResourceShadowDiscriminator().getOrder(); + return Integer.compare(order1, order2); + } + }; + Collections.sort(projCtxs, orderComparator); + return projCtxs; + } + + public static boolean hasLowerOrderContext(LensContext context, + LensProjectionContext refProjCtx) { + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { + return true; + } + } + return false; + } + + public static boolean hasDependentContext(LensContext context, + LensProjectionContext targetProjectionContext) { + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + for (ResourceObjectTypeDependencyType dependency: projectionContext.getDependencies()) { + if (isDependencyTargetContext(projectionContext, targetProjectionContext, dependency)) { + return true; + } + } + } + return false; + } + + public static boolean isDependencyTargetContext(LensProjectionContext sourceProjContext, LensProjectionContext targetProjectionContext, ResourceObjectTypeDependencyType dependency) { + ResourceShadowDiscriminator refDiscr = new ResourceShadowDiscriminator(dependency, + sourceProjContext.getResource().getOid(), sourceProjContext.getKind()); + return targetProjectionContext.compareResourceShadowDiscriminator(refDiscr, false); + } + + public static LensProjectionContext findLowerOrderContext(LensContext context, + LensProjectionContext refProjCtx) { + int minOrder = -1; + LensProjectionContext foundCtx = null; + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { + if (minOrder < 0 || (aDiscr.getOrder() < minOrder)) { + minOrder = aDiscr.getOrder(); + foundCtx = aProjCtx; + } + } + } + return foundCtx; + } + + public static void setContextOid(LensContext context, + LensElementContext objectContext, String oid) { + objectContext.setOid(oid); + // Check if we need to propagate this oid also to higher-order contexts + if (!(objectContext instanceof LensProjectionContext)) { + return; + } + LensProjectionContext refProjCtx = (LensProjectionContext)objectContext; + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + if (refDiscr == null) { + return; + } + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (aDiscr != null && refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() < aDiscr.getOrder())) { + aProjCtx.setOid(oid); + } + } + } + + public static PrismObjectDefinition getFocusDefinition(LensContext context) { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + return null; + } + Class typeClass = focusContext.getObjectTypeClass(); + return context.getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(typeClass); + } + + public static IterationSpecificationType getIterationSpecification(ObjectTemplateType objectTemplate) { + return objectTemplate != null ? objectTemplate.getIterationSpecification() : null; + } + + public static int determineMaxIterations(IterationSpecificationType iterationSpecType) { + return iterationSpecType != null ? iterationSpecType.getMaxIterations() : 0; + } + + public static String formatIterationToken(LensContext context, + LensElementContext accountContext, IterationSpecificationType iterationType, + int iteration, ExpressionFactory expressionFactory, ExpressionVariables variables, + Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + if (iterationType == null) { + return formatIterationTokenDefault(iteration); + } + ExpressionType tokenExpressionType = iterationType.getTokenExpression(); + if (tokenExpressionType == null) { + return formatIterationTokenDefault(iteration); + } + PrismContext prismContext = context.getPrismContext(); + PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, + DOMUtil.XSD_STRING); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(tokenExpressionType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), "iteration token expression in "+accountContext.getHumanReadableName(), task, result); + + Collection> sources = new ArrayList<>(); + MutablePrismPropertyDefinition inputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, + DOMUtil.XSD_INT); + inputDefinition.setMaxOccurs(1); + PrismProperty input = inputDefinition.instantiate(); + input.addRealValue(iteration); + ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(input); + Source,PrismPropertyDefinition> iterationSource = new Source<>(idi, ExpressionConstants.VAR_ITERATION_QNAME); + sources.add(iterationSource); + + ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(sources , variables, + "iteration token expression in "+accountContext.getHumanReadableName(), task); + PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, task, result); + Collection> outputValues = outputTriple.getNonNegativeValues(); + if (outputValues.isEmpty()) { + return ""; + } + if (outputValues.size() > 1) { + throw new ExpressionEvaluationException("Iteration token expression in "+accountContext.getHumanReadableName()+" returned more than one value ("+outputValues.size()+" values)"); + } + String realValue = outputValues.iterator().next().getValue(); + if (realValue == null) { + return ""; + } + return realValue; + } + + public static String formatIterationTokenDefault(int iteration) { + if (iteration == 0) { + return ""; + } + return Integer.toString(iteration); + } + + public static boolean evaluateIterationCondition(LensContext context, + LensElementContext accountContext, IterationSpecificationType iterationSpecification, + int iteration, String iterationToken, boolean beforeIteration, + ExpressionFactory expressionFactory, ExpressionVariables variables, Task task, OperationResult result) + throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + if (iterationSpecification == null) { + return true; + } + ExpressionType expressionType; + String desc; + if (beforeIteration) { + expressionType = iterationSpecification.getPreIterationCondition(); + desc = "pre-iteration expression in "+accountContext.getHumanReadableName(); + } else { + expressionType = iterationSpecification.getPostIterationCondition(); + desc = "post-iteration expression in "+accountContext.getHumanReadableName(); + } + if (expressionType == null) { + return true; + } + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( + expressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), MiscSchemaUtil.getExpressionProfile(), + desc, task, result); + + variables.put(ExpressionConstants.VAR_ITERATION, iteration, Integer.class); + variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken, String.class); + + ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); + ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); + PrismValueDeltaSetTriple> outputTriple = + ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); + Collection> outputValues = outputTriple.getNonNegativeValues(); + if (outputValues.isEmpty()) { + return false; + } + if (outputValues.size() > 1) { + throw new ExpressionEvaluationException(desc+" returned more than one value ("+outputValues.size()+" values)"); + } + Boolean realValue = outputValues.iterator().next().getValue(); + if (realValue == null) { + return false; + } + return realValue; + + } + + /** + * Used for assignments and similar objects that do not have separate lifecycle. + */ + public static boolean isAssignmentValid(AssignmentHolderType focus, AssignmentType assignment, XMLGregorianCalendar now, + ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { + ObjectReferenceType targetRef = assignment.getTargetRef(); + if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { + // Archetype assignments are always valid, even in non-valid lifecycle states. + // The object cannot lose its (arche)type. + return true; + } + String focusLifecycleState = focus.getLifecycleState(); + + if (!activationComputer.lifecycleHasActiveAssignments(focusLifecycleState, focusStateModel)) { + return false; + } + return isValid(assignment.getLifecycleState(), assignment.getActivation(), now, activationComputer, focusStateModel); + } + + public static Collection getForcedAssignments(LifecycleStateModelType lifecycleModel, String targetLifecycle, + ObjectResolver objectResolver, PrismContext prismContext, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + VirtualAssignmenetSpecification virtualAssignmenetSpecification = LifecycleUtil.getForcedAssignmentSpecification(lifecycleModel, targetLifecycle, prismContext); + + Collection forcedAssignments = new HashSet<>(); + if (virtualAssignmenetSpecification != null) { + + ResultHandler handler = (object, parentResult) -> { + AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(object, prismContext); + return forcedAssignments.add(assignment); + }; + + objectResolver.searchIterative(virtualAssignmenetSpecification.getType(), + prismContext.queryFactory().createQuery(virtualAssignmenetSpecification.getFilter()), null, handler, task, result); + + } + + return forcedAssignments; + } + + public static boolean isFocusValid(AssignmentHolderType focus, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { + if (FocusType.class.isAssignableFrom(focus.getClass())) { + return isValid(focus.getLifecycleState(), ((FocusType) focus).getActivation(), now, activationComputer, focusStateModel); + } + return isValid(focus.getLifecycleState(), null, now, activationComputer, focusStateModel); + } + + private static boolean isValid(String lifecycleState, ActivationType activationType, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { + TimeIntervalStatusType validityStatus = activationComputer.getValidityStatus(activationType, now); + ActivationStatusType effectiveStatus = activationComputer.getEffectiveStatus(lifecycleState, activationType, validityStatus, focusStateModel); + return effectiveStatus == ActivationStatusType.ENABLED; + } + + public static AssignmentPathVariables computeAssignmentPathVariables(AssignmentPathImpl assignmentPath) throws SchemaException { + if (assignmentPath == null || assignmentPath.isEmpty()) { + return null; + } + AssignmentPathVariables vars = new AssignmentPathVariables(); + vars.setAssignmentPath(assignmentPath.clone()); + + Iterator iterator = assignmentPath.getSegments().iterator(); + while (iterator.hasNext()) { + AssignmentPathSegmentImpl segment = iterator.next(); + ItemDeltaItem,PrismContainerDefinition> segmentAssignmentIdi = segment.getAssignmentIdi(); + + ItemDeltaItem,PrismContainerDefinition> magicAssignmentIdi; + // Magic assignment + if (vars.getMagicAssignment() == null) { + magicAssignmentIdi = segmentAssignmentIdi.clone(); + vars.setMagicAssignment(magicAssignmentIdi); + } else { + // Collect extension values from the assignment extension + magicAssignmentIdi = vars.getMagicAssignment(); + mergeExtension(magicAssignmentIdi, segmentAssignmentIdi); + } + + // Collect extension values from the source object extension + ObjectType segmentSource = segment.getSource(); + if (segmentSource != null) { + mergeExtension(magicAssignmentIdi, segmentSource.asPrismObject()); + } + + // immediate assignment (use assignment from previous iteration) + vars.setImmediateAssignment(vars.getThisAssignment()); + + // this assignment + ItemDeltaItem,PrismContainerDefinition> thisAssignment = segmentAssignmentIdi.clone(); + vars.setThisAssignment(thisAssignment); + + if (iterator.hasNext() && segmentSource instanceof AbstractRoleType) { + vars.setImmediateRole((PrismObject) segmentSource.asPrismObject()); + } + } + + AssignmentPathSegmentImpl focusAssignmentSegment = assignmentPath.first(); + vars.setFocusAssignment(focusAssignmentSegment.getAssignmentIdi().clone()); + + // a bit of hack -- TODO reconsider in 3.7 + // objects are already cloned + convertToLegacy(vars.getMagicAssignment()); + convertToLegacy(vars.getThisAssignment()); + convertToLegacy(vars.getFocusAssignment()); + convertToLegacy(vars.getImmediateAssignment()); + + return vars; + } + + private static void convertToLegacy( + ItemDeltaItem, PrismContainerDefinition> idi) { + if (idi == null || idi.getDelta() == null || idi.getSubItemDeltas() != null) { + return; + } + // Legacy approach (when adding/removing assignments) was: itemOld+itemNew = value, delta = null + // This was recently changed, to provide precise information (add = null->itemNew, delete = itemOld->null). + // However, to not break scripts before 3.6 release we provide imitation of old behavior here. + // (Moreover, for magic assignment the delta is not correct anyway.) + if (idi.getDelta().isAdd() || idi.getDelta().isReplace()) { + idi.setItemOld(idi.getItemNew().clone()); + } else { + idi.setItemNew(idi.getItemOld().clone()); + } + idi.setDelta(null); + } + + private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, ItemDeltaItem,PrismContainerDefinition> srcIdi) throws SchemaException { + mergeExtension(destIdi.getItemOld(), srcIdi.getItemOld()); + mergeExtension(destIdi.getItemNew(), srcIdi.getItemNew()); + if (srcIdi.getDelta() != null || srcIdi.getSubItemDeltas() != null) { + throw new UnsupportedOperationException("Merge of IDI with deltas not supported"); + } + } + + private static void mergeExtension(Item,PrismContainerDefinition> dstItem, + Item,PrismContainerDefinition> srcItem) throws SchemaException { + if (srcItem == null || dstItem == null) { + return; + } + PrismContainer srcExtension = ((PrismContainer)srcItem).findContainer(AssignmentType.F_EXTENSION); + mergeExtensionContainers(dstItem, srcExtension); + } + + private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, + PrismObject srcObject) throws SchemaException { + if (srcObject == null) { + return; + } + + PrismContainer srcExtension = srcObject.findContainer(ObjectType.F_EXTENSION); + + mergeExtensionContainers(destIdi.getItemNew(), srcExtension); + mergeExtensionContainers(destIdi.getItemOld(), srcExtension); + } + + private static void mergeExtensionContainers(Item,PrismContainerDefinition> dstItem, PrismContainer srcExtension) throws SchemaException { + if (dstItem == null) { + return; + } + PrismContainer dstContainer = (PrismContainer) dstItem; + if (srcExtension != null && !srcExtension.isEmpty()) { + PrismContainer dstExtensionContainer = dstContainer.findOrCreateContainer(AssignmentType.F_EXTENSION); + PrismContainerValue dstExtensionContainerValue = dstExtensionContainer.getValues().isEmpty() + ? dstExtensionContainer.createNewValue() : dstExtensionContainer.getValue(); + ObjectTypeUtil.mergeExtension(dstExtensionContainerValue, srcExtension.getValue()); + } + } + + public static MappingImpl.Builder addAssignmentPathVariables(MappingImpl.Builder builder, AssignmentPathVariables assignmentPathVariables, PrismContext prismContext) { + ExpressionVariables expressionVariables = new ExpressionVariables(); + ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, expressionVariables, prismContext); + return builder.addVariableDefinitions(expressionVariables); + } + + public static void checkContextSanity(LensContext context, String activityDescription, + OperationResult result) throws SchemaException, PolicyViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + PrismObject focusObjectNew = focusContext.getObjectNew(); + if (focusObjectNew != null) { + PolyStringType namePolyType = focusObjectNew.asObjectable().getName(); + if (namePolyType == null) { + throw new SchemaException("Focus "+focusObjectNew+" does not have a name after "+activityDescription); + } + ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); + checkArchetypePolicy(focusContext, archetypePolicy); + } + } + } + + private static void checkArchetypePolicy(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy) throws SchemaException, PolicyViolationException { + if (archetypePolicy == null) { + return; + } + PrismObject focusObjectNew = focusContext.getObjectNew(); + ObjectDelta focusDelta = focusContext.getDelta(); + + for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { + processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); + } + // Deprecated + for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { + processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); + } + + } + + private static void processItemConstraint(LensFocusContext focusContext, ObjectDelta focusDelta, PrismObject focusObjectNew, ItemConstraintType itemConstraintType) throws PolicyViolationException { + ItemPath itemPath = itemConstraintType.getPath().getItemPath(); + if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { + if (focusDelta != null) { + if (focusDelta.isAdd()) { + PrismProperty propNew = focusObjectNew.findProperty(itemPath); + if (propNew != null) { + // prop delta is OK, but it has to match + if (focusObjectNew.getOid() != null) { + if (!focusObjectNew.getOid().equals(propNew.getRealValue().toString())) { + throw new PolicyViolationException("Cannot set "+itemPath+" to a value different than OID in oid bound mode"); + } + } + } + } else { + PropertyDelta nameDelta = focusDelta.findPropertyDelta(itemPath); + if (nameDelta != null) { + if (nameDelta.isReplace()) { + Collection> valuesToReplace = nameDelta.getValuesToReplace(); + if (valuesToReplace.size() == 1) { + String stringValue = valuesToReplace.iterator().next().getValue().toString(); + if (focusContext.getOid().equals(stringValue)) { + // This is OK. It is most likely a correction made by a recompute. + return; + } + } + } + throw new PolicyViolationException("Cannot change "+itemPath+" in oid bound mode"); + } + } + } + } + + } + + public static PrismContainer createAssignmentSingleValueContainer(@NotNull AssignmentType assignmentType) throws SchemaException { + // Make it appear to be single-value. Therefore paths without segment IDs will work. + return assignmentType.asPrismContainerValue().asSingleValuedContainer(SchemaConstantsGenerated.C_ASSIGNMENT); + } + + public static AssignmentType getAssignmentType(ItemDeltaItem,PrismContainerDefinition> assignmentIdi, boolean old) { + return PrismContainerValue.asContainerable(assignmentIdi.getSingleValue(old)); + } + + + public static String getChannel(LensContext context, Task task) { + if (context != null && context.getChannel() != null){ + return context.getChannel(); + } else if (task.getChannel() != null){ + return task.getChannel(); + } + return null; + } + + public static void setDeltaOldValue(LensElementContext ctx, ItemDelta itemDelta) { + if (itemDelta.getEstimatedOldValues() != null) { + return; + } + if (ctx.getObjectOld() == null) { + return; + } + Item itemOld = ctx.getObjectOld().findItem(itemDelta.getPath()); + if (itemOld != null) { + //noinspection unchecked + itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); + return; + } + // Here we need to distinguish whether the item is missing because it is not filled in (e.g. familyName in MID-4237) + // or because it was not loaded (as for attributes or associations). + if (!isItemLoadable(ctx.getObjectOld(), itemDelta.getPath())) { + itemDelta.setEstimatedOldValues(emptySet()); + return; + } + // get the old data from current object. Still better estimate than nothing + if (ctx.getObjectCurrent() != null) { + itemOld = ctx.getObjectCurrent().findItem(itemDelta.getPath()); + if (itemOld != null) { + //noinspection unchecked + itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); + } + } + } + + // a heuristic by now + private static boolean isItemLoadable(PrismObject object, ItemPath path) { + if (!(object.asObjectable() instanceof ShadowType)) { + return false; + } + return path.startsWithName(ShadowType.F_ATTRIBUTES) || path.startsWithName(ShadowType.F_ASSOCIATION); + } + + public static void setDeltaOldValue(LensElementContext ctx, ObjectDelta objectDelta) { + if (objectDelta == null) { + return; + } + if (!objectDelta.isModify()) { + return; + } + for (ItemDelta modification: objectDelta.getModifications()) { + setDeltaOldValue(ctx, modification); + } + } + + public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta focusDelta, OperationResult result, + LensElementContext focusContext, LensProjectionContext projCtx) { + return createObjectDeltaOperation(focusDelta, result, focusContext, projCtx, null); + } + + // projCtx may or may not be present (object itself can be focus or projection) + public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta objectDelta, OperationResult result, + LensElementContext objectContext, + LensProjectionContext projCtx, + ResourceType resource) { + LensObjectDeltaOperation objectDeltaOp = new LensObjectDeltaOperation<>(objectDelta.clone()); + objectDeltaOp.setExecutionResult(result); + PrismObject object = objectContext.getObjectAny(); + if (object != null) { + PolyString name = object.getName(); + if (name == null && object.asObjectable() instanceof ShadowType) { + try { + name = ShadowUtil.determineShadowName((PrismObject) object); + if (name == null) { + LOGGER.debug("No name for shadow:\n{}", object.debugDump()); + } else if (name.getNorm() == null) { + name.recompute(objectContext.getPrismContext().getDefaultPolyStringNormalizer()); + } + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine name for shadow -- continuing with no name; shadow:\n{}", e, object.debugDump()); + } + } + objectDeltaOp.setObjectName(name); + } + if (resource == null && projCtx != null) { + resource = projCtx.getResource(); + } + if (resource != null) { + objectDeltaOp.setResourceOid(resource.getOid()); + objectDeltaOp.setResourceName(PolyString.toPolyString(resource.getName())); + } else if (objectContext instanceof LensProjectionContext) { + objectDeltaOp.setResourceOid(((LensProjectionContext) objectContext).getResourceOid()); + } + return objectDeltaOp; + } + + public static void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers, + Collection policySituations) { + + LOGGER.debug("Policy rule {} triggered: {}", rule.getName(), triggers); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Policy rule {} triggered:\n{}", rule.getName(), DebugUtil.debugDump(triggers, 1)); + } + + ((EvaluatedPolicyRuleImpl) rule).addTriggers(triggers); + CollectionUtils.addIgnoreNull(policySituations, rule.getPolicySituation()); + } + + public static void processRuleWithException(@NotNull EvaluatedPolicyRule rule, Collection> triggers, + PolicyExceptionType policyException) { + + LOGGER.debug("Policy rule {} would be triggered, but there is an exception for it. Not triggering", rule.getName()); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Policy rule {} would be triggered, but there is an exception for it:\nTriggers:\n{}\nException:\n{}", + rule.getName(), DebugUtil.debugDump(triggers, 1), policyException); + } + ((EvaluatedPolicyRuleImpl)rule).addPolicyException(policyException); + } + + + public static void checkMaxIterations(int iteration, int maxIterations, String conflictMessage, String humanReadableName) + throws ObjectAlreadyExistsException { + if (iteration > maxIterations) { + StringBuilder sb = new StringBuilder(); + if (iteration == 1) { + sb.append("Error processing "); + } else { + sb.append("Too many iterations (").append(iteration).append(") for "); + } + sb.append(humanReadableName); + if (iteration == 1) { + sb.append(": constraint violation: "); + } else { + sb.append(": cannot determine values that satisfy constraints: "); + } + if (conflictMessage != null) { + sb.append(conflictMessage); + } + throw new ObjectAlreadyExistsException(sb.toString()); + } + } + + public static boolean needsFullShadowForCredentialProcessing(LensProjectionContext projCtx) throws SchemaException { + RefinedObjectClassDefinition refinedProjDef = projCtx.getStructuralObjectClassDefinition(); + if (refinedProjDef == null) { + return false; + } + + List outboundMappingType = refinedProjDef.getPasswordOutbound(); + if (outboundMappingType == null) { + return false; + } + for (MappingType mappingType: outboundMappingType) { + if (mappingType.getStrength() == MappingStrengthType.STRONG || mappingType.getStrength() == MappingStrengthType.WEAK) { + return true; + } + } + return false; + } + + public static boolean isPasswordReturnedByDefault(LensProjectionContext projCtx) { + CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), CredentialsCapabilityType.class); + return CapabilityUtil.isPasswordReturnedByDefault(credentialsCapabilityType); + } + + public static boolean evaluateBoolean(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, + task, result, + DOMUtil.XSD_BOOLEAN, false, null); + } + + public static String evaluateString(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, + task, result, + DOMUtil.XSD_STRING, null, null); + } + + public static LocalizableMessageType evaluateLocalizableMessageType(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + Function additionalConvertor = (o) -> { + if (o == null || o instanceof LocalizableMessageType) { + return o; + } else if (o instanceof LocalizableMessage) { + return LocalizationUtil.createLocalizableMessageType((LocalizableMessage) o); + } else { + return new SingleLocalizableMessageType().fallbackMessage(String.valueOf(o)); + } + }; + return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, + task, result, LocalizableMessageType.COMPLEX_TYPE, null, additionalConvertor); + } + + public static T evaluateExpressionSingle(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result, QName typeName, + T defaultValue, Function additionalConvertor) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition( + new QName(SchemaConstants.NS_C, "result"), typeName); + Expression,PrismPropertyDefinition> expression = + expressionFactory.makeExpression(expressionBean, resultDef, MiscSchemaUtil.getExpressionProfile(), contextDescription, task, result); + ExpressionEvaluationContext eeContext = new ExpressionEvaluationContext(null, expressionVariables, contextDescription, task); + eeContext.setAdditionalConvertor(additionalConvertor); + PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, eeContext, task, result); + List results = exprResultTriple.getZeroSet().stream() + .map(ppv -> (T) ppv.getRealValue()) + .collect(Collectors.toList()); + return getSingleValue(results, defaultValue, contextDescription); + } + + @NotNull + public static SingleLocalizableMessageType interpretLocalizableMessageTemplate(LocalizableMessageTemplateType template, + ExpressionVariables var, ExpressionFactory expressionFactory, PrismContext prismContext, + Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException { + SingleLocalizableMessageType rv = new SingleLocalizableMessageType(); + if (template.getKey() != null) { + rv.setKey(template.getKey()); + } else if (template.getKeyExpression() != null) { + rv.setKey(evaluateString(template.getKeyExpression(), var, "localizable message key expression", expressionFactory, prismContext, task, result)); + } + if (!template.getArgument().isEmpty() && !template.getArgumentExpression().isEmpty()) { + throw new IllegalArgumentException("Both argument and argumentExpression items are non empty"); + } else if (!template.getArgumentExpression().isEmpty()) { + for (ExpressionType argumentExpression : template.getArgumentExpression()) { + LocalizableMessageType argument = evaluateLocalizableMessageType(argumentExpression, var, + "localizable message argument expression", expressionFactory, prismContext, task, result); + rv.getArgument().add(new LocalizableMessageArgumentType().localizable(argument)); + } + } else { + // TODO allow localizable messages templates here + rv.getArgument().addAll(template.getArgument()); + } + if (template.getFallbackMessage() != null) { + rv.setFallbackMessage(template.getFallbackMessage()); + } else if (template.getFallbackMessageExpression() != null) { + rv.setFallbackMessage(evaluateString(template.getFallbackMessageExpression(), var, + "localizable message fallback expression", expressionFactory, prismContext, task, result)); + } + return rv; + } + + public static void reclaimSequences(LensContext context, RepositoryService repositoryService, Task task, OperationResult result) throws SchemaException { + if (context == null) { + return; + } + + Map sequenceMap = context.getSequences(); + LOGGER.trace("Context sequence map: {}", sequenceMap); + for (Map.Entry sequenceMapEntry: sequenceMap.entrySet()) { + Collection unusedValues = new ArrayList<>(1); + unusedValues.add(sequenceMapEntry.getValue()); + try { + LOGGER.trace("Returning value {} to sequence {}", sequenceMapEntry.getValue(), sequenceMapEntry.getKey()); + repositoryService.returnUnusedValuesToSequence(sequenceMapEntry.getKey(), unusedValues, result); + } catch (ObjectNotFoundException e) { + LOGGER.error("Cannot return unused value to sequence {}: it does not exist", sequenceMapEntry.getKey(), e); + // ... but otherwise ignore it and go on + } + } + } + + public static void applyObjectPolicyConstraints(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext) throws SchemaException, ConfigurationException { + if (archetypePolicy == null) { + return; + } + + final PrismObject focusNew = focusContext.getObjectNew(); + if (focusNew == null) { + // This is delete. Nothing to do. + return; + } + + for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { + applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); + } + // Deprecated + for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { + applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); + } + } + + private static void applyObjectPolicyItemConstraint(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext, PrismObject focusNew, ItemConstraintType itemConstraintType) throws SchemaException, ConfigurationException { + if (itemConstraintType.getPath() == null) { + LOGGER.error("Invalid configuration. Path is mandatory for property constraint definition in {} defined in system configuration", archetypePolicy); + throw new SchemaException("Invalid configuration. Path is mandatory for property constraint definition in " + archetypePolicy + " defined in system configuration."); + } + ItemPath itemPath = itemConstraintType.getPath().getItemPath(); + if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { + PrismProperty prop = focusNew.findProperty(itemPath); + if (prop == null || prop.isEmpty()) { + String newValue = focusNew.getOid(); + if (newValue == null) { + newValue = OidUtil.generateOid(); + } + LOGGER.trace("Generating new OID-bound value for {}: {}", itemPath, newValue); + PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); + PrismPropertyDefinition propDef = focusDefinition.findPropertyDefinition(itemPath); + if (propDef == null) { + throw new SchemaException("No definition for property "+itemPath+" in "+focusDefinition+" as specified in object policy"); + } + PropertyDelta propDelta = propDef.createEmptyDelta(itemPath); + if (String.class.isAssignableFrom(propDef.getTypeClass())) { + propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(newValue, OriginType.USER_POLICY, null)); + } else if (PolyString.class.isAssignableFrom(propDef.getTypeClass())) { + propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(new PolyString(newValue), OriginType.USER_POLICY, null)); + } else { + throw new SchemaException("Unsupported type "+propDef.getTypeName()+" for property "+itemPath+" in "+focusDefinition+" as specified in object policy, only string and polystring properties are supported for OID-bound mode"); + } + focusContext.swallowToSecondaryDelta(propDelta); + focusContext.recompute(); + } + } + } + + public static LensContext.ExportType getExportType(TraceType trace, OperationResult result) { + return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.MINIMAL; + } + + public static LensContext.ExportType getExportTypeTraceOrReduced(TraceType trace, OperationResult result) { + return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.REDUCED; + } + + public static ItemDelta getAprioriItemDelta(ObjectDelta focusDelta, ItemPath itemPath) { + return focusDelta != null ? focusDelta.findItemDelta(itemPath) : null; + } + + public static String determineExplicitArchetypeOid(PrismObject object) { + String explicitArchetypeOid = null; + // Used in cases where archetype assignment haven't had the change to be processed yet. + // E.g. in case that we are creating a new object with archetype assignment + if (object.canRepresent(AssignmentHolderType.class)) { + AssignmentHolderType assignmentHolderType = (AssignmentHolderType)object.asObjectable(); + List archetypeRefs = assignmentHolderType.getArchetypeRef(); + if (archetypeRefs.isEmpty()) { + explicitArchetypeOid = determineExplicitArchetypeOidFromAssignments(object); + } + } + return explicitArchetypeOid; + } + + public static String determineExplicitArchetypeOidFromAssignments(PrismObject object) { + String explicitArchetypeOid = null; + if (object.canRepresent(AssignmentHolderType.class)) { + for (AssignmentType assignment : ((AssignmentHolderType)object.asObjectable()).getAssignment()) { + ObjectReferenceType targetRef = assignment.getTargetRef(); + if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { + explicitArchetypeOid = targetRef.getOid(); + } + } + } + return explicitArchetypeOid; + } +} 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 06af7be080a..7afdf41d776 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 @@ -6,7 +6,10 @@ */ package com.evolveum.midpoint.model.impl.lens.projector; +import com.evolveum.midpoint.model.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; @@ -14,10 +17,6 @@ import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.api.expr.MidpointFunctions; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.api.context.SynchronizationIntent; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; @@ -50,6 +49,7 @@ import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationValidityCapabilityType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -68,7 +68,8 @@ * @author Radovan Semancik */ @Component -public class ActivationProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class ActivationProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ActivationProcessor.class); @@ -77,15 +78,47 @@ public class ActivationProcessor { 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"); + private static final String OP_ACTIVATION = Projector.class.getName() + ".activation"; // for historical reasons + @Autowired private ContextLoader contextLoader; @Autowired private PrismContext prismContext; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private MidpointFunctions midpointFunctions; + @Autowired private ClockworkMedic medic; private PrismObjectDefinition userDefinition; private PrismContainerDefinition activationDefinition; - public void processActivation(LensContext context, + // not a "medic-managed" entry point + void processActivationForAllResources(LensContext context, String activityDescription, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, + SecurityViolationException { + OperationResult activationResult = result.subresult(OP_ACTIVATION) + .setMinor() + .build(); + try { + LOGGER.trace("Processing activation for all contexts"); + for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + if (projectionContext.getSynchronizationPolicyDecision() != SynchronizationPolicyDecision.BROKEN + && projectionContext.getSynchronizationPolicyDecision() != SynchronizationPolicyDecision.IGNORE) { + processActivation(context, projectionContext, now, task, activationResult); + projectionContext.recompute(); + } + } + context.removeIgnoredContexts(); + medic.traceContext(LOGGER, activityDescription, "projection activation of all resources", true, + context, true); + context.checkConsistenceIfNeeded(); + } catch (Throwable t) { + activationResult.recordFatalError(t); + throw t; + } finally { + activationResult.computeStatusIfUnknown(); + } + } + + private void processActivation(LensContext context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { LensFocusContext focusContext = context.getFocusContext(); @@ -844,52 +877,27 @@ private ItemDeltaItem,PrismPr PrismProperty existsPropOld = existsProp.clone(); existsPropOld.setRealValue(existsOld); PropertyDelta existsDelta = existsPropOld.createDelta(); + //noinspection unchecked existsDelta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(existsNew)); return new ItemDeltaItem<>(existsPropOld, existsDelta, existsProp, existsDef); } } - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void processLifecycle(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal object. - LOGGER.trace("Skipping lifecycle evaluation because focus is not FocusType"); - return; - } - - processLifecycleFocus((LensContext)context, projCtx, now, task, result); - } - - private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no focus"); - return; - } - - ResourceObjectLifecycleDefinitionType lifecycleDef = null; - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType != null) { - lifecycleDef = resourceAccountDefType.getLifecycle(); - } - ResourceBidirectionalMappingType lifecycleStateMappingType = null; - if (lifecycleDef != null) { - lifecycleStateMappingType = lifecycleDef.getLifecycleState(); - } + @ProcessorMethod + void processLifecycle(LensContext context, LensProjectionContext projCtx, + @SuppressWarnings("unused") String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, + CommunicationException, ConfigurationException, SecurityViolationException { - if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { + ResourceBidirectionalMappingType lifecycleStateMapping = getLifecycleStateMapping(projCtx); + if (lifecycleStateMapping == null || lifecycleStateMapping.getOutbound() == null) { if (!projCtx.isAdd()) { - LOGGER.trace("Skipping lifecycle evaluation because this is not add operation (default expression)"); + LOGGER.trace("Skipping lifecycle evaluation because this is not an add operation (default expression)"); return; } - PrismObject focusNew = focusContext.getObjectNew(); + PrismObject focusNew = context.getFocusContext().getObjectNew(); if (focusNew == null) { LOGGER.trace("Skipping lifecycle evaluation because there is no new focus (default expression)"); return; @@ -911,18 +919,40 @@ private void processLifecycleFocus(LensContext context, PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); PrismPropertyValue pval = prismContext.itemFactory().createPropertyValue(lifecycle); pval.setOriginType(OriginType.OUTBOUND); + //noinspection unchecked lifeCycleDelta.setValuesToReplace(pval); projCtx.swallowToSecondaryDelta(lifeCycleDelta); } } else { - LOGGER.trace("Computing projection lifecycle (mapping): {}", lifecycleStateMappingType); - evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, + LOGGER.trace("Computing projection lifecycle (using mapping): {}", lifecycleStateMapping); + evaluateActivationMapping(context, projCtx, lifecycleStateMapping, SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, null, now, MappingTimeEval.CURRENT, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); } + context.checkConsistenceIfNeeded(); + projCtx.recompute(); + context.checkConsistenceIfNeeded(); + } + + @Nullable + private ResourceBidirectionalMappingType getLifecycleStateMapping(LensProjectionContext projCtx) { + ResourceObjectLifecycleDefinitionType lifecycleDef; + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType != null) { + lifecycleDef = resourceAccountDefType.getLifecycle(); + } else { + lifecycleDef = null; + } + ResourceBidirectionalMappingType lifecycleStateMapping; + if (lifecycleDef != null) { + lifecycleStateMapping = lifecycleDef.getLifecycleState(); + } else { + lifecycleStateMapping = null; + } + return lifecycleStateMapping; } private PrismObjectDefinition getUserDefinition() { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java new file mode 100644 index 00000000000..d3754061362 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 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; + +/** + * Names of projector/clockwork components invoked by medic.partialExecute method calls. + */ +@SuppressWarnings("WeakerAccess") +public class Components { + + public static final String LOAD = "load"; + public static final String FOCUS = "focus"; + public static final String INBOUND = "inbound"; + public static final String FOCUS_ACTIVATION = "focusActivation"; + public static final String OBJECT_TEMPLATE_BEFORE_ASSIGNMENTS = "objectTemplateBeforeAssignments"; + public static final String ASSIGNMENTS = "assignments"; + public static final String ASSIGNMENTS_ORG = "assignmentsOrg"; + public static final String ASSIGNMENTS_MEMBERSHIP_AND_DELEGATE = "assignmentsMembershipAndDelegate"; + public static final String ASSIGNMENTS_CONFLICTS = "assignmentsConflicts"; + public static final String FOCUS_LIFECYCLE = "focusLifecycle"; + public static final String OBJECT_TEMPLATE_AFTER_ASSIGNMENTS = "objectTemplateAfterAssignments"; + public static final String FOCUS_CREDENTIALS = "focusCredentials"; + public static final String FOCUS_POLICY_RULES = "focusPolicyRules"; + public static final String EXECUTION = "execution"; + public static final String PROJECTION = "projection"; + public static final String PROJECTION_VALUES = "projectionValues"; + public static final String PROJECTION_CREDENTIALS = "projectionCredentials"; + public static final String PROJECTION_RECONCILIATION = "projectionReconciliation"; + public static final String PROJECTION_VALUES_POST_RECON = "projectionValuesPostRecon"; + public static final String PROJECTION_LIFECYCLE = "projectionLifecycle"; +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java index 5d90b7828ef..fcb7f286447 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java @@ -75,14 +75,9 @@ public class ConsolidationProcessor { private PrismContainerDefinition associationDefinition; - @Autowired - private ContextLoader contextLoader; - - @Autowired - private MatchingRuleRegistry matchingRuleRegistry; - - @Autowired - PrismContext prismContext; + @Autowired private ContextLoader contextLoader; + @Autowired private MatchingRuleRegistry matchingRuleRegistry; + @Autowired private PrismContext prismContext; /** * Converts delta set triples to a secondary account deltas. @@ -104,14 +99,16 @@ void consolidateValues(LensContext context, LensProject SynchronizationPolicyDecision policyDecision = accCtx.getSynchronizationPolicyDecision(); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); if (policyDecision == SynchronizationPolicyDecision.DELETE) { // Nothing to do } else { // This is ADD, KEEP, UNLINK or null. All are in fact the same as KEEP consolidateValuesModifyProjection(context, accCtx, task, result); + context.checkConsistenceIfNeeded(); } - if (consistencyChecks) context.checkConsistence(); + context.recompute(); + context.checkConsistenceIfNeeded(); } catch (Throwable t) { result.recordFatalError(t.getMessage(), t); throw t; 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 74b04ed4b4e..9bf264c1d89 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 @@ -14,6 +14,8 @@ import java.util.Iterator; import java.util.List; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.schema.*; @@ -65,6 +67,8 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import javax.xml.datatype.XMLGregorianCalendar; + /** * 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 @@ -74,7 +78,8 @@ * */ @Component -public class ContextLoader { +@ProcessorExecution() +public class ContextLoader implements ProjectorProcessor { @Autowired @Qualifier("cacheRepositoryService") @@ -93,13 +98,12 @@ public class ContextLoader { 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) + @ProcessorMethod + void load(LensContext context, String activityDescription, + @SuppressWarnings("unused") XMLGregorianCalendar now, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - context.checkAbortRequested(); - context.recompute(); OperationResult result = parentResult.createMinorSubresult(OPERATION_LOAD); @@ -121,7 +125,7 @@ public void load(LensContext context, String activityD preprocessProjectionContext(context, projectionContext, task, result); } - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); determineFocusContext(context, task, result); @@ -163,7 +167,7 @@ public void load(LensContext context, String activityD removeRottenContexts(context); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); for (LensProjectionContext projectionContext: context.getProjectionContexts()) { context.checkAbortRequested(); @@ -179,14 +183,17 @@ public void load(LensContext context, String activityD projectionResult.computeStatus(); } - if (consistencyChecks) context.checkConsistence(); - + context.checkConsistenceIfNeeded(); context.recompute(); if (consistencyChecks) { fullCheckConsistence(context); } + // Set the "fresh" mark now so following consistency check will be stricter + context.setFresh(true); + context.checkConsistenceIfNeeded(); + medic.traceContext(LOGGER, activityDescription, "after load", false, context, false); result.computeStatusComposite(); @@ -250,8 +257,10 @@ private void removeRottenContexts(LensContext context) /** * 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 { + 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); } @@ -492,7 +501,7 @@ private void loadFromSystemConfig(LensContext context, if (context.getFocusTemplate() == null) { // TODO is the nullity check needed here? - setFocusTemplate(context, result); + updateFocusTemplate(context, result); } if (context.getAccountSynchronizationSettings() == null) { @@ -555,7 +564,7 @@ public void updateArchetypePolicy(LensContext context, } // expects that object policy configuration is already set in focusContext - public void setFocusTemplate(LensContext context, OperationResult result) + public void updateFocusTemplate(LensContext context, OperationResult result) throws ObjectNotFoundException, SchemaException { // 1. When this method is called after inbound processing, we might want to change the existing template @@ -616,17 +625,17 @@ private void loadLinkRefs(LensContext context, Task tas LOGGER.trace("loadLinkRefsFromFocus done"); } - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); loadLinkRefsFromDelta(context, focusCurrent, focusContext, task, result); LOGGER.trace("loadLinkRefsFromDelta done"); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); loadProjectionContextsSync(context, task, result); LOGGER.trace("loadProjectionContextsSync done"); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); } /** diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java index 0b185b30b96..b7f4e644459 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java @@ -12,7 +12,6 @@ import java.util.List; import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.PointInTimeType; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; @@ -22,7 +21,6 @@ import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; @@ -54,9 +52,6 @@ public class DependencyProcessor { @Autowired private TaskManager taskManager; - public void resetWaves(LensContext context) throws PolicyViolationException { - } - public void sortProjectionsToWaves(LensContext context) throws PolicyViolationException { // Create a snapshot of the projection collection at the beginning of computation. // The collection may be changed during computation (projections may be added). We do not want to process @@ -89,16 +84,6 @@ public void sortProjectionsToWaves(LensContext context } } - public int computeMaxWaves(LensContext context) { - if (context.getPartialProcessingOptions().getInbound() != PartialProcessingTypeType.SKIP) { - // Let's do one extra wave with no accounts in it. This time we expect to get the results of the execution to the user - // via inbound, e.g. identifiers generated by the resource, DNs and similar things. Hence the +2 instead of +1 - return context.getMaxWave() + 2; - } else { - return context.getMaxWave() + 1; - } - } - private LensProjectionContext determineProjectionWave(LensContext context, LensProjectionContext projectionContext, ResourceObjectTypeDependencyType inDependency, List depPath) throws PolicyViolationException { if (!projectionContext.isWaveIncomplete()) { @@ -207,18 +192,16 @@ private LensProjectionContext determineProjectionWaveProv } private String getResourceNameFromRef(ResourceShadowDiscriminator refDiscr) { - String name = null; try { Task task = taskManager.createTaskInstance("Load resource"); GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); Collection> options = SelectorOptions.createCollection(rootOpts); PrismObject resource = provisioningService.getObject(ResourceType.class, refDiscr.getResourceOid(), options, task, task.getResult()); - name = resource.getName().getOrig(); + return resource.getName().getOrig(); } catch (Exception e) { //ignoring exception and return null return null; } - return name; } private LensProjectionContext determineProjectionWaveDeprovision(LensContext context, @@ -305,7 +288,7 @@ private LensProjectionContext determineProjectionWaveDepr } private Collection findReverseDependecies(LensContext context, - LensProjectionContext targetProjectionContext) throws PolicyViolationException { + LensProjectionContext targetProjectionContext) { Collection deps = new ArrayList<>(); for (LensProjectionContext projectionContext: context.getProjectionContexts()) { for (ResourceObjectTypeDependencyType dependency: projectionContext.getDependencies()) { @@ -391,9 +374,7 @@ private LensProjectionContext findDependencyTargetContext // } private LensProjectionContext createAnotherContext(LensContext context, LensProjectionContext origProjectionContext, - ResourceShadowDiscriminator discr) throws PolicyViolationException { - - + ResourceShadowDiscriminator discr) { LensProjectionContext otherCtx = context.createProjectionContext(discr); otherCtx.setResource(origProjectionContext.getResource()); // Force recon for the new context. This is a primitive way how to avoid phantom changes. @@ -402,19 +383,18 @@ private LensProjectionContext createAnotherContext(LensCo } private LensProjectionContext createAnotherContext(LensContext context, LensProjectionContext origProjectionContext, - int determinedOrder) throws PolicyViolationException { + int determinedOrder) { ResourceShadowDiscriminator origDiscr = origProjectionContext.getResourceShadowDiscriminator(); ResourceShadowDiscriminator discr = new ResourceShadowDiscriminator(origDiscr.getResourceOid(), origDiscr.getKind(), origDiscr.getIntent(), origDiscr.getTag(), origDiscr.isTombstone()); discr.setOrder(determinedOrder); - LensProjectionContext otherCtx = createAnotherContext(context, origProjectionContext, discr); - return otherCtx; + return createAnotherContext(context, origProjectionContext, discr); } /** - * Check that the dependencies are still satisfied. Also check for high-ordes vs low-order operation consistency + * Check that the dependencies are still satisfied. Also check for high-orders vs low-order operation consistency * and stuff like that. */ - public boolean checkDependencies(LensContext context, + boolean checkDependencies(LensContext context, LensProjectionContext projContext, OperationResult result) throws PolicyViolationException { if (projContext.isDelete()) { // It is OK if we depend on something that is not there if we are being removed ... for now @@ -503,15 +483,15 @@ public boolean checkDependencies(LensContext context, return true; } - public void preprocessDependencies(LensContext context){ + void preprocessDependencies(LensContext context){ //in the first wave we do not have enough information to preprocess contexts - if (context.getExecutionWave() == 0){ + if (context.getExecutionWave() == 0) { return; } - for (LensProjectionContext projContext : context.getProjectionContexts()){ - if (!projContext.isCanProject()){ + for (LensProjectionContext projContext : context.getProjectionContexts()) { + if (!projContext.isCanProject()) { continue; } @@ -521,16 +501,13 @@ public void preprocessDependencies(LensContext context LOGGER.trace("LOOKING FOR {}", refRat); LensProjectionContext dependencyAccountContext = context.findProjectionContext(refRat); ResourceObjectTypeDependencyStrictnessType strictness = ResourceTypeUtil.getDependencyStrictness(dependency); - if (dependencyAccountContext != null){ - if (!dependencyAccountContext.isCanProject()){ - continue; - } + if (dependencyAccountContext != null && dependencyAccountContext.isCanProject()) { // We have the context of the object that we depend on. We need to check if it was provisioned. if (strictness == ResourceObjectTypeDependencyStrictnessType.STRICT || strictness == ResourceObjectTypeDependencyStrictnessType.RELAXED) { if (wasExecuted(dependencyAccountContext)) { // everything OK - if (ResourceTypeUtil.isForceLoadDependentShadow(dependency) && !dependencyAccountContext.isDelete()){ + if (ResourceTypeUtil.isForceLoadDependentShadow(dependency) && !dependencyAccountContext.isDelete()) { dependencyAccountContext.setDoReconciliation(true); projContext.setDoReconciliation(true); } @@ -539,14 +516,18 @@ public void preprocessDependencies(LensContext context } } } - } /** - * Finally checks for all the dependencies. Some dependencies cannot be checked during wave computations as - * we might not have all activation decisions yet. + * Original comment (since 2014): + * Finally checks for all the dependencies. Some dependencies cannot be checked during wave computations as + * we might not have all activation decisions yet. + * + * However, for almost five years this method is called at end of each projection wave, i.e. not + * only at the real end. (With the exception of previewChanges regime.) So let's keep executing + * it in this way in both normal + preview modes. */ - public void checkDependenciesFinal(LensContext context, OperationResult result) throws PolicyViolationException { + void checkDependenciesFinal(LensContext context, OperationResult result) throws PolicyViolationException { for (LensProjectionContext accountContext: context.getProjectionContexts()) { checkDependencies(context, accountContext, result); @@ -585,7 +566,7 @@ public void checkDependenciesFinal(LensContext context } } - private boolean wasProvisioned(LensProjectionContext projectionContext, int executionWave) { + private boolean wasProvisioned(LensProjectionContext projectionContext, int executionWave) { int accountWave = projectionContext.getWave(); if (accountWave >= executionWave) { // This had no chance to be provisioned yet, so we assume it will be provisioned @@ -644,7 +625,7 @@ private boolean wasExecuted(LensProjectionContext accountContext){ } List> executedDeltas = accountContext.getExecutedDeltas(); - if (executedDeltas == null || executedDeltas.isEmpty()) { + if (executedDeltas.isEmpty()) { return false; } } 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 b3da95829bd..7d6de084c61 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 @@ -170,6 +170,9 @@ void processOutbound(LensContext context, LensProjectio if (nextRecompute != null) { nextRecompute.createTrigger(context.getFocusContext()); } + + context.recompute(); + context.checkConsistenceIfNeeded(); } // TODO: unify with MappingEvaluator.evaluateOutboundMapping(...) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java index f32125be3af..d02b1adbc56 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java @@ -6,17 +6,18 @@ */ package com.evolveum.midpoint.model.impl.lens.projector; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; - import java.util.Collection; import java.util.Iterator; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.model.impl.sync.SynchronizationService; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -27,11 +28,9 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentProcessor; -import com.evolveum.midpoint.model.impl.sync.SynchronizationExpressionsEvaluator; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; @@ -59,6 +58,10 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import javax.xml.datatype.XMLGregorianCalendar; + +import static java.util.Objects.requireNonNull; + /** * Processor that determines values of account attributes. It does so by taking the pre-processed information left * behind by the assignment processor. It also does some checks, such as check of identifier uniqueness. It tries to @@ -67,63 +70,38 @@ * @author Radovan Semancik */ @Component -public class ProjectionValuesProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class ProjectionValuesProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ProjectionValuesProcessor.class); - @Autowired - private OutboundProcessor outboundProcessor; - - @Autowired - private ConsolidationProcessor consolidationProcessor; - - @Autowired - private AssignmentProcessor assignmentProcessor; - - @Autowired - @Qualifier("cacheRepositoryService") - RepositoryService repositoryService; - - @Autowired - private ExpressionFactory expressionFactory; - - @Autowired - private PrismContext prismContext; - - @Autowired - private SynchronizationExpressionsEvaluator correlationConfirmationEvaluator; - - @Autowired - private SynchronizationService synchronizationService; - - @Autowired - private ContextLoader contextLoader; - - @Autowired - private ProvisioningService provisioningService; - - public void process(LensContext context, - LensProjectionContext projectionContext, String activityDescription, Task task, OperationResult result) + @Autowired private OutboundProcessor outboundProcessor; + @Autowired private ConsolidationProcessor consolidationProcessor; + @Autowired private AssignmentProcessor assignmentProcessor; + @Autowired @Qualifier("cacheRepositoryService") RepositoryService repositoryService; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private PrismContext prismContext; + @Autowired private SynchronizationService synchronizationService; + @Autowired private ContextLoader contextLoader; + @Autowired private ProvisioningService provisioningService; + + @ProcessorMethod + public void process(LensContext context, LensProjectionContext projectionContext, + String activityDescription, @SuppressWarnings("unused") XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focus types. - return; - } - processProjections((LensContext) context, projectionContext, - activityDescription, task, result); + processProjectionValues(context, projectionContext, activityDescription, task, result); + context.checkConsistenceIfNeeded(); + projectionContext.recompute(); + context.checkConsistenceIfNeeded(); } - private void processProjections(LensContext context, + private void processProjectionValues(LensContext context, LensProjectionContext projContext, String activityDescription, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - checkSchemaAndPolicies(context, projContext, activityDescription, result); + checkSchemaAndPolicies(projContext, activityDescription); SynchronizationPolicyDecision policyDecision = projContext.getSynchronizationPolicyDecision(); if (policyDecision == SynchronizationPolicyDecision.UNLINK) { @@ -135,7 +113,7 @@ private void processProjections(LensContext context, return; } - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); if (!projContext.hasFullShadow() && hasIterationExpression(projContext)) { contextLoader.loadFullShadow(context, projContext, "iteration expression", task, result); @@ -171,7 +149,7 @@ private void processProjections(LensContext context, // These are normally null. But there may be leftover from the previous iteration. // While that should not affect the algorithm (it should overwrite it) it may confuse - // people during debugging and unecessarily clutter the debug output. + // people during debugging and unnecessarily clutter the debug output. projContext.setOutboundConstruction(null); projContext.setSqueezedAttributes(null); projContext.setSqueezedAssociations(null); @@ -179,8 +157,6 @@ private void processProjections(LensContext context, LOGGER.trace("Projection values iteration {}, token '{}' for {}", iteration, iterationToken, projContext.getHumanReadableName()); -// LensUtil.traceContext(LOGGER, activityDescription, "values (start)", false, context, true); - if (!evaluateIterationCondition(context, projContext, iteration, iterationToken, true, task, result)) { conflictMessage = "pre-iteration condition was false"; @@ -188,34 +164,23 @@ private void processProjections(LensContext context, iteration, iterationToken, projContext.getHumanReadableName()); } else { - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); // Re-evaluates the values in the account constructions (including roles) assignmentProcessor.processAssignmentsAccountValues(projContext, result); context.recompute(); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); // policyRuleProcessor.evaluateShadowPolicyRules(context, projContext, activityDescription, task, result); -// LensUtil.traceContext(LOGGER, activityDescription, "values (assignment account values)", false, context, true); - // Evaluates the values in outbound mappings outboundProcessor.processOutbound(context, projContext, task, result); - context.recompute(); - if (consistencyChecks) context.checkConsistence(); - -// LensUtil.traceContext(LOGGER, activityDescription, "values (outbound)", false, context, true); - // Merges the values together, processing exclusions and strong/weak mappings are needed consolidationProcessor.consolidateValues(context, projContext, task, result); - if (consistencyChecks) context.checkConsistence(); - context.recompute(); - if (consistencyChecks) context.checkConsistence(); - // Aux object classes may have changed during consolidation. Make sure we have up-to-date definitions. context.refreshAuxiliaryObjectClassDefinitions(); @@ -229,14 +194,10 @@ private void processProjections(LensContext context, iterationToken = null; cleanupContext(projContext, null); LOGGER.trace("Resetting iteration counter and token because we have rename"); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); continue; } - // Too noisy for now -// LensUtil.traceContext(LOGGER, activityDescription, "values (consolidation)", false, context, true); - - if (policyDecision == SynchronizationPolicyDecision.DELETE) { // No need to play the iterative game if the account is deleted break; @@ -418,11 +379,11 @@ private void processProjections(LensContext context, LensUtil.checkMaxIterations(iteration, maxIterations, conflictMessage, projContext.getHumanReadableName()); cleanupContext(projContext, null); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); } addIterationTokenDeltas(projContext); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); } private boolean willResetIterationCounter(LensProjectionContext projectionContext) throws SchemaException { @@ -446,8 +407,7 @@ private boolean willResetIterationCounter(LensProjectionContext projectionContex return false; } - - + @SuppressWarnings("RedundantIfStatement") private boolean hasIterationExpression(LensProjectionContext accountContext) { ResourceObjectTypeDefinitionType accDef = accountContext.getResourceObjectTypeDefinitionType(); if (accDef == null) { @@ -517,8 +477,8 @@ private boolean evaluateIterationCondition(LensContext * Check that the primary deltas do not violate schema and policies * TODO: implement schema check */ - public void checkSchemaAndPolicies(LensContext context, - LensProjectionContext accountContext, String activityDescription, OperationResult result) throws SchemaException, PolicyViolationException { + private void checkSchemaAndPolicies(LensProjectionContext accountContext, String activityDescription) + throws SchemaException, PolicyViolationException { ObjectDelta primaryDelta = accountContext.getPrimaryDelta(); if (primaryDelta == null || primaryDelta.isDelete()) { return; @@ -535,7 +495,7 @@ public void checkSchemaAndPolicies(LensContext context ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(accountToAdd); if (attributesContainer != null) { for (ResourceAttribute attribute: attributesContainer.getAttributes()) { - RefinedAttributeDefinition rAttrDef = rAccountDef.findAttributeDefinition(attribute.getElementName()); + RefinedAttributeDefinition rAttrDef = requireNonNull(rAccountDef.findAttributeDefinition(attribute.getElementName())); if (!rAttrDef.isTolerant()) { throw new PolicyViolationException("Attempt to add object with non-tolerant attribute "+attribute.getElementName()+" in "+ "account "+accountContext.getResourceShadowDiscriminator()+" during "+activityDescription); @@ -546,7 +506,7 @@ public void checkSchemaAndPolicies(LensContext context for(ItemDelta modification: primaryDelta.getModifications()) { if (modification.getParentPath().equivalent(SchemaConstants.PATH_ATTRIBUTES)) { PropertyDelta attrDelta = (PropertyDelta) modification; - RefinedAttributeDefinition rAttrDef = rAccountDef.findAttributeDefinition(attrDelta.getElementName()); + RefinedAttributeDefinition rAttrDef = requireNonNull(rAccountDef.findAttributeDefinition(attrDelta.getElementName())); if (!rAttrDef.isTolerant()) { throw new PolicyViolationException("Attempt to modify non-tolerant attribute "+attrDelta.getElementName()+" in "+ "account "+accountContext.getResourceShadowDiscriminator()+" during "+activityDescription); @@ -563,42 +523,40 @@ public void checkSchemaAndPolicies(LensContext context */ private void cleanupContext(LensProjectionContext accountContext, PrismObject fullConflictingShadow) throws SchemaException { // We must NOT clean up activation computation here. This has happened before, it will not happen again - // and it does not depend on iteration. But, in fact we want to cleaup up activation changes if they + // and it does not depend on iteration. But, in fact we want to cleanup up activation changes if they // are already applied to the new shadow. ObjectDelta secondaryDelta = accountContext.getSecondaryDelta(); if (secondaryDelta != null) { boolean administrativeStatusDeltaRemoved = false; Collection modifications = secondaryDelta.getModifications(); - if (modifications != null) { - Iterator iterator = modifications.iterator(); - while (iterator.hasNext()) { - ItemDelta modification = iterator.next(); - if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { - if (fullConflictingShadow != null) { - if (QNameUtil.match(ActivationType.F_ADMINISTRATIVE_STATUS, modification.getElementName())) { - if (modification.isRedundant(fullConflictingShadow, false)) { - LOGGER.trace("Removing redundant secondary activation delta: {}", modification); - iterator.remove(); - } - administrativeStatusDeltaRemoved = true; + Iterator iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta modification = iterator.next(); + if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { + if (fullConflictingShadow != null) { + if (QNameUtil.match(ActivationType.F_ADMINISTRATIVE_STATUS, modification.getElementName())) { + if (modification.isRedundant(fullConflictingShadow, false)) { + LOGGER.trace("Removing redundant secondary activation delta: {}", modification); + iterator.remove(); } + administrativeStatusDeltaRemoved = true; } - } else { - iterator.remove(); } + } else { + iterator.remove(); } - if (administrativeStatusDeltaRemoved) { - iterator = modifications.iterator(); - while (iterator.hasNext()) { - ItemDelta modification = iterator.next(); - if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { - if (QNameUtil.match(ActivationType.F_ENABLE_TIMESTAMP, modification.getElementName()) || - QNameUtil.match(ActivationType.F_DISABLE_TIMESTAMP, modification.getElementName()) || - QNameUtil.match(ActivationType.F_DISABLE_REASON, modification.getElementName()) || - QNameUtil.match(ActivationType.F_ARCHIVE_TIMESTAMP, modification.getElementName())) { - LOGGER.trace("Removing secondary activation delta because redundant delta was removed before: {}", modification); - iterator.remove(); - } + } + if (administrativeStatusDeltaRemoved) { + iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta modification = iterator.next(); + if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { + if (QNameUtil.match(ActivationType.F_ENABLE_TIMESTAMP, modification.getElementName()) || + QNameUtil.match(ActivationType.F_DISABLE_TIMESTAMP, modification.getElementName()) || + QNameUtil.match(ActivationType.F_DISABLE_REASON, modification.getElementName()) || + QNameUtil.match(ActivationType.F_ARCHIVE_TIMESTAMP, modification.getElementName())) { + LOGGER.trace("Removing secondary activation delta because redundant delta was removed before: {}", modification); + iterator.remove(); } } } @@ -611,7 +569,6 @@ private void cleanupContext(LensProjectionContext accountContext, PrismObject void processPostRecon(LensContext context, - LensProjectionContext projectionContext, String activityDescription, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, - CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focus types. - return; - } - processProjectionsPostRecon((LensContext) context, projectionContext, - activityDescription, task, result); - } - - private void processProjectionsPostRecon(LensContext context, - LensProjectionContext projContext, String activityDescription, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, - CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - + @ProcessorMethod + void processPostRecon(LensContext context, LensProjectionContext projContext, + @SuppressWarnings("unused") String activityDescription, @SuppressWarnings("unused") XMLGregorianCalendar now, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, PolicyViolationException { SynchronizationPolicyDecision policyDecision = projContext.getSynchronizationPolicyDecision(); if (policyDecision == SynchronizationPolicyDecision.UNLINK) { // We will not update accounts that are being unlinked. @@ -675,7 +615,8 @@ private void processProjectionsPostRecon(LensContext co } consolidationProcessor.consolidateValuesPostRecon(context, projContext, task, result); - + context.checkConsistenceIfNeeded(); + projContext.recompute(); + context.checkConsistenceIfNeeded(); } - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java index b8f7503ec21..af656b169ad 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java @@ -9,13 +9,11 @@ import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.PROJECTOR; import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; - -import java.util.List; import javax.xml.datatype.XMLGregorianCalendar; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectorRunTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -25,14 +23,11 @@ import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.lens.projector.credentials.ProjectionCredentialsProcessor; import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentHolderProcessor; -import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentProcessor; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.repo.api.PreconditionViolationException; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.internals.InternalCounters; import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.result.OperationResult; @@ -48,9 +43,6 @@ import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingOptionsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingTypeType; /** * Projector recomputes the context. It takes the context with a few basic data as input. It uses all the policies @@ -73,8 +65,7 @@ public class Projector { private static final String OPERATION_PROJECT_PROJECTION = Projector.class.getName() + ".projectProjection"; @Autowired private ContextLoader contextLoader; - @Autowired private AssignmentHolderProcessor focusProcessor; - @Autowired private AssignmentProcessor assignmentProcessor; + @Autowired private AssignmentHolderProcessor assignmentHolderProcessor; @Autowired private ProjectionValuesProcessor projectionValuesProcessor; @Autowired private ReconciliationProcessor reconciliationProcessor; @Autowired private ProjectionCredentialsProcessor projectionCredentialsProcessor; @@ -92,26 +83,21 @@ public void project(LensContext context, String activi OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - projectInternal(context, activityDescription, true, false, task, parentResult); + context.normalize(); + context.resetProjectionWave(); + projectInternal(context, activityDescription, true, task, parentResult); } /** * Resumes projection at current projection wave. */ - public void resume(LensContext context, String activityDescription, - Task task, OperationResult parentResult) + public void resume(LensContext context, String activityDescription, Task task, + OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - - if (context.getProjectionWave() != context.getExecutionWave()) { - throw new IllegalStateException("Projector.resume called in illegal wave state: execution wave = " + context.getExecutionWave() + - ", projection wave = " + context.getProjectionWave()); - } - if (!context.isFresh()) { - throw new IllegalStateException("Projector.resume called on non-fresh context"); - } - - projectInternal(context, activityDescription, false, false, task, parentResult); + assert context.getProjectionWave() == context.getExecutionWave(); + assert context.isFresh(); + projectInternal(context, activityDescription, false, task, parentResult); } /** @@ -122,11 +108,17 @@ public void projectAllWaves(LensContext context, Strin Task task, OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - projectInternal(context, activityDescription, true, true, task, parentResult); + assert context.getProjectionWave() == 0; + assert context.getExecutionWave() == 0; + context.normalize(); + while (context.getProjectionWave() < context.computeMaxWaves()) { + boolean fromStart = context.getProjectionWave() == 0; + projectInternal(context, activityDescription, fromStart, task, parentResult); + } } private void projectInternal(LensContext context, String activityDescription, - boolean fromStart, boolean allWaves, Task task, OperationResult parentResult) + boolean fromStart, Task task, OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { @@ -146,20 +138,12 @@ private void projectInternal(LensContext context, Stri String traceTitle = fromStart ? "projector start" : "projector resume"; medic.traceContext(LOGGER, activityDescription, traceTitle, false, context, false); -// setupCounters(now, context.getPrismContext(), task, parentResult); - - if (consistencyChecks) context.checkConsistence(); - - if (fromStart) { - context.normalize(); - context.resetProjectionWave(); - } + context.checkConsistenceIfNeeded(); OperationResult result = parentResult.subresult(Projector.class.getName() + ".project") .addQualifier(context.getOperationQualifier()) .addParam("fromStart", fromStart) .addContext("projectionWave", context.getProjectionWave()) - .addContext("executionWave", context.getExecutionWave()) .build(); ProjectorRunTraceType trace; if (result.isTraced()) { @@ -180,119 +164,53 @@ private void projectInternal(LensContext context, Stri context.reportProgress(new ProgressInformation(PROJECTOR, ENTERING)); if (fromStart) { - medic.partialExecute("load", - (result1) -> { - contextLoader.load(context, activityDescription, task, result1); - // Set the "fresh" mark now so following consistency check will be stricter - context.setFresh(true); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.LOAD, contextLoader, contextLoader::load, partialProcessingOptions::getLoad, - Projector.class, context, result); + Projector.class, context, activityDescription, now, task, result); } - // For now let's pretend to do just one wave. The maxWaves number will be corrected in the - // first wave when dependencies are sorted out for the first time. - int maxWaves = context.getExecutionWave() + 1; - - // Start the waves .... - LOGGER.trace("WAVE: Starting the waves."); - - boolean firstWave = true; - - while ((allWaves && context.getProjectionWave() < maxWaves) || - (!allWaves && context.getProjectionWave() <= context.getExecutionWave())) { - - boolean inFirstWave = firstWave; - firstWave = false; // in order to not forget to reset it ;) - - context.checkAbortRequested(); - - LOGGER.trace("WAVE {} (maxWaves={}, executionWave={})", - context.getProjectionWave(), maxWaves, context.getExecutionWave()); - - //just make sure everything is loaded and set as needed - dependencyProcessor.preprocessDependencies(context); - - // Process the focus-related aspects of the context. That means inbound, focus activation, - // object template and assignments. - - medic.partialExecute("focus", - (result1) -> { - focusProcessor.processFocus(context, activityDescription, now, task, result1); - context.recomputeFocus(); - if (consistencyChecks) context.checkConsistence(); - }, - partialProcessingOptions::getFocus, - Projector.class, context, result); - - medic.traceContext(LOGGER, activityDescription, "focus processing", false, context, false); - LensUtil.checkContextSanity(context, "focus processing", result); - - if (partialProcessingOptions.getProjection() != PartialProcessingTypeType.SKIP) { - // Process activation of all resources, regardless of the waves. This is needed to properly - // sort projections to waves as deprovisioning will reverse the dependencies. And we know whether - // a projection is provisioned or deprovisioned only after the activation is processed. - if (fromStart && inFirstWave) { - OperationResult activationResult = result.subresult(Projector.class.getName() + ".activation") - .setMinor() - .build(); - try { - LOGGER.trace("Processing activation for all contexts"); - for (LensProjectionContext projectionContext : context.getProjectionContexts()) { - if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN - || projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { - continue; - } - activationProcessor.processActivation(context, projectionContext, now, task, activationResult); - projectionContext.recompute(); - } - assignmentProcessor.removeIgnoredContexts( - context); // TODO move implementation of this method elsewhere; but it has to be invoked here, as activationProcessor sets the IGNORE flag - } catch (Throwable t) { - activationResult.recordFatalError(t); - throw t; - } finally { - activationResult.computeStatusIfUnknown(); - } - } - medic.traceContext(LOGGER, activityDescription, "projection activation of all resources", true, context, - true); - if (consistencyChecks) { - context.checkConsistence(); - } - dependencyProcessor.sortProjectionsToWaves(context); - maxWaves = dependencyProcessor.computeMaxWaves(context); - LOGGER.trace("Continuing wave {}, maxWaves={}", context.getProjectionWave(), maxWaves); + LOGGER.trace("WAVE {} (executionWave={})", context.getProjectionWave(), context.getExecutionWave()); - for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + //just make sure everything is loaded and set as needed + dependencyProcessor.preprocessDependencies(context); - medic.partialExecute("projection", - (result1) -> projectProjection(context, projectionContext, - partialProcessingOptions, now, activityDescription, task, result1), - partialProcessingOptions::getProjection, - Projector.class, context, projectionContext, result); - // TODO: make this condition more complex in the future. We may want the ability - // to select only some projections to process + // Process the focus-related aspects of the context. That means inbound, focus activation, + // object template and assignments. - } + medic.partialExecute(Components.FOCUS, assignmentHolderProcessor, + assignmentHolderProcessor::processFocus, + partialProcessingOptions::getFocus, + Projector.class, context, activityDescription, now, task, result); - // if there exists some conflicting projection contexts, add them to the context so they will be recomputed in the next wave.. - addConflictingContexts(context); + if (partialProcessingOptions.getProjection() != PartialProcessingTypeType.SKIP) { + // Process activation of all resources, regardless of the waves. This is needed to properly + // sort projections to waves as deprovisioning will reverse the dependencies. And we know whether + // a projection is provisioned or deprovisioned only after the activation is processed. + if (fromStart) { + activationProcessor.processActivationForAllResources(context, activityDescription, now, task, result); } - if (consistencyChecks) context.checkConsistence(); + dependencyProcessor.sortProjectionsToWaves(context); + + // In the future we may want the ability to select only some projections to process. + for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + medic.partialExecute(Components.PROJECTION, + (result1) -> projectProjection(context, projectionContext, + partialProcessingOptions, now, activityDescription, task, result1), + partialProcessingOptions::getProjection, + Projector.class, context, projectionContext, result); + } - context.incrementProjectionWave(); + // If there exists some conflicting projection contexts, add them to the context so they will be recomputed + // in the next wave. + addConflictingContexts(context); } - LOGGER.trace("WAVE: Stopping the waves. There was {} waves", context.getProjectionWave()); + context.checkConsistenceIfNeeded(); + context.incrementProjectionWave(); - // We can do this only when computation of all the waves is finished. Before that we do not know - // activation of every account and therefore cannot decide what is OK and what is not dependencyProcessor.checkDependenciesFinal(context, result); - - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); computeResultStatus(now, result); @@ -316,43 +234,16 @@ private void projectInternal(LensContext context, Stri } context.reportProgress(new ProgressInformation(PROJECTOR, result)); } - } -// -// private synchronized void setupCounters(XMLGregorianCalendar now, PrismContext prismContext, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { -// -// if (task.getOid() == null) { -// return; -// } -// -// TaskType taskType = task.getTaskType(); -// LOGGER.trace("Collecting counters for {}", task); -// -// AssignmentTripleEvaluator ate = new AssignmentTripleEvaluator<>(); -// ate.setNow(now); -// ate.setPrismContext(prismContext); -// ate.setResult(result); -// ate.setSource(taskType); -// ate.setTask(task); -// DeltaSetTriple> evaluatedAssignments = ate.preProcessAssignments(task.getUpdatedOrClonedTaskObject()); -// -// Set evaluatedPolicyRules = new HashSet<>(); -// -// for (EvaluatedAssignmentImpl evaluatedAssignment : evaluatedAssignments.union()) { -// evaluatedPolicyRules.addAll(evaluatedAssignment.getOtherTargetsPolicyRules()); -// } -// evaluatedPolicyRules.forEach(policyRule -> counterManager.registerCounter(task, policyRule.getPolicyRule())); -// -// } -// + private void projectProjection(LensContext context, LensProjectionContext projectionContext, PartialProcessingOptionsType partialProcessingOptions, XMLGregorianCalendar now, String activityDescription, Task task, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { - String projectionDesc = getProjectionDesc(projectionContext); - parentResult.addParam(OperationResult.PARAM_PROJECTION, projectionDesc); // a bit of hack -> to have projection info also on the root "component" operation result + String projectionDesc = projectionContext.getDescription(); + parentResult.addParam(OperationResult.PARAM_PROJECTION, projectionDesc); // a bit of hack -> to have projection info also on the root "component" operation result parentResult.addParam("resourceName", projectionContext.getResourceName()); if (projectionContext.getWave() != context.getProjectionWave()) { @@ -389,7 +280,7 @@ private void projectProjection(LensContext context, Le // Some projections may not be loaded at this point, e.g. high-order dependency projections contextLoader.makeSureProjectionIsLoaded(context, projectionContext, task, result); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); if (!dependencyProcessor.checkDependencies(context, projectionContext, result)) { result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Skipping projection because it has unsatisfied dependencies"); @@ -398,68 +289,36 @@ private void projectProjection(LensContext context, Le // TODO: decide if we need to continue - medic.partialExecute("projectionValues", - (result1) -> { - // This is a "composite" processor. it contains several more processor invocations inside - projectionValuesProcessor.process(context, projectionContext, activityDescription, task, result1); - if (consistencyChecks) context.checkConsistence(); - - projectionContext.recompute(); - if (consistencyChecks) context.checkConsistence(); - }, + // This is a "composite" processor. it contains several more processor invocations inside + medic.partialExecute(Components.PROJECTION_VALUES, projectionValuesProcessor, + projectionValuesProcessor::process, partialProcessingOptions::getProjectionValues, - Projector.class, context, projectionContext, result); + Projector.class, context, projectionContext, activityDescription, now, task, result); if (projectionContext.isTombstone()) { result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Skipping projection because it is a tombstone"); return; } - medic.partialExecute("projectionCredentials", - (result1) -> { - projectionCredentialsProcessor.processProjectionCredentials(context, projectionContext, now, task, result1); - if (consistencyChecks) context.checkConsistence(); - - projectionContext.recompute(); - medic.traceContext(LOGGER, activityDescription, "projection values and credentials of "+projectionDesc, false, context, true); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_CREDENTIALS, projectionCredentialsProcessor, + projectionCredentialsProcessor::processProjectionCredentials, partialProcessingOptions::getProjectionCredentials, - Projector.class, context, projectionContext, result); - + Projector.class, context, projectionContext, activityDescription, now, task, result); - medic.partialExecute("projectionReconciliation", - (result1) -> { - reconciliationProcessor.processReconciliation(context, projectionContext, task, result1); - projectionContext.recompute(); - medic.traceContext(LOGGER, activityDescription, "projection reconciliation of "+projectionDesc, false, context, false); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_RECONCILIATION, reconciliationProcessor, + reconciliationProcessor::processReconciliation, partialProcessingOptions::getProjectionReconciliation, - Projector.class, context, projectionContext, result); + Projector.class, context, projectionContext, activityDescription, now, task, result); - medic.partialExecute("projectionValuesPostRecon", - (result1) -> { - projectionValuesProcessor.processPostRecon(context, projectionContext, activityDescription, task, result1); - if (consistencyChecks) context.checkConsistence(); - - projectionContext.recompute(); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_VALUES_POST_RECON, projectionValuesProcessor, + projectionValuesProcessor::processPostRecon, partialProcessingOptions::getProjectionValues, - Projector.class, context, projectionContext, result); - - medic.partialExecute("projectionLifecycle", - (result1) -> { - activationProcessor.processLifecycle(context, projectionContext, now, task, result1); - if (consistencyChecks) context.checkConsistence(); + Projector.class, context, projectionContext, activityDescription, now, task, result); - projectionContext.recompute(); -// LensUtil.traceContext(LOGGER, activityDescription, "projection lifecycle of "+projectionDesc, false, context, false); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_LIFECYCLE, activationProcessor, + activationProcessor::processLifecycle, partialProcessingOptions::getProjectionLifecycle, - Projector.class, context, projectionContext, result); + Projector.class, context, projectionContext, activityDescription, now, task, result); result.recordSuccess(); @@ -469,33 +328,14 @@ private void projectProjection(LensContext context, Le projectionContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); ModelImplUtils.handleConnectorErrorCriticality(projectionContext.getResource(), e, result); } - - - } - - private String getProjectionDesc(LensProjectionContext projectionContext) { - if (projectionContext.getResource() != null) { - return projectionContext.getResource() + "("+projectionContext.getResourceShadowDiscriminator().getIntent()+")"; - } else { - ResourceShadowDiscriminator discr = projectionContext.getResourceShadowDiscriminator(); - if (discr != null) { - return projectionContext.getResourceShadowDiscriminator().toString(); - } else { - return "(UNKNOWN)"; - } - } } private void addConflictingContexts(LensContext context) { - List conflictingContexts = context.getConflictingProjectionContexts(); - if (conflictingContexts != null && !conflictingContexts.isEmpty()){ - for (LensProjectionContext conflictingContext : conflictingContexts){ - LOGGER.trace("Adding conflicting projection context {}", conflictingContext.getHumanReadableName()); - context.addProjectionContext(conflictingContext); - } - context.clearConflictingProjectionContexts(); + for (LensProjectionContext conflictingContext : context.getConflictingProjectionContexts()) { + LOGGER.trace("Adding conflicting projection context {}", conflictingContext.getHumanReadableName()); + context.addProjectionContext(conflictingContext); } - + context.clearConflictingProjectionContexts(); } private void recordFatalError(Exception e, XMLGregorianCalendar projectorStartTimestampCal, OperationResult result) { @@ -545,5 +385,4 @@ private void computeResultStatus(XMLGregorianCalendar projectorStartTimestampCal LOGGER.trace("Projector finished ({}). Etime: {} ms", result.getStatus(), (projectorEndTimestamp - projectorStartTimestamp)); } } - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java new file mode 100644 index 00000000000..e5f6d97e0ce --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector; + +import com.evolveum.midpoint.util.annotation.Experimental; + +/** + * Marker interface for processors in Projector. + * + * Note that functionality of the processor (i.e. some "process" method) is not exposed via this interface + * because many of the processors have multiple entry points. Moreover, to make code navigation in IDE easy + * (between processors and their caller) it is better to have direct caller-callee relations. + */ +@Experimental +public interface ProjectorProcessor { + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java index fa3179a7a1a..4dbf212fc31 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java @@ -14,9 +14,13 @@ import java.util.Map; import java.util.Set; +import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition; +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.match.MatchingRule; @@ -37,11 +41,6 @@ import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; -import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.schema.GetOperationOptions; @@ -82,38 +81,32 @@ * @author Radovan Semancik */ @Component -public class ReconciliationProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class ReconciliationProcessor implements ProjectorProcessor { - @Autowired - private ProvisioningService provisioningService; + @Autowired private ProvisioningService provisioningService; + @Autowired PrismContext prismContext; + @Autowired private MatchingRuleRegistry matchingRuleRegistry; + @Autowired private ClockworkMedic medic; - @Autowired - PrismContext prismContext; - - @Autowired - private MatchingRuleRegistry matchingRuleRegistry; - - private static final String PROCESS_RECONCILIATION = ReconciliationProcessor.class.getName() + ".processReconciliation"; private static final Trace LOGGER = TraceManager.getTrace(ReconciliationProcessor.class); - void processReconciliation(LensContext context, - LensProjectionContext projectionContext, Task task, OperationResult result) throws SchemaException, + @ProcessorMethod + void processReconciliation(LensContext context, LensProjectionContext projectionContext, + String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal types. - return; - } - processReconciliationFocus(context, projectionContext, task, result); + + processReconciliation(projectionContext, task, result); + + projectionContext.recompute(); + context.checkConsistenceIfNeeded(); + + medic.traceContext(LOGGER, activityDescription, "projection reconciliation of "+projectionContext.getDescription(), false, context, false); } - private void processReconciliationFocus(LensContext context, - LensProjectionContext projCtx, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, + private void processReconciliation(LensProjectionContext projCtx, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { // Reconcile even if it was not explicitly requested and if we have full shadow @@ -126,8 +119,7 @@ private void processReconciliationFocus(LensContext co } SynchronizationPolicyDecision policyDecision = projCtx.getSynchronizationPolicyDecision(); - if (policyDecision != null - && (policyDecision == SynchronizationPolicyDecision.DELETE || policyDecision == SynchronizationPolicyDecision.UNLINK)) { + if (((policyDecision == SynchronizationPolicyDecision.DELETE) || (policyDecision == SynchronizationPolicyDecision.UNLINK))) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping reconciliation of {}: decision={}", projCtx.getHumanReadableName(), policyDecision); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java index 3175757bd00..2bf063fe6b7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java @@ -11,6 +11,9 @@ import javax.xml.datatype.XMLGregorianCalendar; import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.path.ItemPath; @@ -62,7 +65,8 @@ * @author Radovan Semancik */ @Component -public class CredentialsProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class CredentialsProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(CredentialsProcessor.class); @@ -74,14 +78,11 @@ public class CredentialsProcessor { @Autowired private LocalizationService localizationService; @Autowired private ContextLoader contextLoader; - /** - * @pre context.focusContext is not null and of FocusType.class - */ + @ProcessorMethod public void processFocusCredentials(LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); contextLoader.reloadSecurityPolicyIfNeeded(context, focusContext, task, result); 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 0477a713067..4eee63fbe52 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 @@ -13,6 +13,10 @@ import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.path.ItemPath; @@ -25,16 +29,10 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.common.stringpolicy.ObjectValuePolicyEvaluator; import com.evolveum.midpoint.model.common.stringpolicy.ShadowValuePolicyOriginResolver; import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.OperationalDataManager; -import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingInitializer; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingOutputProcessor; @@ -71,37 +69,36 @@ * @author Radovan Semancik */ @Component -public class ProjectionCredentialsProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenProjectionDeleted = true) +public class ProjectionCredentialsProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ProjectionCredentialsProcessor.class); @Autowired private PrismContext prismContext; - @Autowired private ContextLoader contextLoader; - @Autowired private MappingFactory mappingFactory; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private ValuePolicyProcessor valuePolicyProcessor; @Autowired private Protector protector; @Autowired private OperationalDataManager operationalDataManager; @Autowired private ModelObjectResolver modelObjectResolver; + @Autowired private ClockworkMedic medic; - public void processProjectionCredentials(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, - OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, - SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + @ProcessorMethod + public void processProjectionCredentials(LensContext context, + LensProjectionContext projectionContext, String activityDescription, XMLGregorianCalendar now, Task task, + OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, + SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (projectionContext.isDelete()) { - return; - } - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + processProjectionCredentials(context, projectionContext, now, task, result); + context.checkConsistenceIfNeeded(); - processProjectionCredentialsFocus((LensContext) context, projectionContext, now, task, result); + projectionContext.recompute(); + context.checkConsistenceIfNeeded(); - } + medic.traceContext(LOGGER, activityDescription, "projection values and credentials of "+projectionContext.getDescription(), false, context, true); } - private void processProjectionCredentialsFocus(LensContext context, + private void processProjectionCredentials(LensContext context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java index b9aa4952dfd..6d7981d4f20 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java @@ -6,65 +6,60 @@ */ package com.evolveum.midpoint.model.impl.lens.projector.focus; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; -import static org.apache.commons.lang3.BooleanUtils.isTrue; - -import java.util.Map; - import javax.xml.datatype.XMLGregorianCalendar; -import com.evolveum.midpoint.model.api.context.ModelState; +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.projector.Components; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; import com.evolveum.midpoint.model.impl.lens.projector.Projector; -import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEnforcer; +import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleProcessor; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.util.DefinitionUtil; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; -import com.evolveum.midpoint.util.exception.NoFocusNameSchemaException; +import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; -import com.evolveum.midpoint.prism.path.UniformItemPath; -import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** - * Processor to handle everything about focus: values, assignments, etc. + * Handles everything about AssignmentHolder-typed focus: + *
    + *
  1. inbounds (for FocusType),
  2. + *
  3. activation before object template (for FocusType),
  4. + *
  5. object template before assignments,
  6. + *
  7. activation after object template (for FocusType),
  8. + *
  9. assignments (including processing orgs, membership/delegate refs, conflicts),
  10. + *
  11. focus lifecycle,
  12. + *
  13. object template after assignments,
  14. + *
  15. activation after second object template (for FocusType),
  16. + *
  17. credentials (for FocusType),
  18. + *
  19. focus policy rules.
  20. + *
+ * + * All of this is executed with regard to iteration. See also {@link IterationHelper}. + * Also takes care for minor things like item limitations * * @author Radovan Semancik * */ @Component -public class AssignmentHolderProcessor { +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class) +public class AssignmentHolderProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(AssignmentHolderProcessor.class); - @Autowired private ContextLoader contextLoader; @Autowired private InboundProcessor inboundProcessor; @Autowired private AssignmentProcessor assignmentProcessor; @Autowired private ObjectTemplateProcessor objectTemplateProcessor; @@ -73,426 +68,168 @@ public class AssignmentHolderProcessor { @Autowired private PolicyRuleProcessor policyRuleProcessor; @Autowired private FocusLifecycleProcessor focusLifecycleProcessor; @Autowired private ClockworkMedic medic; - @Autowired private PolicyRuleEnforcer policyRuleEnforcer; @Autowired private CacheConfigurationManager cacheConfigurationManager; - - @Autowired private FocusProcessor focusProcessor; + @Autowired private CredentialsProcessor credentialsProcessor; + @Autowired private ItemLimitationsChecker itemLimitationsChecker; + @Autowired private FocusActivationProcessor focusActivationProcessor; @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; - public void processFocus(LensContext context, String activityDescription, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, - ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - - if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - LOGGER.trace("Skipping processing focus. Not assignment holder type, type {}", focusContext.getObjectTypeClass()); - return; - } + @ProcessorMethod + public void processFocus(LensContext context, String activityDescription, XMLGregorianCalendar now, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, + CommunicationException, SecurityViolationException, PolicyViolationException, ObjectAlreadyExistsException, + PreconditionViolationException { - //noinspection unchecked - processFocusFocus((LensContext)context, activityDescription, now, task, result); - - } - - @SuppressWarnings({ "unused", "RedundantThrows" }) - private void processFocusNonFocus(LensContext context, String activityDescription, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, - ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - // This is somehow "future legacy" code. It will be removed later when we have better support for organizational structure - // membership in resources and tasks. - assignmentProcessor.computeTenantRefLegacy(context, task, result); - } - - private void processFocusFocus(LensContext context, String activityDescription, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, - ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { LensFocusContext focusContext = context.getFocusContext(); PartialProcessingOptionsType partialProcessingOptions = context.getPartialProcessingOptions(); - checkArchetypeRefDelta(context); - - boolean resetOnRename = true; // This is fixed now. TODO: make it configurable - - boolean wasResetOnIterationSpecificationChange = false; - boolean iterationSpecificationInitialized = false; - IterationSpecificationType iterationSpecification = null; // initializing just for the compiler not to complain - int maxIterations = 0; // initializing just for the compiler not to complain - - int iteration = focusContext.getIteration(); - String iterationToken = focusContext.getIterationToken(); - boolean wasResetIterationCounter = false; - - PrismObject focusCurrent = focusContext.getObjectCurrent(); - if (focusCurrent != null && iterationToken == null) { - Integer focusIteration = focusCurrent.asObjectable().getIteration(); - if (focusIteration != null) { - iteration = focusIteration; - } - iterationToken = focusCurrent.asObjectable().getIterationToken(); - } + IterationHelper iterationHelper = new IterationHelper<>(this, context, focusContext); while (true) { - ObjectTemplateType objectTemplate = context.getFocusTemplate(); - - if (!iterationSpecificationInitialized) { - iterationSpecification = LensUtil.getIterationSpecification(objectTemplate); - maxIterations = LensUtil.determineMaxIterations(iterationSpecification); - LOGGER.trace("maxIterations = {}, iteration specification = {} derived from template {}", maxIterations, - iterationSpecification, objectTemplate); - iterationSpecificationInitialized = true; - } - ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); LensUtil.applyObjectPolicyConstraints(focusContext, archetypePolicy, prismContext); - ExpressionVariables variablesPreIteration = ModelImplUtils.getDefaultExpressionVariables(focusContext.getObjectNew(), - null, null, null, context.getSystemConfiguration(), focusContext, prismContext); - if (iterationToken == null) { - iterationToken = LensUtil.formatIterationToken(context, focusContext, - iterationSpecification, iteration, expressionFactory, variablesPreIteration, task, result); - } + iterationHelper.onIterationStart(task, result); - // We have to remember the token and iteration in the context. - // The context can be recomputed several times. But we always want - // to use the same iterationToken if possible. If there is a random - // part in the iterationToken expression that we need to avoid recomputing - // the token otherwise the value can change all the time (even for the same inputs). - // Storing the token in the secondary delta is not enough because secondary deltas can be dropped - // if the context is re-projected. - focusContext.setIteration(iteration); - focusContext.setIterationToken(iterationToken); - LOGGER.trace("Focus {} processing, iteration {}, token '{}'", focusContext.getHumanReadableName(), iteration, iterationToken); - - String conflictMessage; - if (!LensUtil.evaluateIterationCondition(context, focusContext, - iterationSpecification, iteration, iterationToken, true, expressionFactory, variablesPreIteration, task, result)) { - - conflictMessage = "pre-iteration condition was false"; - LOGGER.debug("Skipping iteration {}, token '{}' for {} because the pre-iteration condition was false", - iteration, iterationToken, focusContext.getHumanReadableName()); - } else { + if (iterationHelper.doesPreIterationConditionHold(task, result)) { // INBOUND - if (consistencyChecks) context.checkConsistence(); - - medic.partialExecute("inbound", - (result1) -> { - // Loop through the account changes, apply inbound expressions - inboundProcessor.processInbound(context, now, task, result1); - if (consistencyChecks) context.checkConsistence(); - context.recomputeFocus(); - contextLoader.updateArchetypePolicy(context, task, result1); - contextLoader.updateArchetype(context, task, result1); - medic.traceContext(LOGGER, activityDescription, "inbound", false, context, false); - if (consistencyChecks) context.checkConsistence(); - }, - partialProcessingOptions::getInbound, - Projector.class, context, result); + context.checkConsistenceIfNeeded(); + + boolean inboundRun = + medic.partialExecute(Components.INBOUND, inboundProcessor, + inboundProcessor::processInbounds, + partialProcessingOptions::getInbound, + Projector.class, context, activityDescription, now, task, result); + + if (inboundRun && !focusContext.isDelete() && iterationHelper.didIterationSpecificationChange()) { + cleanupContext(focusContext); + continue; + } // ACTIVATION - medic.partialExecute("focusActivation", - (result1) -> focusProcessor.processActivationBeforeAssignments(context, now, result1), + medic.partialExecute(Components.FOCUS_ACTIVATION, focusActivationProcessor, + focusActivationProcessor::processActivationBeforeObjectTemplate, partialProcessingOptions::getFocusActivation, - Projector.class, context, result); - + Projector.class, context, now, task, result); // OBJECT TEMPLATE (before assignments) - if (focusContext.isDelete()) { - LOGGER.trace("Skipping refreshing of object template and iterator specification: focus delete"); - } else { - contextLoader.setFocusTemplate(context, result); - if (!wasResetOnIterationSpecificationChange) { - IterationSpecificationType newIterationSpecification = context.getFocusTemplate() != null ? - context.getFocusTemplate().getIterationSpecification() : null; - if (!java.util.Objects.equals(iterationSpecification, newIterationSpecification)) { - LOGGER.trace("Resetting iteration counter and token because of iteration specification change"); - iteration = 0; - iterationToken = null; - wasResetOnIterationSpecificationChange = true; - wasResetIterationCounter = false; - iterationSpecificationInitialized = false; - cleanupContext(focusContext); - continue; - } - } - } - - medic.partialExecute("objectTemplateBeforeAssignments", - (result1) -> objectTemplateProcessor.processTemplate(context, - ObjectTemplateMappingEvaluationPhaseType.BEFORE_ASSIGNMENTS, now, task, result1), + medic.partialExecute(Components.OBJECT_TEMPLATE_BEFORE_ASSIGNMENTS, objectTemplateProcessor, + objectTemplateProcessor::processTemplateBeforeAssignments, partialProcessingOptions::getObjectTemplateBeforeAssignments, - Projector.class, context, result); + Projector.class, context, now, task, result); + // Process activation again. Object template might have changed it. - // process activation again. Object template might have changed it. - context.recomputeFocus(); - medic.partialExecute("focusActivation", - (result1) -> focusProcessor.processActivationAfterAssignments(context, now, result1), + medic.partialExecute(Components.FOCUS_ACTIVATION, focusActivationProcessor, + focusActivationProcessor::processActivationAfterObjectTemplate, partialProcessingOptions::getFocusActivation, - Projector.class, context, result); + Projector.class, context, now, task, result); // ASSIGNMENTS focusContext.clearPendingObjectPolicyStateModifications(); focusContext.clearPendingAssignmentPolicyStateModifications(); - medic.partialExecute("assignments", - (result1) -> assignmentProcessor.processAssignments(context, now, task, result1), + medic.partialExecute(Components.ASSIGNMENTS, assignmentProcessor, + assignmentProcessor::processAssignments, partialProcessingOptions::getAssignments, - Projector.class, context, result); + Projector.class, context, now, task, result); - medic.partialExecute("assignmentsOrg", - (result1) -> assignmentProcessor.processOrgAssignments(context, task, result1), + medic.partialExecute(Components.ASSIGNMENTS_ORG, assignmentProcessor, + assignmentProcessor::processOrgAssignments, partialProcessingOptions::getAssignmentsOrg, - Projector.class, context, result); - + Projector.class, context, now, task, result); - medic.partialExecute("assignmentsMembershipAndDelegate", - (result1) -> assignmentProcessor.processMembershipAndDelegatedRefs(context, result1), + medic.partialExecute(Components.ASSIGNMENTS_MEMBERSHIP_AND_DELEGATE, assignmentProcessor, + assignmentProcessor::processMembershipAndDelegatedRefs, partialProcessingOptions::getAssignmentsMembershipAndDelegate, - Projector.class, context, result); - - context.recompute(); + Projector.class, context, now, task, result); - medic.partialExecute("assignmentsConflicts", - (result1) -> assignmentProcessor.checkForAssignmentConflicts(context, result1), + medic.partialExecute(Components.ASSIGNMENTS_CONFLICTS, assignmentProcessor, + assignmentProcessor::checkForAssignmentConflicts, partialProcessingOptions::getAssignmentsConflicts, - Projector.class, context, result); + Projector.class, context, now, task, result); - medic.partialExecute("focusLifecycle", - (result1) -> focusLifecycleProcessor.processLifecycle(context, now, task, result1), + medic.partialExecute(Components.FOCUS_LIFECYCLE, focusLifecycleProcessor, + focusLifecycleProcessor::process, partialProcessingOptions::getFocusLifecycle, - Projector.class, context, result); + Projector.class, context, now, task, result); // OBJECT TEMPLATE (after assignments) - medic.partialExecute("objectTemplateAfterAssignments", - (result1) -> objectTemplateProcessor.processTemplate(context, - ObjectTemplateMappingEvaluationPhaseType.AFTER_ASSIGNMENTS, now, task, result1), - partialProcessingOptions::getObjectTemplateBeforeAssignments, - Projector.class, context, result); + medic.partialExecute(Components.OBJECT_TEMPLATE_AFTER_ASSIGNMENTS, objectTemplateProcessor, + objectTemplateProcessor::processTemplateAfterAssignments, + partialProcessingOptions::getObjectTemplateAfterAssignments, + Projector.class, context, now, task, result); - context.recompute(); + // Process activation again. Second pass through object template might have changed it. + // We also need to apply assignment activation if needed. - // process activation again. Second pass through object template might have changed it. - // We also need to apply assignment activation if needed - context.recomputeFocus(); - medic.partialExecute("focusActivation", - (result1) -> focusProcessor.processActivationAfterAssignments(context, now, result1), + medic.partialExecute(Components.FOCUS_ACTIVATION, focusActivationProcessor, + focusActivationProcessor::processActivationAfterAssignments, partialProcessingOptions::getFocusActivation, - Projector.class, context, result); + Projector.class, context, now, task, result); // CREDENTIALS (including PASSWORD POLICY) - medic.partialExecute("focusCredentials", - (result1) -> focusProcessor.processCredentials(context, now, task, result1), + medic.partialExecute(Components.FOCUS_CREDENTIALS, credentialsProcessor, + credentialsProcessor::processFocusCredentials, partialProcessingOptions::getFocusCredentials, - Projector.class, context, result); + Projector.class, context, now, task, result); // We need to evaluate this as a last step. We need to make sure we have all the // focus deltas so we can properly trigger the rules. - medic.partialExecute("focusPolicyRules", - (result1) -> policyRuleProcessor.evaluateObjectPolicyRules(context, activityDescription, now, task, result1), + medic.partialExecute(Components.FOCUS_POLICY_RULES, policyRuleProcessor, + policyRuleProcessor::evaluateObjectPolicyRules, partialProcessingOptions::getFocusPolicyRules, - Projector.class, context, result); - - // to mimic operation of the original enforcer hook, we execute the following only in the initial state - if (context.getState() == ModelState.INITIAL) { - // If partial execution for focus policy rules and for assignments is turned off, this method call is a no-op. - // So we don't need to check the partial execution flags for its invocation. - policyRuleEnforcer.execute(context); - } + Projector.class, context, now, task, result); // Processing done, check for success - //noinspection ConstantConditions - if (resetOnRename && !wasResetIterationCounter && willResetIterationCounter(focusContext)) { - // Make sure this happens only the very first time during the first recompute. - // Otherwise it will always change the token (especially if the token expression has a random part) - // hence the focusContext.getIterationToken() == null - wasResetIterationCounter = true; - if (iteration != 0) { - iteration = 0; - iterationToken = null; - LOGGER.trace("Resetting iteration counter and token because rename was detected"); - cleanupContext(focusContext); - continue; - } - } - - ConstraintsCheckingStrategyType strategy = context.getFocusConstraintsCheckingStrategy(); - boolean skipWhenNoChange = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoChange()); - boolean skipWhenNoIteration = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoIteration()); - - boolean checkConstraints; - if (skipWhenNoChange && !hasNameDelta(focusContext)) { - LOGGER.trace("Skipping constraints check because 'skipWhenNoChange' is true and there's no name delta"); - checkConstraints = false; - } else if (skipWhenNoIteration && maxIterations == 0) { - LOGGER.trace("Skipping constraints check because 'skipWhenNoIteration' is true and there is no iteration defined"); - checkConstraints = false; - } else if (TaskType.class == focusContext.getObjectTypeClass()) { - LOGGER.trace("Skipping constraints check for task, not needed because tasks names are not unique."); - checkConstraints = false; - } else { - checkConstraints = true; - } - - PrismObject previewObjectNew = focusContext.getObjectNew(); - if (previewObjectNew == null) { - // this must be delete - } else { - // Explicitly check for name. The checker would check for this also. But checking it here - // will produce better error message - PolyStringType objectName = previewObjectNew.asObjectable().getName(); - if (objectName == null || objectName.getOrig().isEmpty()) { - throw new NoFocusNameSchemaException("No name in new object "+objectName+" as produced by template "+objectTemplate+ - " in iteration "+iteration+", we cannot process an object without a name"); - } + if (iterationHelper.didResetOnRenameOccur()) { + cleanupContext(focusContext); + continue; } - // Check if iteration constraints are OK - FocusConstraintsChecker checker = new FocusConstraintsChecker<>(); - checker.setPrismContext(prismContext); - checker.setContext(context); - checker.setRepositoryService(cacheRepositoryService); - checker.setCacheConfigurationManager(cacheConfigurationManager); - boolean satisfies = !checkConstraints || checker.check(previewObjectNew, result); - if (satisfies) { - LOGGER.trace("Current focus satisfies uniqueness constraints. Iteration {}, token '{}'", iteration, iterationToken); - ExpressionVariables variablesPostIteration = ModelImplUtils.getDefaultExpressionVariables(focusContext.getObjectNew(), - null, null, null, context.getSystemConfiguration(), focusContext, prismContext); - if (LensUtil.evaluateIterationCondition(context, focusContext, - iterationSpecification, iteration, iterationToken, false, expressionFactory, variablesPostIteration, - task, result)) { - // stop the iterations - break; - } else { - conflictMessage = "post-iteration condition was false"; - LOGGER.debug("Skipping iteration {}, token '{}' for {} because the post-iteration condition was false", - iteration, iterationToken, focusContext.getHumanReadableName()); - } - } else { - LOGGER.trace("Current focus does not satisfy constraints. Conflicting object: {}; iteration={}, maxIterations={}", - checker.getConflictingObject(), iteration, maxIterations); - conflictMessage = checker.getMessages(); + PrismObject objectNew = focusContext.getObjectNew(); + if (objectNew == null) { + LOGGER.trace("Object delete: skipping name presence and uniqueness check"); + break; } - if (!wasResetIterationCounter) { - wasResetIterationCounter = true; - if (iteration != 0) { - iterationToken = null; - iteration = 0; - LOGGER.trace("Resetting iteration counter and token after conflict"); - cleanupContext(focusContext); - continue; - } + if (iterationHelper.isIterationOk(objectNew, task, result)) { + break; } - } - - // Next iteration - iteration++; - iterationToken = null; - LensUtil.checkMaxIterations(iteration, maxIterations, conflictMessage, focusContext.getHumanReadableName()); - cleanupContext(focusContext); - } - addIterationTokenDeltas(focusContext, iteration, iterationToken); - checkItemsLimitations(focusContext); - if (consistencyChecks) context.checkConsistence(); - - } - - private void checkItemsLimitations(LensFocusContext focusContext) - throws SchemaException, ConfigurationException { - Map itemDefinitionsMap = focusContext.getItemDefinitionsMap(); - PrismObject objectNew = null; // lazily evaluated - for (Map.Entry entry : itemDefinitionsMap.entrySet()) { - for (PropertyLimitationsType limitation : entry.getValue().getLimitations()) { - if (!limitation.getLayer().contains(LayerType.MODEL)) { // or should we apply SCHEMA-layer limitations as well? + if (iterationHelper.shouldResetOnConflict()) { + cleanupContext(focusContext); continue; } - if (objectNew == null) { - focusContext.recompute(); - objectNew = focusContext.getObjectNew(); - if (objectNew == null) { - return; // nothing to check on DELETE operation - } - } - checkItemLimitations(objectNew, entry.getKey(), limitation); } - } - } - - private void checkItemLimitations(PrismObject object, ItemPath path, PropertyLimitationsType limitation) - throws SchemaException { - Object item = object.find(path); - if (isTrue(limitation.isIgnore())) { - return; - } - int count = getValueCount(item); - Integer min = DefinitionUtil.parseMultiplicity(limitation.getMinOccurs()); - if (min != null && min > 0 && count < min) { - throw new SchemaException("Expected at least " + min + " values of " + path + ", got " + count); - } - Integer max = DefinitionUtil.parseMultiplicity(limitation.getMaxOccurs()); - if (max != null && max >= 0 && count > max) { - throw new SchemaException("Expected at most " + max + " values of " + path + ", got " + count); - } - } - private int getValueCount(Object item) { - if (item == null) { - return 0; - } - if (!(item instanceof Item)) { - throw new IllegalStateException("Expected Item but got " + item.getClass() + " instead"); + iterationHelper.incrementIterationCounter(); + cleanupContext(focusContext); } - return ((Item) item).getValues().size(); - } + iterationHelper.createIterationTokenDeltas(); + context.recomputeFocus(); + itemLimitationsChecker.checkItemsLimitations(focusContext); + context.checkConsistenceIfNeeded(); - private boolean willResetIterationCounter(LensFocusContext focusContext) throws SchemaException { - ObjectDelta focusDelta = focusContext.getDelta(); - if (focusDelta == null) { - return false; - } - if (focusContext.isAdd() || focusContext.isDelete()) { - return false; - } - if (focusDelta.findPropertyDelta(FocusType.F_ITERATION) != null) { - // there was a reset already in previous projector runs - return false; - } - // Check for rename - return hasNameDelta(focusDelta); - } - - private boolean hasNameDelta(LensFocusContext focusContext) throws SchemaException { - ObjectDelta focusDelta = focusContext.getDelta(); - return focusDelta != null && hasNameDelta(focusDelta); - } - - private boolean hasNameDelta(ObjectDelta focusDelta) { - PropertyDelta nameDelta = focusDelta.findPropertyDelta(FocusType.F_NAME); - return nameDelta != null; + medic.traceContext(LOGGER, activityDescription, "focus processing", false, context, false); + LensUtil.checkContextSanity(context, "focus processing", result); } /** @@ -508,383 +245,19 @@ private void cleanupContext(LensFocusContext void processActivationBeforeAssignments(LensContext context, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// processActivationBasic(context, now, result); -// } -// -// private void processActivationAfterAssignments(LensContext context, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// processActivationBasic(context, now, result); -// processAssignmentActivation(context, now, result); -// } -// -// private void processActivationBasic(LensContext context, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// LensFocusContext focusContext = context.getFocusContext(); -// -// if (focusContext.isDelete()) { -// LOGGER.trace("Skipping processing of focus activation: focus delete"); -// return; -// } -// -// processActivationAdministrativeAndValidity(focusContext, now, result); -// -// if (focusContext.canRepresent(UserType.class)) { -// processActivationLockout((LensFocusContext) focusContext, now, result); -// } -// } -// -// private void processActivationAdministrativeAndValidity(LensFocusContext focusContext, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// -// TimeIntervalStatusType validityStatusNew = null; -// TimeIntervalStatusType validityStatusCurrent = null; -// XMLGregorianCalendar validityChangeTimestamp = null; -// -// String lifecycleStateNew = null; -// String lifecycleStateCurrent = null; -// ActivationType activationNew = null; -// ActivationType activationCurrent = null; -// -// PrismObject focusNew = focusContext.getObjectNew(); -// if (focusNew != null) { -// F focusTypeNew = focusNew.asObjectable(); -// activationNew = focusTypeNew.getActivation(); -// if (activationNew != null) { -// validityStatusNew = activationComputer.getValidityStatus(activationNew, now); -// validityChangeTimestamp = activationNew.getValidityChangeTimestamp(); -// } -// lifecycleStateNew = focusTypeNew.getLifecycleState(); -// } -// -// PrismObject focusCurrent = focusContext.getObjectCurrent(); -// if (focusCurrent != null) { -// F focusCurrentType = focusCurrent.asObjectable(); -// activationCurrent = focusCurrentType.getActivation(); -// if (activationCurrent != null) { -// validityStatusCurrent = activationComputer.getValidityStatus(activationCurrent, validityChangeTimestamp); -// } -// lifecycleStateCurrent = focusCurrentType.getLifecycleState(); -// } -// -// if (validityStatusCurrent == validityStatusNew) { -// // No change, (almost) no work -// if (validityStatusNew != null && activationNew.getValidityStatus() == null) { -// // There was no validity change. But the status is not recorded. So let's record it so it can be used in searches. -// recordValidityDelta(focusContext, validityStatusNew, now); -// } else { -// LOGGER.trace("Skipping validity processing because there was no change ({} -> {})", validityStatusCurrent, validityStatusNew); -// } -// } else { -// LOGGER.trace("Validity change {} -> {}", validityStatusCurrent, validityStatusNew); -// recordValidityDelta(focusContext, validityStatusNew, now); -// } -// -// LifecycleStateModelType lifecycleModel = focusContext.getLifecycleModel(); -// ActivationStatusType effectiveStatusNew = activationComputer.getEffectiveStatus(lifecycleStateNew, activationNew, validityStatusNew, lifecycleModel); -// ActivationStatusType effectiveStatusCurrent = activationComputer.getEffectiveStatus(lifecycleStateCurrent, activationCurrent, validityStatusCurrent, lifecycleModel); -// -// if (effectiveStatusCurrent == effectiveStatusNew) { -// // No change, (almost) no work -// if (effectiveStatusNew != null && (activationNew == null || activationNew.getEffectiveStatus() == null)) { -// // There was no effective status change. But the status is not recorded. So let's record it so it can be used in searches. -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } else { -// if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().hasItemDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS)) { -// LOGGER.trace("Forcing effective status delta even though there was no change ({} -> {}) because there is explicit administrativeStatus delta", effectiveStatusCurrent, effectiveStatusNew); -// // We need this to force the change down to the projections later in the activation processor -// // some of the mappings will use effectiveStatus as a source, therefore there has to be a delta for the mapping to work correctly -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } else { -// //check computed effective status current with the saved one - e.g. there can be some inconsistencies so we need to check and force the change.. in other cases, effectvie status will be stored with -// // incorrect value. Maybe another option is to not compute effectiveStatusCurrent if there is an existing (saved) effective status in the user.. TODO -// if (activationCurrent != null && activationCurrent.getEffectiveStatus() != null) { -// ActivationStatusType effectiveStatusSaved = activationCurrent.getEffectiveStatus(); -// if (effectiveStatusSaved != effectiveStatusNew) { -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } -// } -// LOGGER.trace("Skipping effective status processing because there was no change ({} -> {})", effectiveStatusCurrent, effectiveStatusNew); -// } -// } -// } else { -// LOGGER.trace("Effective status change {} -> {}", effectiveStatusCurrent, effectiveStatusNew); -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } -// -// -// } -// -// private void processActivationLockout(LensFocusContext focusContext, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// -// ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); -// if (focusPrimaryDelta != null) { -// PropertyDelta lockoutStatusDelta = focusContext.getPrimaryDelta().findPropertyDelta(SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS); -// if (lockoutStatusDelta != null) { -// if (lockoutStatusDelta.isAdd()) { -// for (PrismPropertyValue pval: lockoutStatusDelta.getValuesToAdd()) { -// if (pval.getValue() == LockoutStatusType.LOCKED) { -// throw new SchemaException("Lockout status cannot be changed to LOCKED value"); -// } -// } -// } else if (lockoutStatusDelta.isReplace()) { -// for (PrismPropertyValue pval: lockoutStatusDelta.getValuesToReplace()) { -// if (pval.getValue() == LockoutStatusType.LOCKED) { -// throw new SchemaException("Lockout status cannot be changed to LOCKED value"); -// } -// } -// } -// } -// } -// -// ActivationType activationNew = null; -// ActivationType activationCurrent = null; -// -// LockoutStatusType lockoutStatusNew = null; -// LockoutStatusType lockoutStatusCurrent = null; -// -// PrismObject focusNew = focusContext.getObjectNew(); -// if (focusNew != null) { -// activationNew = focusNew.asObjectable().getActivation(); -// if (activationNew != null) { -// lockoutStatusNew = activationNew.getLockoutStatus(); -// } -// } -// -// PrismObject focusCurrent = focusContext.getObjectCurrent(); -// if (focusCurrent != null) { -// activationCurrent = focusCurrent.asObjectable().getActivation(); -// if (activationCurrent != null) { -// lockoutStatusCurrent = activationCurrent.getLockoutStatus(); -// } -// } -// -// if (lockoutStatusNew == lockoutStatusCurrent) { -// // No change, (almost) no work -// LOGGER.trace("Skipping lockout processing because there was no change ({} -> {})", lockoutStatusCurrent, lockoutStatusNew); -// return; -// } -// -// LOGGER.trace("Lockout change {} -> {}", lockoutStatusCurrent, lockoutStatusNew); -// -// if (lockoutStatusNew == LockoutStatusType.NORMAL) { -// -// CredentialsType credentialsTypeNew = focusNew.asObjectable().getCredentials(); -// if (credentialsTypeNew != null) { -// resetFailedLogins(focusContext, credentialsTypeNew.getPassword(), SchemaConstants.PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS); -// resetFailedLogins(focusContext, credentialsTypeNew.getNonce(), SchemaConstants.PATH_CREDENTIALS_NONCE_FAILED_LOGINS); -// resetFailedLogins(focusContext, credentialsTypeNew.getSecurityQuestions(), SchemaConstants.PATH_CREDENTIALS_SECURITY_QUESTIONS_FAILED_LOGINS); -// } -// -// if (activationNew != null && activationNew.getLockoutExpirationTimestamp() != null) { -// PrismContainerDefinition activationDefinition = getActivationDefinition(); -// PrismPropertyDefinition lockoutExpirationTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP); -// PropertyDelta lockoutExpirationTimestampDelta -// = lockoutExpirationTimestampDef.createEmptyDelta(ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP)); -// lockoutExpirationTimestampDelta.setValueToReplace(); -// focusContext.swallowToProjectionWaveSecondaryDelta(lockoutExpirationTimestampDelta); -// } -// } -// -// } -// -// private void resetFailedLogins(LensFocusContext focusContext, AbstractCredentialType credentialTypeNew, ItemPath path) throws SchemaException{ -// if (credentialTypeNew != null) { -// Integer failedLogins = credentialTypeNew.getFailedLogins(); -// if (failedLogins != null && failedLogins != 0) { -// PrismPropertyDefinition failedLoginsDef = getFailedLoginsDefinition(); -// PropertyDelta failedLoginsDelta = failedLoginsDef.createEmptyDelta(path); -// failedLoginsDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(0, OriginType.USER_POLICY, null)); -// focusContext.swallowToProjectionWaveSecondaryDelta(failedLoginsDelta); -// } -// } -// } -// -// private void recordValidityDelta(LensFocusContext focusContext, TimeIntervalStatusType validityStatusNew, -// XMLGregorianCalendar now) throws SchemaException { -// PrismContainerDefinition activationDefinition = getActivationDefinition(); -// -// PrismPropertyDefinition validityStatusDef = activationDefinition.findPropertyDefinition(ActivationType.F_VALIDITY_STATUS); -// PropertyDelta validityStatusDelta -// = validityStatusDef.createEmptyDelta(ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_VALIDITY_STATUS)); -// if (validityStatusNew == null) { -// validityStatusDelta.setValueToReplace(); -// } else { -// validityStatusDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(validityStatusNew, OriginType.USER_POLICY, null)); -// } -// focusContext.swallowToProjectionWaveSecondaryDelta(validityStatusDelta); -// -// PrismPropertyDefinition validityChangeTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_VALIDITY_CHANGE_TIMESTAMP); -// PropertyDelta validityChangeTimestampDelta -// = validityChangeTimestampDef.createEmptyDelta(ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_VALIDITY_CHANGE_TIMESTAMP)); -// validityChangeTimestampDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(now, OriginType.USER_POLICY, null)); -// focusContext.swallowToProjectionWaveSecondaryDelta(validityChangeTimestampDelta); -// } -// -// private void recordEffectiveStatusDelta(LensFocusContext focusContext, -// ActivationStatusType effectiveStatusNew, XMLGregorianCalendar now) -// throws SchemaException { -// PrismContainerDefinition activationDefinition = getActivationDefinition(); -// -// // We always want explicit delta for effective status even if there is no real change -// // we want to propagate enable/disable events to all the resources, even if we are enabling -// // already enabled user (some resources may be disabled) -// // This may produce duplicate delta, but that does not matter too much. The duplicate delta -// // will be filtered out later. -// PrismPropertyDefinition effectiveStatusDef = activationDefinition.findPropertyDefinition(ActivationType.F_EFFECTIVE_STATUS); -// PropertyDelta effectiveStatusDelta -// = effectiveStatusDef.createEmptyDelta(PATH_ACTIVATION_EFFECTIVE_STATUS); -// effectiveStatusDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(effectiveStatusNew, OriginType.USER_POLICY, null)); -// if (!focusContext.alreadyHasDelta(effectiveStatusDelta)){ -// focusContext.swallowToProjectionWaveSecondaryDelta(effectiveStatusDelta); -// } -// -// // It is not enough to check alreadyHasDelta(). The change may happen in previous waves -// // and the secondary delta may no longer be here. When it comes to disableTimestamp we even -// // cannot rely on natural filtering of already executed deltas as the timestamp here may -// // be off by several milliseconds. So explicitly check for the change here. -// PrismObject objectCurrent = focusContext.getObjectCurrent(); -// if (objectCurrent != null) { -// PrismProperty effectiveStatusPropCurrent = objectCurrent.findProperty(PATH_ACTIVATION_EFFECTIVE_STATUS); -// if (effectiveStatusPropCurrent != null && effectiveStatusNew.equals(effectiveStatusPropCurrent.getRealValue())) { -// LOGGER.trace("Skipping setting disableTimestamp because there was no change"); -// return; -// } -// } -// -// PropertyDelta timestampDelta = LensUtil.createActivationTimestampDelta(effectiveStatusNew, now, activationDefinition, OriginType.USER_POLICY, -// prismContext); -// if (!focusContext.alreadyHasDelta(timestampDelta)) { -// focusContext.swallowToProjectionWaveSecondaryDelta(timestampDelta); -// } -// } -// -// -// private PrismContainerDefinition getActivationDefinition() { -// if (activationDefinition == null) { -// ComplexTypeDefinition focusDefinition = prismContext.getSchemaRegistry().findComplexTypeDefinition(FocusType.COMPLEX_TYPE); -// activationDefinition = focusDefinition.findContainerDefinition(FocusType.F_ACTIVATION); -// } -// return activationDefinition; -// } -// -// private PrismPropertyDefinition getFailedLoginsDefinition() { -// if (failedLoginsDefinition == null) { -// PrismObjectDefinition userDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(UserType.class); -// failedLoginsDefinition = userDef.findPropertyDefinition(SchemaConstants.PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS); -// } -// return failedLoginsDefinition; -// } - - /** - * Adds deltas for iteration and iterationToken to the focus if needed. - */ - private void addIterationTokenDeltas(LensFocusContext focusContext, int iteration, String iterationToken) throws SchemaException { - PrismObject objectCurrent = focusContext.getObjectCurrent(); - if (objectCurrent != null) { - Integer iterationOld = objectCurrent.asObjectable().getIteration(); - String iterationTokenOld = objectCurrent.asObjectable().getIterationToken(); - if (iterationOld != null && iterationOld == iteration && - iterationTokenOld != null && iterationTokenOld.equals(iterationToken)) { - // Already stored - return; - } - } - PrismObjectDefinition objDef = focusContext.getObjectDefinition(); - - PrismPropertyValue iterationVal = prismContext.itemFactory().createPropertyValue(iteration); - iterationVal.setOriginType(OriginType.USER_POLICY); - PropertyDelta iterationDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, - FocusType.F_ITERATION, iterationVal); - focusContext.swallowToSecondaryDelta(iterationDelta); - - PrismPropertyValue iterationTokenVal = prismContext.itemFactory().createPropertyValue(iterationToken); - iterationTokenVal.setOriginType(OriginType.USER_POLICY); - PropertyDelta iterationTokenDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, - FocusType.F_ITERATION_TOKEN, iterationTokenVal); - focusContext.swallowToSecondaryDelta(iterationTokenDelta); - + ExpressionFactory getExpressionFactory() { + return expressionFactory; } - private void checkArchetypeRefDelta(LensContext context) throws PolicyViolationException { - ObjectDelta focusPrimaryDelta = context.getFocusContext().getPrimaryDelta(); - if (focusPrimaryDelta != null) { - ReferenceDelta archetypeRefDelta = focusPrimaryDelta.findReferenceModification(AssignmentHolderType.F_ARCHETYPE_REF); - if (archetypeRefDelta != null) { - // We want to allow this under special circumstances. E.g. we want be able to import user with archetypeRef. - // Otherwise we won't be able to export a user and re-import it again. - if (focusPrimaryDelta.isAdd()) { - String archetypeOidFromAssignments = LensUtil.determineExplicitArchetypeOidFromAssignments(focusPrimaryDelta.getObjectToAdd()); - if (archetypeOidFromAssignments == null) { - throw new PolicyViolationException("Attempt add archetypeRef without a matching assignment"); - } else { - boolean match = true; - for (PrismReferenceValue archetypeRefDeltaVal : archetypeRefDelta.getValuesToAdd()) { - if (!archetypeOidFromAssignments.equals(archetypeRefDeltaVal.getOid())) { - match = false; - } - } - if (match) { - return; - } else { - throw new PolicyViolationException("Attempt add archetypeRef that does not match assignment"); - } - } - } - throw new PolicyViolationException("Attempt to modify archetypeRef directly"); - } - } + PrismContext getPrismContext() { + return prismContext; } -// private void processAssignmentActivation(LensContext context, XMLGregorianCalendar now, -// OperationResult result) throws SchemaException { -// DeltaSetTriple> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple(); -// if (evaluatedAssignmentTriple == null) { -// // Code path that should not normally happen. But is used in some tests and may -// // happen during partial processing. -// return; -// } -// // We care only about existing assignments here. New assignments will be taken care of in the executor -// // (OperationalDataProcessor). And why care about deleted assignments? -// Collection> zeroSet = evaluatedAssignmentTriple.getZeroSet(); -// if (zeroSet == null) { -// return; -// } -// LensFocusContext focusContext = context.getFocusContext(); -// for (EvaluatedAssignmentImpl evaluatedAssignment: zeroSet) { -// if (evaluatedAssignment.isVirtual()) { -// continue; -// } -// AssignmentType assignmentType = evaluatedAssignment.getAssignmentType(); -// ActivationType currentActivationType = assignmentType.getActivation(); -// ActivationStatusType expectedEffectiveStatus = activationComputer.getEffectiveStatus(assignmentType.getLifecycleState(), currentActivationType, null); -// if (currentActivationType == null) { -// PrismContainerDefinition activationDef = focusContext.getObjectDefinition().findContainerDefinition(SchemaConstants.PATH_ASSIGNMENT_ACTIVATION); -// ContainerDelta activationDelta = activationDef.createEmptyDelta( -// ItemPath.create(FocusType.F_ASSIGNMENT, assignmentType.getId(), AssignmentType.F_ACTIVATION)); -// ActivationType newActivationType = new ActivationType(); -// activationDelta.setValuesToReplace(newActivationType.asPrismContainerValue()); -// newActivationType.setEffectiveStatus(expectedEffectiveStatus); -// focusContext.swallowToSecondaryDelta(activationDelta); -// } else { -// ActivationStatusType currentEffectiveStatus = currentActivationType.getEffectiveStatus(); -// if (!expectedEffectiveStatus.equals(currentEffectiveStatus)) { -// PrismPropertyDefinition effectiveStatusPropertyDef = focusContext.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_ASSIGNMENT_ACTIVATION_EFFECTIVE_STATUS); -// PropertyDelta effectiveStatusDelta = effectiveStatusPropertyDef.createEmptyDelta( -// ItemPath.create(FocusType.F_ASSIGNMENT, assignmentType.getId(), AssignmentType.F_ACTIVATION, ActivationType.F_EFFECTIVE_STATUS)); -// effectiveStatusDelta.setRealValuesToReplace(expectedEffectiveStatus); -// focusContext.swallowToSecondaryDelta(effectiveStatusDelta); -// } -// } -// } -// } - + RepositoryService getCacheRepositoryService() { + return cacheRepositoryService; + } + CacheConfigurationManager getCacheConfigurationManager() { + return cacheConfigurationManager; + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java index 4830a18b583..0e2e3b59ad3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java @@ -16,8 +16,11 @@ import com.evolveum.midpoint.model.impl.lens.projector.ComplexConstructionConsumer; import com.evolveum.midpoint.model.impl.lens.projector.ConstructionProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; @@ -89,7 +92,8 @@ * @author Radovan Semancik */ @Component -public class AssignmentProcessor { +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class) +public class AssignmentProcessor implements ProjectorProcessor { @Autowired @Qualifier("cacheRepositoryService") @@ -118,22 +122,16 @@ public class AssignmentProcessor { /** * Processing all the assignments. */ - @SuppressWarnings("unchecked") + @ProcessorMethod public void processAssignments(LensContext context, XMLGregorianCalendar now, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for AssignmentHolderType. - return; - } + assert context.hasFocusOfType(AssignmentHolderType.class); OperationResult result = parentResult.createSubresult(AssignmentProcessor.class.getName() + ".processAssignments"); try { try { + //noinspection unchecked processAssignmentsProjectionsWithFocus((LensContext) context, now, task, result); } catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException | PolicyViolationException | CommunicationException | ConfigurationException | SecurityViolationException | RuntimeException | Error e) { @@ -263,7 +261,7 @@ private void processAssig LOGGER.trace("Projection processing done"); - removeIgnoredContexts(context); + context.removeIgnoredContexts(); finishLegalDecisions(context); } else { LOGGER.trace("Skipping evaluating constructions. Not a focus."); @@ -797,12 +795,12 @@ private void createAssignmentDelta(LensContext< } - public void processOrgAssignments(LensContext context, Task task, OperationResult result) throws SchemaException, PolicyViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - + @ProcessorMethod + void processOrgAssignments(LensContext context, + @SuppressWarnings("unused") XMLGregorianCalendar now, Task task, + OperationResult result) throws SchemaException, PolicyViolationException, ObjectNotFoundException, + CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } Collection shouldBeParentOrgRefs = new ArrayList<>(); @@ -894,41 +892,6 @@ private void computeTenantRef(LensContext context, Tas addTenantRefDelta(context, objectNew, tenantOid); } - /** - * This is somehow "future legacy" code. It will be removed later when we have better support for organizational structure - * membership in resources and tasks. - */ - public void computeTenantRefLegacy(LensContext context, Task task, OperationResult result) throws PolicyViolationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - String tenantOid = null; - - LensFocusContext focusContext = context.getFocusContext(); - PrismObject objectNew = focusContext.getObjectNew(); - if (objectNew == null) { - return; - } - if (!objectNew.canRepresent(ResourceType.class) && !objectNew.canRepresent(TaskType.class)) { - return; - } - - String desc = "parentOrgRef in "+objectNew; - for (ObjectReferenceType parentOrgRef: objectNew.asObjectable().getParentOrgRef()) { - OrgType parentOrg = objectResolver.resolve(parentOrgRef, OrgType.class, null, desc, task, result); - ObjectReferenceType parentTenantRef = parentOrg.getTenantRef(); - if (parentTenantRef == null || parentTenantRef.getOid() == null) { - continue; - } - if (tenantOid == null) { - tenantOid = parentTenantRef.getOid(); - } else { - if (!parentTenantRef.getOid().equals(tenantOid)) { - throw new PolicyViolationException("Two different tenants ("+tenantOid+", "+parentTenantRef.getOid()+") applicable to "+context.getFocusContext().getHumanReadableName()); - } - } - } - - addTenantRefDelta(context, objectNew, tenantOid); - } - private void addTenantRefDelta(LensContext context, PrismObject objectNew, String tenantOid) throws SchemaException { LensFocusContext focusContext = context.getFocusContext(); ObjectReferenceType currentTenantRef = objectNew.asObjectable().getTenantRef(); @@ -956,10 +919,13 @@ private void addTenantRefDelta(LensContext context, Pr } } - public void checkForAssignmentConflicts(LensContext context, - OperationResult result) throws PolicyViolationException, SchemaException { + @ProcessorMethod + void checkForAssignmentConflicts(LensContext context, + @SuppressWarnings("unused") XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, + @SuppressWarnings("unused") OperationResult result) throws PolicyViolationException, SchemaException { for(LensProjectionContext projectionContext: context.getProjectionContexts()) { - if (AssignmentPolicyEnforcementType.NONE == projectionContext.getAssignmentPolicyEnforcementType()){ + if (AssignmentPolicyEnforcementType.NONE == projectionContext.getAssignmentPolicyEnforcementType()) { continue; } if (projectionContext.isTombstone()) { @@ -1019,20 +985,6 @@ private void markPolicyDecision(LensProjectionContext accountSyncContext, Synchr } } - - - public void removeIgnoredContexts(LensContext context) { - Collection projectionContexts = context.getProjectionContexts(); - Iterator projectionIterator = projectionContexts.iterator(); - while (projectionIterator.hasNext()) { - LensProjectionContext projectionContext = projectionIterator.next(); - - if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { - projectionIterator.remove(); - } - } - } - private XMLGregorianCalendar collectFocusTripleFromMappings( Collection> evaluatedAssignments, Map>> outputTripleMap, @@ -1079,12 +1031,14 @@ private void processMembershipAndDelegatedRefs(LensContext context, - OperationResult result) throws SchemaException, PolicyViolationException, ConfigurationException { + @ProcessorMethod + void processMembershipAndDelegatedRefs(LensContext context, + @SuppressWarnings("unused") XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, + @SuppressWarnings("unused") OperationResult result) + throws SchemaException, ConfigurationException { + assert context.hasFocusOfType(AssignmentHolderType.class); LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null || !AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - return; - } Collection shouldBeRoleRefs = new ArrayList<>(); Collection shouldBeDelegatedRefs = new ArrayList<>(); @@ -1110,6 +1064,8 @@ public void processMembershipAndDelegatedRefs(LensContext setReferences(focusContext, AssignmentHolderType.F_ROLE_MEMBERSHIP_REF, shouldBeRoleRefs); setReferences(focusContext, AssignmentHolderType.F_DELEGATED_REF, shouldBeDelegatedRefs); setReferences(focusContext, AssignmentHolderType.F_ARCHETYPE_REF, shouldBeArchetypeRefs); + + context.recompute(); // really needed? } private void setReferences(LensFocusContext focusContext, QName name, diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java similarity index 82% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusProcessor.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java index e2252689d0d..b197acdb1fc 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java @@ -12,6 +12,12 @@ import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; + +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; +import com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -20,7 +26,6 @@ import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; import com.evolveum.midpoint.prism.ComplexTypeDefinition; import com.evolveum.midpoint.prism.OriginType; import com.evolveum.midpoint.prism.PrismContainerDefinition; @@ -37,13 +42,7 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractCredentialType; @@ -64,73 +63,46 @@ * */ @Component -public class FocusProcessor { +// shouldn't we skip on "secondary delete" as well? +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenFocusDeleted = SkipWhenFocusDeleted.PRIMARY) +public class FocusActivationProcessor implements ProjectorProcessor { - private static final Trace LOGGER = TraceManager.getTrace(FocusProcessor.class); + private static final Trace LOGGER = TraceManager.getTrace(FocusActivationProcessor.class); private PrismContainerDefinition activationDefinition; private PrismPropertyDefinition failedLoginsDefinition; @Autowired private PrismContext prismContext; - @Autowired private CredentialsProcessor credentialsProcessor; @Autowired private ActivationComputer activationComputer; - void processActivationBeforeAssignments(LensContext context, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - - if (isFocus(context)) { - //noinspection unchecked - processActivationBasic((LensContext) context, now, result); - } else { - LOGGER.trace("Skipping activation processing. Not a focus."); - } + @ProcessorMethod + void processActivationBeforeObjectTemplate(LensContext context, XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, @SuppressWarnings("unused") OperationResult result) throws SchemaException { + processActivationBasic(context, now); } - private boolean isFocus(LensContext context) { - LensFocusContext focusContext = context.getFocusContext(); - return focusContext != null && focusContext.represents(FocusType.class); + @ProcessorMethod + void processActivationAfterObjectTemplate(LensContext context, XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, @SuppressWarnings("unused") OperationResult result) throws SchemaException { + processActivationBasic(context, now); + processAssignmentActivation(context); // really? } - void processActivationAfterAssignments(LensContext context, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - - processActivationBeforeAssignments(context, now, result); - - processAssignmentActivation(context, now, result); + @ProcessorMethod + void processActivationAfterAssignments(LensContext context, XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, @SuppressWarnings("unused") OperationResult result) throws SchemaException { + processActivationBasic(context, now); + processAssignmentActivation(context); } - void processCredentials(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (isFocus(context)) { - //noinspection unchecked - credentialsProcessor.processFocusCredentials((LensContext) context, now, task, result); - } else { - LOGGER.trace("Skipping credentials processing. Not a focus."); - } - } - private void processActivationBasic(LensContext context, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + private void processActivationBasic(LensContext context, XMLGregorianCalendar now) + throws SchemaException { LensFocusContext focusContext = context.getFocusContext(); - - if (focusContext.isDelete()) { - LOGGER.trace("Skipping processing of focus activation: focus delete"); - return; - } - - processActivationAdministrativeAndValidity(focusContext, now, result); - - if (focusContext.represents(UserType.class)) { - //noinspection unchecked - processActivationLockout((LensFocusContext) focusContext, now, result); - } + processActivationAdministrativeAndValidity(focusContext, now); + processActivationLockout(focusContext); } - private void processAssignmentActivation(LensContext context, XMLGregorianCalendar now, - OperationResult result) throws SchemaException { + private void processAssignmentActivation(LensContext context) throws SchemaException { DeltaSetTriple> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple(); if (evaluatedAssignmentTriple == null) { // Code path that should not normally happen. But is used in some tests and may @@ -161,9 +133,7 @@ private void processAssignmentActivation(LensCo } } - private void processActivationAdministrativeAndValidity(LensFocusContext focusContext, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + private void processActivationAdministrativeAndValidity(LensFocusContext focusContext, XMLGregorianCalendar now) throws SchemaException { TimeIntervalStatusType validityStatusNew = null; TimeIntervalStatusType validityStatusCurrent = null; @@ -224,7 +194,7 @@ private void processActivationAdministrativeAndValidity(Le // some of the mappings will use effectiveStatus as a source, therefore there has to be a delta for the mapping to work correctly recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); } else { - //check computed effective status current with the saved one - e.g. there can be some inconsistencies so we need to check and force the change.. in other cases, effectvie status will be stored with + //check computed effective status current with the saved one - e.g. there can be some inconsistencies so we need to check and force the change.. in other cases, effective status will be stored with // incorrect value. Maybe another option is to not compute effectiveStatusCurrent if there is an existing (saved) effective status in the user.. TODO if (activationCurrent != null && activationCurrent.getEffectiveStatus() != null) { ActivationStatusType effectiveStatusSaved = activationCurrent.getEffectiveStatus(); @@ -239,15 +209,10 @@ private void processActivationAdministrativeAndValidity(Le LOGGER.trace("Effective status change {} -> {}", effectiveStatusCurrent, effectiveStatusNew); recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); } - - } - private void processActivationLockout(LensFocusContext focusContext, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - - ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); + private void processActivationLockout(LensFocusContext focusContext) throws SchemaException { + ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); if (focusPrimaryDelta != null) { PropertyDelta lockoutStatusDelta = focusContext.getPrimaryDelta().findPropertyDelta(SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS); if (lockoutStatusDelta != null) { @@ -268,12 +233,12 @@ private void processActivationLockout(LensFocusContext focusNew = focusContext.getObjectNew(); + PrismObject focusNew = focusContext.getObjectNew(); if (focusNew != null) { activationNew = focusNew.asObjectable().getActivation(); if (activationNew != null) { @@ -281,7 +246,7 @@ private void processActivationLockout(LensFocusContext focusCurrent = focusContext.getObjectCurrent(); + PrismObject focusCurrent = focusContext.getObjectCurrent(); if (focusCurrent != null) { activationCurrent = focusCurrent.asObjectable().getActivation(); if (activationCurrent != null) { @@ -306,7 +271,7 @@ private void processActivationLockout(LensFocusContext activationDefinition = getActivationDefinition(); PrismPropertyDefinition lockoutExpirationTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP); PropertyDelta lockoutExpirationTimestampDelta @@ -315,10 +280,10 @@ private void processActivationLockout(LensFocusContext focusContext, AbstractCredentialType credentialTypeNew, ItemPath path) throws SchemaException{ + private void resetFailedLogins(LensFocusContext focusContext, AbstractCredentialType credentialTypeNew, ItemPath path) + throws SchemaException { if (credentialTypeNew != null) { Integer failedLogins = credentialTypeNew.getFailedLogins(); if (failedLogins != null && failedLogins != 0) { @@ -406,6 +371,4 @@ private PrismPropertyDefinition getFailedLoginsDefinition() { } return failedLoginsDefinition; } - - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java index b1dff7232c4..6bc56e5a400 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java @@ -1,193 +1,177 @@ -/* - * Copyright (c) 2010-2019 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ - -package com.evolveum.midpoint.model.impl.lens.projector.focus; - -import java.util.*; - -import javax.xml.datatype.XMLGregorianCalendar; - -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.LifecycleUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.exception.CommunicationException; -import com.evolveum.midpoint.util.exception.ConfigurationException; -import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; -import com.evolveum.midpoint.util.exception.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; - -/** - * @author Radovan Semancik - */ -@Component -public class FocusLifecycleProcessor { - - @Autowired private ExpressionFactory expressionFactory; - - private static final Trace LOGGER = TraceManager.getTrace(FocusLifecycleProcessor.class); - - public void processLifecycle(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for FocusType. - return; - } - - //noinspection unchecked - processLifecycleWithFocus((LensContext)context, now, task, result); - } - - private void processLifecycleWithFocus(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - ObjectDelta focusDelta = focusContext.getDelta(); - - if (focusDelta != null && focusDelta.isDelete()) { - LOGGER.trace("Skipping lifecycle processing because of focus delete"); - return; - } - - LifecycleStateModelType lifecycleStateModel = focusContext.getLifecycleModel(); - if (lifecycleStateModel == null) { - LOGGER.trace("Skipping lifecycle processing because there is no lifecycle state model for focus"); - return; - } - - PrismObject objectNew = focusContext.getObjectNew(); - String startLifecycleState = objectNew.asObjectable().getLifecycleState(); - if (startLifecycleState == null) { - startLifecycleState = SchemaConstants.LIFECYCLE_ACTIVE; - } - - LifecycleStateType startStateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, startLifecycleState); - if (startStateType == null) { - LOGGER.trace("Skipping lifecycle processing because there is no specification for lifecycle state {}", startLifecycleState); - return; - } - - for (LifecycleStateTransitionType transitionType : startStateType.getTransition()) { - String targetLifecycleState = transitionType.getTargetState(); - if (shouldTransition(context, transitionType, targetLifecycleState, task, result)) { - executeExitActions(context, lifecycleStateModel, startLifecycleState, now, task, result); - LOGGER.debug("Lifecycle state transition of {}: {} -> {}", objectNew, startLifecycleState, targetLifecycleState); - recordLifecycleTransitionDelta(focusContext, targetLifecycleState); - executeEntryActions(context, lifecycleStateModel, targetLifecycleState, now, task, result); - LOGGER.trace("Lifecycle state transition of {} from {} to {} done", objectNew, startLifecycleState, targetLifecycleState); - break; - } - } - } - - private boolean shouldTransition(LensContext context, LifecycleStateTransitionType transitionType, String targetLifecycleState, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType conditionExpressionType = transitionType.getCondition(); - if (conditionExpressionType == null) { - return false; - } - String desc = "condition for transition to state "+targetLifecycleState+" for "+context.getFocusContext().getHumanReadableName(); - - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_OBJECT, context.getFocusContext().getObjectNew(), context.getFocusContext().getObjectNew().getDefinition()); - // TODO: more variables? - - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( - conditionExpressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), - MiscSchemaUtil.getExpressionProfile(), desc, task, result); - ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); - ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); - PrismValueDeltaSetTriple> outputTriple = - ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); - PrismPropertyValue expressionOutputValue = ExpressionUtil.getExpressionOutputValue(outputTriple, desc); - return ExpressionUtil.getBooleanConditionOutput(expressionOutputValue); - } - - private void recordLifecycleTransitionDelta(LensFocusContext focusContext, String targetLifecycleState) throws SchemaException { - PropertyDelta lifecycleDelta = focusContext.getPrismContext().deltaFactory().property() - .createModificationReplaceProperty(ObjectType.F_LIFECYCLE_STATE, focusContext.getObjectDefinition(), - targetLifecycleState); - focusContext.swallowToSecondaryDelta(lifecycleDelta); - } - - private void executeEntryActions(LensContext context, LifecycleStateModelType lifecycleStateModel, - String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); - if (stateType == null) { - return; - } - executeStateActions(context, targetLifecycleState, stateType.getEntryAction(), "entry", now, task, result); - } - - private void executeExitActions(LensContext context, LifecycleStateModelType lifecycleStateModel, - String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); - if (stateType == null) { - return; - } - executeStateActions(context, targetLifecycleState, stateType.getExitAction(), "exit", now, task, result); - } - - private void executeStateActions(LensContext context, String targetLifecycleState, - List actions, String actionTypeDesc, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - for (LifecycleStateActionType action: actions) { - LOGGER.trace("Execute {} action {} for state {} of {}", actionTypeDesc, action.getName(), targetLifecycleState, context.getFocusContext().getObjectNew()); - executeDataReduction(context, action.getDataReduction(), now, task, result); - } - } - - private void executeDataReduction(LensContext context, LifecycleStateActionDataReductionType dataReduction, - XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - if (dataReduction == null) { - return; - } - LensFocusContext focusContext = context.getFocusContext(); - PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); - for (ItemPathType purgeItemPathType : dataReduction.getPurgeItem()) { - ItemPath purgeItemPath = purgeItemPathType.getItemPath(); - LOGGER.trace("Purging item {} from {}", purgeItemPath, focusContext.getObjectNew()); - ItemDefinition purgeItemDef = focusDefinition.findItemDefinition(purgeItemPath); - ItemDelta purgeItemDelta = purgeItemDef.createEmptyDelta(purgeItemPath); - purgeItemDelta.setValueToReplace(); - focusContext.swallowToSecondaryDelta(purgeItemDelta); - } - } - -} +/* + * Copyright (c) 2010-2019 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.focus; + +import java.util.*; + +import javax.xml.datatype.XMLGregorianCalendar; + +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.LifecycleUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.CommunicationException; +import com.evolveum.midpoint.util.exception.ConfigurationException; +import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +import static com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted.PRIMARY_OR_SECONDARY; + +/** + * @author Radovan Semancik + */ +@Component +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class, skipWhenFocusDeleted = PRIMARY_OR_SECONDARY) +public class FocusLifecycleProcessor implements ProjectorProcessor { + + @Autowired private ExpressionFactory expressionFactory; + + private static final Trace LOGGER = TraceManager.getTrace(FocusLifecycleProcessor.class); + + @ProcessorMethod + public void process(LensContext context, XMLGregorianCalendar now, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, + CommunicationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + + LifecycleStateModelType lifecycleStateModel = focusContext.getLifecycleModel(); + if (lifecycleStateModel == null) { + LOGGER.trace("Skipping lifecycle processing because there is no lifecycle state model for focus"); + return; + } + + PrismObject objectNew = focusContext.getObjectNew(); + String startLifecycleState = objectNew.asObjectable().getLifecycleState(); + if (startLifecycleState == null) { + startLifecycleState = SchemaConstants.LIFECYCLE_ACTIVE; + } + + LifecycleStateType startStateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, startLifecycleState); + if (startStateType == null) { + LOGGER.trace("Skipping lifecycle processing because there is no specification for lifecycle state {}", startLifecycleState); + return; + } + + for (LifecycleStateTransitionType transitionType : startStateType.getTransition()) { + String targetLifecycleState = transitionType.getTargetState(); + if (shouldTransition(context, transitionType, targetLifecycleState, task, result)) { + executeExitActions(context, lifecycleStateModel, startLifecycleState, now, task, result); + LOGGER.debug("Lifecycle state transition of {}: {} -> {}", objectNew, startLifecycleState, targetLifecycleState); + recordLifecycleTransitionDelta(focusContext, targetLifecycleState); + executeEntryActions(context, lifecycleStateModel, targetLifecycleState, now, task, result); + LOGGER.trace("Lifecycle state transition of {} from {} to {} done", objectNew, startLifecycleState, targetLifecycleState); + break; + } + } + } + + private boolean shouldTransition(LensContext context, LifecycleStateTransitionType transitionType, String targetLifecycleState, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + ExpressionType conditionExpressionType = transitionType.getCondition(); + if (conditionExpressionType == null) { + return false; + } + String desc = "condition for transition to state "+targetLifecycleState+" for "+context.getFocusContext().getHumanReadableName(); + + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_OBJECT, context.getFocusContext().getObjectNew(), context.getFocusContext().getObjectNew().getDefinition()); + // TODO: more variables? + + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( + conditionExpressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), + MiscSchemaUtil.getExpressionProfile(), desc, task, result); + ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); + ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); + PrismValueDeltaSetTriple> outputTriple = + ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); + PrismPropertyValue expressionOutputValue = ExpressionUtil.getExpressionOutputValue(outputTriple, desc); + return ExpressionUtil.getBooleanConditionOutput(expressionOutputValue); + } + + private void recordLifecycleTransitionDelta(LensFocusContext focusContext, String targetLifecycleState) throws SchemaException { + PropertyDelta lifecycleDelta = focusContext.getPrismContext().deltaFactory().property() + .createModificationReplaceProperty(ObjectType.F_LIFECYCLE_STATE, focusContext.getObjectDefinition(), + targetLifecycleState); + focusContext.swallowToSecondaryDelta(lifecycleDelta); + } + + private void executeEntryActions(LensContext context, LifecycleStateModelType lifecycleStateModel, + String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); + if (stateType == null) { + return; + } + executeStateActions(context, targetLifecycleState, stateType.getEntryAction(), "entry", now, task, result); + } + + private void executeExitActions(LensContext context, LifecycleStateModelType lifecycleStateModel, + String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); + if (stateType == null) { + return; + } + executeStateActions(context, targetLifecycleState, stateType.getExitAction(), "exit", now, task, result); + } + + private void executeStateActions(LensContext context, String targetLifecycleState, + List actions, String actionTypeDesc, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + for (LifecycleStateActionType action: actions) { + LOGGER.trace("Execute {} action {} for state {} of {}", actionTypeDesc, action.getName(), targetLifecycleState, context.getFocusContext().getObjectNew()); + executeDataReduction(context, action.getDataReduction()); + } + } + + private void executeDataReduction(LensContext context, LifecycleStateActionDataReductionType dataReduction) + throws SchemaException { + if (dataReduction == null) { + return; + } + LensFocusContext focusContext = context.getFocusContext(); + PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); + for (ItemPathType purgeItemPathType : dataReduction.getPurgeItem()) { + ItemPath purgeItemPath = purgeItemPathType.getItemPath(); + LOGGER.trace("Purging item {} from {}", purgeItemPath, focusContext.getObjectNew()); + ItemDefinition purgeItemDef = focusDefinition.findItemDefinition(purgeItemPath); + ItemDelta purgeItemDelta = purgeItemDef.createEmptyDelta(purgeItemPath); + purgeItemDelta.setValueToReplace(); + focusContext.swallowToSecondaryDelta(purgeItemDelta); + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java index e723fae42f0..8b98cca4f3f 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 @@ -20,6 +20,9 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; @@ -72,6 +75,8 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import static com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted.PRIMARY_OR_SECONDARY; + /** * Processor that takes changes from accounts and synchronization deltas and updates user attributes if necessary * (by creating secondary user object delta {@link ObjectDelta}). @@ -80,7 +85,8 @@ * @author Radovan Semancik */ @Component -public class InboundProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenFocusDeleted = PRIMARY_OR_SECONDARY) +public class InboundProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(InboundProcessor.class); @@ -90,47 +96,15 @@ public class InboundProcessor { @Autowired private CredentialsProcessor credentialsProcessor; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private Protector protector; + @Autowired private ClockworkMedic medic; @Autowired private ProvisioningService provisioningService; private PrismContainerDefinition associationContainerDefinition; -// private Map>> mappingsToTarget; - - void processInbound(LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, CommunicationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping inbound because there is no focus"); - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focus types. - LOGGER.trace("Skipping inbound because {} is not focal type", focusContext.getObjectTypeClass()); - return; - } - //noinspection unchecked - processInboundFocal((LensContext)context, task, now, result); - } - - private void processInboundFocal(LensContext context, Task task, XMLGregorianCalendar now, - OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, CommunicationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping inbound processing because focus is null"); - return; - } - if (focusContext.isDelete()) { - LOGGER.trace("Skipping inbound processing because focus is being deleted"); - return; - } - - ObjectDelta userSecondaryDelta = focusContext.getProjectionWaveSecondaryDelta(); - - if (userSecondaryDelta != null && ChangeType.DELETE.equals(userSecondaryDelta.getChangeType())) { - //we don't need to do inbound if we are deleting this user - LOGGER.trace("Skipping inbound processing because focus is being deleted (secondary delta)"); - return; - } - + @ProcessorMethod + void processInbounds(LensContext context, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, + CommunicationException, SecurityViolationException { // Used to collect all the mappings from all the projects, sorted by target property. // Motivation: we need to evaluate them together, e.g. in case that there are several mappings // from several projections targeting the same property. @@ -174,6 +148,17 @@ private void processInboundFocal(LensContext context, T } evaluateInboundMapping(mappingsToTarget, context, task, result); + + context.checkConsistenceIfNeeded(); + context.recomputeFocus(); + medic.traceContext(LOGGER, activityDescription, "inbound", false, context, false); + + // It's actually a bit questionable if such cross-components interactions should be treated like this + // or in some higher-level component. But let's try this approach until something nicer is found. + contextLoader.updateArchetypePolicy(context, task, result); + contextLoader.updateArchetype(context, task, result); + contextLoader.updateFocusTemplate(context, result); + context.checkConsistenceIfNeeded(); } private boolean isDeleteAccountDelta(LensProjectionContext accountContext) throws SchemaException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java new file mode 100644 index 00000000000..a296fd60b85 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.focus; + +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.path.UniformItemPath; +import com.evolveum.midpoint.prism.util.DefinitionUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateItemDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PropertyLimitationsType; + +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static org.apache.commons.lang3.BooleanUtils.isTrue; + +/** + * Checks item limitations. + * + * This is a preliminary implementation; to be merged with something related. + */ +@Component +public class ItemLimitationsChecker { + + /** + * @pre Focus context is recomputed. + */ + void checkItemsLimitations(LensFocusContext focusContext) throws SchemaException { + Map itemDefinitionsMap = focusContext.getItemDefinitionsMap(); + PrismObject objectNew = focusContext.getObjectNew(); + if (objectNew == null) { + return; // nothing to check on DELETE operation + } + for (Map.Entry entry : itemDefinitionsMap.entrySet()) { + for (PropertyLimitationsType limitation : entry.getValue().getLimitations()) { + if (!limitation.getLayer().contains(LayerType.MODEL)) { // or should we apply SCHEMA-layer limitations as well? + continue; + } + checkItemLimitations(objectNew, entry.getKey(), limitation); + } + } + } + + private void checkItemLimitations(PrismObject object, ItemPath path, PropertyLimitationsType limitation) + throws SchemaException { + Object item = object.find(path); + if (isTrue(limitation.isIgnore())) { + return; + } + int count = getValueCount(item); + Integer min = DefinitionUtil.parseMultiplicity(limitation.getMinOccurs()); + if (min != null && min > 0 && count < min) { + throw new SchemaException("Expected at least " + min + " values of " + path + ", got " + count); + } + Integer max = DefinitionUtil.parseMultiplicity(limitation.getMaxOccurs()); + if (max != null && max >= 0 && count > max) { + throw new SchemaException("Expected at most " + max + " values of " + path + ", got " + count); + } + } + + private int getValueCount(Object item) { + if (item == null) { + return 0; + } + if (!(item instanceof Item)) { + throw new IllegalStateException("Expected Item but got " + item.getClass() + " instead"); + } + return ((Item) item).getValues().size(); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java new file mode 100644 index 00000000000..00ce12e9b0d --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2020 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ + +package com.evolveum.midpoint.model.impl.lens.projector.focus; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PropertyDelta; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; + +import org.jetbrains.annotations.NotNull; + +/** + * Helps AssignmentHolderProcessor with iteration-related activities. + * 1. keeps iteration state + * 2. evaluates pre/post and uniqueness conditions (including name presence) + * 3. manages fetching and storing of iteration information from/to lens context + * + * And nothing more. + */ +class IterationHelper { + + private static final Trace LOGGER = TraceManager.getTrace(IterationHelper.class); + + @NotNull private final AssignmentHolderProcessor assignmentHolderProcessor; + @NotNull private final LensContext context; + @NotNull private final LensFocusContext focusContext; + + /** + * Current iteration specification. Might change during the processing! + */ + private IterationSpecificationType iterationSpecification; + + /** + * We initialize iteration specification lazily. So this is the flag. + */ + private boolean iterationSpecificationInitialized; + + /** + * Currently we allow only a single reset when iteration specification changes + * (to avoid endless loops, perhaps). + */ + private boolean wasResetOnIterationSpecificationChange; + + /** + * Current iteration number. + */ + private int iteration; + + /** + * Current iteration token. + */ + private String iterationToken; + + /** + * We allow only single reset of iteration counter (except when iteration specification + * changes). So this is the flag. + */ + private boolean wasResetIterationCounter; + + /** + * Upper iterations limit. + */ + private int maxIterations; + + /** + * Lazily evaluated pre-iteration variables. + */ + private ExpressionVariables variablesPreIteration; + + /** + * Message about re-iteration reason. + */ + private String reIterationReason; + + private static final boolean RESET_ON_RENAME = true; // make configurable some day + + IterationHelper(@NotNull AssignmentHolderProcessor assignmentHolderProcessor, @NotNull LensContext context, + @NotNull LensFocusContext focusContext) { + this.assignmentHolderProcessor = assignmentHolderProcessor; + this.context = context; + this.focusContext = focusContext; + iteration = focusContext.getIteration(); + iterationToken = focusContext.getIterationToken(); + PrismObject focusCurrent = focusContext.getObjectCurrent(); + if (focusCurrent != null && iterationToken == null) { + Integer focusIteration = focusCurrent.asObjectable().getIteration(); + if (focusIteration != null) { + iteration = focusIteration; + } + iterationToken = focusCurrent.asObjectable().getIterationToken(); + } + } + + void onIterationStart(Task task, OperationResult result) throws CommunicationException, ObjectNotFoundException, + SchemaException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + reIterationReason = null; + initializeIterationSpecificationIfNeeded(); + variablesPreIteration = null; + computeIterationTokenIfNeeded(task, result); + rememberIterationToken(); + LOGGER.trace("Focus {} processing, iteration {}, token '{}'", focusContext.getHumanReadableName(), iteration, iterationToken); + } + + private void computeIterationTokenIfNeeded(Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException { + if (iterationToken == null) { + createVariablesPreIterationIfNeeded(); + iterationToken = LensUtil.formatIterationToken(context, focusContext, + iterationSpecification, iteration, assignmentHolderProcessor.getExpressionFactory(), variablesPreIteration, task, result); + } + } + + private void rememberIterationToken() { + // We have to remember the token and iteration in the context. + // The context can be recomputed several times. But we always want + // to use the same iterationToken if possible. If there is a random + // part in the iterationToken expression that we need to avoid recomputing + // the token otherwise the value can change all the time (even for the same inputs). + // Storing the token in the secondary delta is not enough because secondary deltas can be dropped + // if the context is re-projected. + focusContext.setIteration(iteration); + focusContext.setIterationToken(iterationToken); + } + + private void initializeIterationSpecificationIfNeeded() { + if (!iterationSpecificationInitialized) { + ObjectTemplateType objectTemplate = context.getFocusTemplate(); + iterationSpecification = LensUtil.getIterationSpecification(objectTemplate); + maxIterations = LensUtil.determineMaxIterations(iterationSpecification); + LOGGER.trace("maxIterations = {}, iteration specification = {} derived from template {}", maxIterations, + iterationSpecification, objectTemplate); + iterationSpecificationInitialized = true; + } + } + + private void createVariablesPreIterationIfNeeded() { + if (variablesPreIteration == null) { + variablesPreIteration = ModelImplUtils.getDefaultExpressionVariables(focusContext.getObjectNew(), + null, null, null, context.getSystemConfiguration(), focusContext, assignmentHolderProcessor.getPrismContext()); + } + } + + boolean doesPreIterationConditionHold(Task task, OperationResult result) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + if (iterationSpecification != null) { + createVariablesPreIterationIfNeeded(); + if (!LensUtil.evaluateIterationCondition(context, focusContext, iterationSpecification, iteration, + iterationToken, true, assignmentHolderProcessor.getExpressionFactory(), variablesPreIteration, task, result)) { + reIterationReason = "pre-iteration condition was false"; + LOGGER.debug("Skipping iteration {}, token '{}' for {} because the pre-iteration condition was false", + iteration, iterationToken, focusContext.getHumanReadableName()); + return false; + } + } + return true; + } + + private void resetOnIterationSpecificationChange() { + iteration = 0; + iterationToken = null; + wasResetOnIterationSpecificationChange = true; + wasResetIterationCounter = false; + iterationSpecificationInitialized = false; + LOGGER.trace("Resetting iteration counter and token because of iteration specification change"); + } + + private void resetOnRename() { + iteration = 0; + iterationToken = null; + wasResetIterationCounter = true; + LOGGER.trace("Resetting iteration counter and token because rename was detected"); + } + + private void resetOnConflict() { + iteration = 0; + iterationToken = null; + wasResetIterationCounter = true; + LOGGER.trace("Resetting iteration counter and token after conflict"); + } + + boolean didIterationSpecificationChange() { + if (wasResetOnIterationSpecificationChange) { + return false; // We won't reset on spec change twice. + } + IterationSpecificationType newIterationSpecification = context.getFocusTemplate() != null ? + context.getFocusTemplate().getIterationSpecification() : null; + if (java.util.Objects.equals(iterationSpecification, newIterationSpecification)) { + return false; + } else { + resetOnIterationSpecificationChange(); + return true; + } + } + + boolean isIterationOk(PrismObject objectNew, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + checkNamePresence(context, this, objectNew); + FocusConstraintsChecker checker = createChecker(context); + if (!shouldCheckConstraints() || checker.check(objectNew, result)) { + LOGGER.trace("Current focus satisfies uniqueness constraints. Iteration {}, token '{}'", iteration, iterationToken); + ExpressionVariables variablesPostIteration = ModelImplUtils.getDefaultExpressionVariables(objectNew, + null, null, null, context.getSystemConfiguration(), focusContext, assignmentHolderProcessor.getPrismContext()); + if (LensUtil.evaluateIterationCondition(context, focusContext, + iterationSpecification, iteration, iterationToken, false, assignmentHolderProcessor.getExpressionFactory(), variablesPostIteration, + task, result)) { + return true; + } else { + reIterationReason = "post-iteration condition was false"; + LOGGER.debug("Skipping iteration {}, token '{}' for {} because the post-iteration condition was false", + iteration, iterationToken, focusContext.getHumanReadableName()); + return false; + } + } else { + LOGGER.trace("Current focus does not satisfy constraints. Conflicting object: {}; iteration={}, maxIterations={}", + checker.getConflictingObject(), iteration, maxIterations); + reIterationReason = checker.getMessages(); + return false; + } + } + + @NotNull + private FocusConstraintsChecker createChecker(LensContext context) { + FocusConstraintsChecker checker = new FocusConstraintsChecker<>(); + checker.setPrismContext(assignmentHolderProcessor.getPrismContext()); + checker.setContext(context); + checker.setRepositoryService(assignmentHolderProcessor.getCacheRepositoryService()); + checker.setCacheConfigurationManager(assignmentHolderProcessor.getCacheConfigurationManager()); + return checker; + } + + private boolean shouldCheckConstraints() throws SchemaException { + ConstraintsCheckingStrategyType strategy = context.getFocusConstraintsCheckingStrategy(); + boolean skipWhenNoChange = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoChange()); + boolean skipWhenNoIteration = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoIteration()); + + if (skipWhenNoChange && !hasNameDelta()) { + LOGGER.trace("Skipping constraints check because 'skipWhenNoChange' is true and there's no name delta"); + return false; + } else if (skipWhenNoIteration && maxIterations == 0) { + LOGGER.trace("Skipping constraints check because 'skipWhenNoIteration' is true and there is no iteration defined"); + return false; + } else if (TaskType.class == focusContext.getObjectTypeClass()) { + LOGGER.trace("Skipping constraints check for task, not needed because tasks names are not unique."); + return false; + } else { + return true; + } + } + + void incrementIterationCounter() throws ObjectAlreadyExistsException { + iteration++; + iterationToken = null; + LensUtil.checkMaxIterations(iteration, maxIterations, reIterationReason, focusContext.getHumanReadableName()); + } + + boolean didResetOnRenameOccur() throws SchemaException { + if (iteration != 0 && RESET_ON_RENAME && !wasResetIterationCounter && willResetIterationCounter()) { + // Make sure this happens only the very first time during the first recompute. + // Otherwise it will always change the token (especially if the token expression has a random part) + // hence the focusContext.getIterationToken() == null + resetOnRename(); + return true; + } else { + return false; + } + } + + private boolean willResetIterationCounter() throws SchemaException { + ObjectDelta focusDelta = focusContext.getDelta(); + if (focusDelta == null) { + return false; + } + if (focusContext.isAdd() || focusContext.isDelete()) { + return false; + } + if (focusDelta.findPropertyDelta(FocusType.F_ITERATION) != null) { + // there was a reset already in previous projector runs + return false; + } + // Check for rename + return hasNameDelta(focusDelta); + } + + private boolean hasNameDelta() throws SchemaException { + ObjectDelta focusDelta = focusContext.getDelta(); + return focusDelta != null && hasNameDelta(focusDelta); + } + + private boolean hasNameDelta(ObjectDelta focusDelta) { + PropertyDelta nameDelta = focusDelta.findPropertyDelta(FocusType.F_NAME); + return nameDelta != null; + } + + boolean shouldResetOnConflict() { + if (!wasResetIterationCounter && iteration != 0) { + resetOnConflict(); + return true; + } else { + return false; + } + } + + private void checkNamePresence(LensContext context, + IterationHelper ctx, @NotNull PrismObject objectNew) throws NoFocusNameSchemaException { + // Explicitly check for name. The checker would check for this also. But checking it here + // will produce better error message + PolyStringType objectName = objectNew.asObjectable().getName(); + if (objectName == null || objectName.getOrig().isEmpty()) { + throw new NoFocusNameSchemaException("No name in new object " + objectName + " as produced by template " + context.getFocusTemplate() + + " in iteration " + ctx.iteration + ", we cannot process an object without a name"); + } + } + + /** + * Adds deltas for iteration and iterationToken to the focus if needed. + */ + void createIterationTokenDeltas() throws SchemaException { + PrismContext prismContext = assignmentHolderProcessor.getPrismContext(); + + PrismObject objectCurrent = focusContext.getObjectCurrent(); + if (objectCurrent != null) { + Integer iterationOld = objectCurrent.asObjectable().getIteration(); + String iterationTokenOld = objectCurrent.asObjectable().getIterationToken(); + if (iterationOld != null && iterationOld == iteration && + iterationTokenOld != null && iterationTokenOld.equals(iterationToken)) { + // Already stored + return; + } + } + PrismObjectDefinition objDef = focusContext.getObjectDefinition(); + + PrismPropertyValue iterationVal = prismContext.itemFactory().createPropertyValue(iteration); + iterationVal.setOriginType(OriginType.USER_POLICY); + PropertyDelta iterationDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, + FocusType.F_ITERATION, iterationVal); + focusContext.swallowToSecondaryDelta(iterationDelta); + + PrismPropertyValue iterationTokenVal = prismContext.itemFactory().createPropertyValue(iterationToken); + iterationTokenVal.setOriginType(OriginType.USER_POLICY); + PropertyDelta iterationTokenDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, + FocusType.F_ITERATION_TOKEN, iterationTokenVal); + focusContext.swallowToSecondaryDelta(iterationTokenDelta); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java index 96ac81b126f..316ccc949c6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java @@ -17,7 +17,10 @@ import javax.xml.namespace.QName; import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.path.ItemPathCollectionsUtil; @@ -82,6 +85,8 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import static com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted.PRIMARY; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingEvaluationPhaseType.AFTER_ASSIGNMENTS; import static com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingEvaluationPhaseType.BEFORE_ASSIGNMENTS; /** @@ -91,7 +96,8 @@ * */ @Component -public class ObjectTemplateProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenFocusDeleted = PRIMARY) +public class ObjectTemplateProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ObjectTemplateProcessor.class); @@ -105,18 +111,27 @@ public class ObjectTemplateProcessor { @Autowired private MappingSetEvaluator mappingSetEvaluator; @Autowired private MatchingRuleRegistry matchingRuleRegistry; - /** - * Process focus template: application of object template where focus is both source and target. - */ - void processTemplate(LensContext context, + @ProcessorMethod + void processTemplateBeforeAssignments(LensContext context, + XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, + SecurityViolationException, ConfigurationException, CommunicationException { + processTemplate(context, BEFORE_ASSIGNMENTS, now, task, result); + } + + @ProcessorMethod + void processTemplateAfterAssignments(LensContext context, + XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, + SecurityViolationException, ConfigurationException, CommunicationException { + processTemplate(context, AFTER_ASSIGNMENTS, now, task, result); + } + + private void processTemplate(LensContext context, ObjectTemplateMappingEvaluationPhaseType phase, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { LensFocusContext focusContext = context.getFocusContext(); - if (focusContext.isDelete()) { - LOGGER.trace("Skipping processing of object template: focus delete"); - return; - } ObjectTemplateType objectTemplate = context.getFocusTemplate(); String objectTemplateDesc; @@ -163,6 +178,8 @@ void processTemplate(LensContext context, if (nextRecompute != null) { nextRecompute.createTrigger(focusContext); } + + focusContext.recompute(); } /** diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java index cddfb944ff5..2dd98135252 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java @@ -42,9 +42,6 @@ public class PolicyRuleEnforcer { //private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleEnforcer.class); - // deprecated - private static final String HOOK_URI = SchemaConstants.NS_MODEL + "/policy-rule-enforcer-hook-3"; - @Autowired private PrismContext prismContext; @Autowired private LocalizationService localizationService; @@ -104,6 +101,7 @@ private void evaluateAssignmentRules(EvaluationContext eva if (evaluatedAssignmentTriple == null) { return; } + //noinspection unchecked evaluatedAssignmentTriple.simpleAccept(assignment -> enforceTriggeredRules(evalCtx, assignment.getAllTargetsPolicyRules())); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java index 903cbd3d2c0..99436caf9d6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java @@ -11,8 +11,11 @@ import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.policy.evaluators.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.util.CloneUtil; @@ -56,7 +59,8 @@ * @author mederly */ @Component -public class PolicyRuleProcessor { +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class) +public class PolicyRuleProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleProcessor.class); @@ -233,14 +237,10 @@ private void resolveReferences(Collection evaluatedRules, C // } //region ------------------------------------------------------------------ Focus policy rules - public void evaluateObjectPolicyRules(LensContext context, String activityDescription, + @ProcessorMethod + public void evaluateObjectPolicyRules(LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) throws PolicyViolationException, SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - RulesEvaluationContext globalCtx = new RulesEvaluationContext(); List rules = new ArrayList<>(); @@ -253,6 +253,7 @@ public void evaluateObjectPolicyRules(LensCont List nonSituationRules = new ArrayList<>(); LOGGER.trace("Evaluating {} object policy rules", rules.size()); + LensFocusContext focusContext = context.getFocusContext(); focusContext.clearPolicyRules(); for (EvaluatedPolicyRule rule : rules) { if (isApplicableToObject(rule)) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java new file mode 100644 index 00000000000..d3bf3c40906 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies requirements on execution of processor methods through ClockworkMedic.partialExecute + * and related methods. + *

+ * Beware that these requirements apply to all methods callable via ClockworkMedic ({@link ProcessorMethod}). + *

+ * In the future we might consider declaring execution requirements directly for those methods. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Experimental +public @interface ProcessorExecution { + + /** + * Is the focus context required? (Usually yes, but let's be explicit.) + */ + boolean focusRequired() default false; + + /** + * What kind of focus there should be in order for the processor methods to be executed? + * Checked only if focusRequired = true. + */ + Class focusType() default ObjectType.class; + + /** + * Should the methods execution be skipped if the focus is going to be deleted? + */ + SkipWhenFocusDeleted skipWhenFocusDeleted() default SkipWhenFocusDeleted.NONE; + + /** + * Should the execution be skipped if the projection is to be deleted? + * (We should perhaps make this more flexible in the future.) + */ + boolean skipWhenProjectionDeleted() default false; +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java new file mode 100644 index 00000000000..587663ee9a7 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation for medic-invocable projection processor method. + * It is used basically as a reminder that this method is called under checks + * of ClockworkMedic. In the future we might declare execution requirements here. + * + * (Unfortunately, Java does not provide us with the annotation of the method referenced in + * "component::method" way. So this is only a wish for the time being.) + * + * We should consider finding a better name for this annotation. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Experimental +public @interface ProcessorMethod { +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java new file mode 100644 index 00000000000..b8fd0b6eb67 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * Typical processor "component-level" method that performs a well defined part of the computation. + * This is the full version for focus-level, i.e. with activityDescription and without projection context. + * + * @param Fake type parameter that is necessary to make type inference in partialProcessorExecute methods happy. + */ +@Experimental +@FunctionalInterface +public interface ProcessorMethodRef { + + void run(LensContext lensContext, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException; + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java new file mode 100644 index 00000000000..af7842247b7 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * Typical processor "component-level" method that performs a well defined part of the computation. + * This is the full version for projection-level, i.e. with projection context and activityDescription. + * + * @param Fake type parameter that is necessary to make type inference in partialProcessorExecute methods happy. + */ +@Experimental +@FunctionalInterface +public interface ProjectionAwareProcessorMethodRef { + + void run(LensContext lensContext, LensProjectionContext projectionContext, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException; + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java new file mode 100644 index 00000000000..b855129db4d --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * Typical processor "component-level" method that performs a well defined part of the computation. + * This is the simplified version, i.e. without activityDescription. + * + * @param Fake type parameter that is necessary to make type inference in partialProcessorExecute methods happy. + */ +@Experimental +@FunctionalInterface +public interface SimplifiedProcessorMethodRef { + + void run(LensContext lensContext, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException; + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java new file mode 100644 index 00000000000..c0ca231db56 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 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.util; + +/** + * Should we skip the processing if the focus is going to be deleted? + */ +public enum SkipWhenFocusDeleted { + + /** + * No. Processor should be always executed. + */ + NONE, + + /** + * Yes. Processor should be skipped if the primary delta is DELETE. + * TODO Do we really need this? It was created by inspecting existing code. + * Maybe NONE + PRIMARY_OR_SECONDARY (i.e. simple false/true flag) is sufficient. + */ + PRIMARY, + + /** + * Yes. Processor should be skipped if the primary or secondary delta is DELETE. + */ + PRIMARY_OR_SECONDARY +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java index f32e0a5c1fd..d402efb124b 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java @@ -497,7 +497,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti setDefaultObjectTemplate(UserType.COMPLEX_TYPE, USER_TEMPLATE_SECURITY_OID, initResult); - InternalsConfig.setDetailedAuhotizationLog(true); + InternalsConfig.setDetailedAuthorizationLog(true); } protected int getNumberOfRoles() { diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index da372435a13..43e787318b8 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -62,7 +62,7 @@ - + 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 13914d9e491..3567587fe24 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 @@ -107,24 +107,17 @@ public void init() { } @Override - public void invoke(PrismObject object, - Collection> options, Task task, - OperationResult parentResult) throws SchemaException, - ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException { + public void invoke(PrismObject object, Collection> options, + Task task, OperationResult parentResult) throws SchemaException { - if (!ReportType.class.equals(object.getCompileTimeClass())) { - return; - } - - boolean raw = isRaw(options); - if (!raw) { + if (ReportType.class.equals(object.getCompileTimeClass()) && !isRaw(options)) { + //noinspection unchecked ReportTypeUtil.applyDefinition((PrismObject) object, prismContext); } } private boolean isRaw(Collection> options) { - GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); - return rootOptions == null ? false : GetOperationOptions.isRaw(rootOptions); + return GetOperationOptions.isRaw(SelectorOptions.findRootOptions(options)); } /** @@ -190,14 +183,10 @@ private boolean isDashboarReport(PrismObject object) { public HookOperationMode invoke(@NotNull ModelContext context, @NotNull Task task, @NotNull OperationResult parentResult) { ModelState state = context.getState(); if (state != ModelState.FINAL) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report manager called in state = " + state + ", exiting."); - } + LOGGER.trace("report manager called in state = {}, exiting.", state); return HookOperationMode.FOREGROUND; } else { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report manager called in state = " + state + ", proceeding."); - } + LOGGER.trace("report manager called in state = {}, proceeding.", state); } boolean relatesToReport = false; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java index cad1b3eff37..7eda22b990b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java @@ -96,7 +96,6 @@ public HookOperationMode invoke(@NotNull ModelContext result.addArbitraryObjectAsContext("model state", context.getState()); try { WfConfigurationType wfConfigurationType = configurationHelper.getWorkflowConfiguration(context, result); - // TODO consider this if it's secure enough if (wfConfigurationType != null && Boolean.FALSE.equals(wfConfigurationType.isModelHookEnabled())) { LOGGER.info("Workflow model hook is disabled. Proceeding with operation execution as if everything is approved."); result.recordSuccess(); diff --git a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java index a4a86d11c05..9bdcff7cb3d 100644 --- a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java +++ b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java @@ -338,7 +338,7 @@ private AccessDecision determine private void logSubitemContainerDecision(AccessDecision subdecision, String location, PrismContainerValue cval) { if (LOGGER.isTraceEnabled()) { - if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuhotizationLog()) { + if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuthorizationLog()) { LOGGER.trace(" container {} for {} (processed subitems): decision={}", cval.getPath(), location, subdecision); } } @@ -346,7 +346,7 @@ private void logSubitemContainerDecision(AccessDecisio private void logSubitemDecision(AccessDecision subdecision, String location, ItemPath path) { if (LOGGER.isTraceEnabled()) { - if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuhotizationLog()) { + if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuthorizationLog()) { LOGGER.trace(" item {} for {}: decision={}", path, location, subdecision); } } diff --git a/testing/story/src/test/resources/logback-test.xml b/testing/story/src/test/resources/logback-test.xml index b16814c6a15..f87bfe1b0ed 100644 --- a/testing/story/src/test/resources/logback-test.xml +++ b/testing/story/src/test/resources/logback-test.xml @@ -59,7 +59,7 @@ - + From 35025f7e2c9933c8a78f553082adeb0c2da8462b Mon Sep 17 00:00:00 2001 From: kate Date: Thu, 23 Apr 2020 15:29:13 +0200 Subject: [PATCH 23/27] MID-6035 Request a Role Cart - Include a Delete/remove item for each item --- .../AbstractAssignmentListPanel.java | 1 + .../assignment/AssignmentEditorPanel.html | 13 +-- .../assignment/AssignmentEditorPanel.java | 85 ++++++++++++------- .../assignment/AssignmentTablePanel.java | 5 ++ 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentListPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentListPanel.java index 1486bc619fb..c748f9b17d0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentListPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AbstractAssignmentListPanel.java @@ -108,6 +108,7 @@ protected void deleteAssignmentConfirmedPerformed(AjaxRequestTarget target, } } target.add(getPageBase().getFeedbackPanel()); + getPageBase().reloadShoppingCartIcon(target); reloadMainAssignmentsComponent(target); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.html index d16e774f4f5..8c5545116c6 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.html @@ -16,15 +16,16 @@ - - - - - + + + + +

- + +
diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java index 51618ec9e3e..705f9f73909 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentEditorPanel.java @@ -36,6 +36,7 @@ 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.web.component.AjaxButton; import com.evolveum.midpoint.web.component.DateInput; import com.evolveum.midpoint.web.component.input.DropDownChoicePanel; import com.evolveum.midpoint.web.component.input.RelationDropDownChoicePanel; @@ -55,6 +56,7 @@ import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox; +import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.head.CssHeaderItem; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.html.WebMarkupContainer; @@ -93,9 +95,10 @@ public class AssignmentEditorPanel extends BasePanel { private static final String ID_TYPE_IMAGE = "typeImage"; private static final String ID_NAME_LABEL = "nameLabel"; private static final String ID_NAME = "name"; - private static final String ID_ACTIVATION = "activation"; +// private static final String ID_ACTIVATION = "activation"; private static final String ID_ACTIVATION_BLOCK = "activationBlock"; private static final String ID_EXPAND = "expand"; + private static final String ID_REMOVE_BUTTON = "removeButton"; protected static final String ID_BODY = "body"; private static final String ID_DESCRIPTION = "description"; private static final String ID_RELATION_CONTAINER = "relationContainer"; @@ -118,8 +121,8 @@ public class AssignmentEditorPanel extends BasePanel { private static final String ID_TENANT_CHOOSER = "tenantRefChooser"; private static final String ID_CONTAINER_ORG_REF = "orgRefContainer"; private static final String ID_ORG_CHOOSER = "orgRefChooser"; - private static final String ID_BUTTON_SHOW_MORE = "errorLink"; - private static final String ID_ERROR_ICON = "errorIcon"; +// private static final String ID_BUTTON_SHOW_MORE = "errorLink"; +// private static final String ID_ERROR_ICON = "errorIcon"; private static final String ID_METADATA_CONTAINER = "metadataContainer"; private static final String ID_PROPERTY_CONTAINER = "propertyContainer"; private static final String ID_DESCRIPTION_CONTAINER = "descriptionContainer"; @@ -214,16 +217,16 @@ public boolean isVisible(){ typeImage.add(AttributeModifier.append("class", createImageTypeModel(getModel()))); headerRow.add(typeImage); - Label errorIcon = new Label(ID_ERROR_ICON); - errorIcon.add(new VisibleEnableBehaviour() { - private static final long serialVersionUID = 1L; - - @Override - public boolean isVisible() { - return !isTargetValid(); - } - }); - headerRow.add(errorIcon); +// Label errorIcon = new Label(ID_ERROR_ICON); +// errorIcon.add(new VisibleEnableBehaviour() { +// private static final long serialVersionUID = 1L; +// +// @Override +// public boolean isVisible() { +// return !isTargetValid(); +// } +// }); +// headerRow.add(errorIcon); AjaxLink name = new AjaxLink(ID_NAME) { private static final long serialVersionUID = 1L; @@ -235,30 +238,30 @@ public void onClick(AjaxRequestTarget target) { }; headerRow.add(name); - AjaxLink errorLink = new AjaxLink(ID_BUTTON_SHOW_MORE) { - private static final long serialVersionUID = 1L; - - @Override - public void onClick(AjaxRequestTarget target) { - showErrorPerformed(target); - } - }; - errorLink.add(new VisibleEnableBehaviour() { - private static final long serialVersionUID = 1L; - - @Override - public boolean isVisible() { - return !isTargetValid(); - } - }); - headerRow.add(errorLink); +// AjaxLink errorLink = new AjaxLink(ID_BUTTON_SHOW_MORE) { +// private static final long serialVersionUID = 1L; +// +// @Override +// public void onClick(AjaxRequestTarget target) { +// showErrorPerformed(target); +// } +// }; +// errorLink.add(new VisibleEnableBehaviour() { +// private static final long serialVersionUID = 1L; +// +// @Override +// public boolean isVisible() { +// return !isTargetValid(); +// } +// }); +// headerRow.add(errorLink); Label nameLabel = new Label(ID_NAME_LABEL, createAssignmentNameLabelModel(false)); nameLabel.setOutputMarkupId(true); name.add(nameLabel); - Label activation = new Label(ID_ACTIVATION, AssignmentsUtil.createActivationTitleModel(getModel().getObject().getActivation(), "-", getPageBase())); - headerRow.add(activation); +// Label activation = new Label(ID_ACTIVATION, AssignmentsUtil.createActivationTitleModel(getModel().getObject().getActivation(), "-", getPageBase())); +// headerRow.add(activation); ToggleIconButton expandButton = new ToggleIconButton(ID_EXPAND, GuiStyleConstants.CLASS_ICON_EXPAND, GuiStyleConstants.CLASS_ICON_COLLAPSE) { @@ -283,7 +286,20 @@ public boolean isVisible(){ return !getModel().getObject().isSimpleView(); } }); + expandButton.setOutputMarkupId(true); headerRow.add(expandButton); + + AjaxButton removeButton = new AjaxButton(ID_REMOVE_BUTTON) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + removeButtonClickPerformed(AssignmentEditorPanel.this.getModelObject(), target); + } + }; + removeButton.add(AttributeAppender.append("title", getPageBase().createStringResource("AssignmentTablePanel.menu.unassign"))); + removeButton.setOutputMarkupId(true); + headerRow.add(removeButton); } protected IModel createAssignmentNameLabelModel(final boolean isManager) { @@ -1165,4 +1181,9 @@ private ItemSecurityConstraints loadSecurityConstraints() { return constraints; } + protected void removeButtonClickPerformed(AssignmentEditorDto assignmentDto, AjaxRequestTarget target){ + //Override if needed + } + + } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java index 0c496d7123e..edbf6032cd6 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java @@ -167,6 +167,11 @@ protected boolean isRelationEditable(){ return AssignmentTablePanel.this.isRelationEditable(); } + @Override + protected void removeButtonClickPerformed(AssignmentEditorDto assignmentDto, AjaxRequestTarget target){ + deleteAssignmentPerformed(target, assignmentDto); + } + }; item.add(editor); From 1ed3716a10dc1b529d227191c9a6f4af7e9a9982 Mon Sep 17 00:00:00 2001 From: kate Date: Thu, 23 Apr 2020 23:31:03 +0200 Subject: [PATCH 24/27] MID-6056 Missing information about approval role / candidate actors --- .../component/data/column/ColumnUtils.java | 105 ++++++++++++------ .../page/admin/cases/ChildCasesTabPanel.java | 13 +-- .../admin/workflow/WorkItemDetailsPanel.html | 6 + .../admin/workflow/WorkItemDetailsPanel.java | 23 +++- 4 files changed, 101 insertions(+), 46 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/ColumnUtils.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/ColumnUtils.java index 9b9edb1c977..c4200fdbfb7 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/ColumnUtils.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/data/column/ColumnUtils.java @@ -22,6 +22,7 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CaseTypeUtil; +import com.evolveum.midpoint.schema.util.CaseWorkItemUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.schema.util.ApprovalContextUtil; import com.evolveum.midpoint.util.logging.Trace; @@ -580,11 +581,33 @@ public void populateItem(Item>> cellItem, String componentId, IModel> rowModel) { - - String assignee = WebComponentUtil.getReferencedObjectNames(unwrapRowModel(rowModel).getAssigneeRef(), false, true); - cellItem.add(new Label(componentId, - assignee != null ? assignee - : WebComponentUtil.getReferencedObjectNames(unwrapRowModel(rowModel).getCandidateRef(), true, true))); + RepeatingView actorLinks = new RepeatingView(componentId); + actorLinks.setOutputMarkupId(true); + List assigneeRefs; + if (CaseWorkItemUtil.doesAssigneeExist(unwrapRowModel(rowModel))){ + assigneeRefs = unwrapRowModel(rowModel).getAssigneeRef(); + } else { + assigneeRefs = unwrapRowModel(rowModel).getCandidateRef(); + } + if (assigneeRefs != null){ + assigneeRefs.forEach(assigneeRef -> { + LinkPanel assigneeLinkPanel = new LinkPanel(actorLinks.newChildId(), + Model.of(WebModelServiceUtils.resolveReferenceName(assigneeRef, pageBase))) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + CaseWorkItemType caseWorkItemType = unwrapRowModel(rowModel); + CaseType caseType = CaseTypeUtil.getCase(caseWorkItemType); + + dispatchToObjectDetailsPage(caseType.getObjectRef(), pageBase, false); + } + }; + assigneeLinkPanel.setOutputMarkupId(true); + actorLinks.add(assigneeLinkPanel); + }); + } + cellItem.add(actorLinks); } @Override @@ -707,18 +730,7 @@ public String getObject() { columns.add(column); if (!isDashboard) { - column = new AbstractColumn, String>(createStringResource("pageCases.table.actors")){ - @Override - public void populateItem(Item>> item, String componentId, IModel> rowModel) { - item.add(new Label(componentId, new IModel() { - @Override - public String getObject() { - return getActorsForCase(rowModel, pageBase); - } - })); - } - }; - columns.add(column); + columns.add(createCaseActorsColumn(pageBase)); } column = new AbstractColumn, String>( @@ -821,28 +833,55 @@ public IModel getDataModel(IModel> rowModel) { return columns; } + public static AbstractColumn, String> createCaseActorsColumn(PageBase pageBase){ + return new AbstractColumn, String>(createStringResource("pageCases.table.actors")){ + @Override + public void populateItem(Item>> item, String componentId, IModel> rowModel) { + CaseType caseInstance = rowModel != null ? rowModel.getObject().getValue() : null; + item.add(getMultilineLinkPanel(componentId, getActorsForCase(rowModel != null ? rowModel.getObject().getValue() : null), + caseInstance, pageBase)); + } + }; + } + + public static RepeatingView getMultilineLinkPanel(String componentId, List referencesList, + CaseType caseType, PageBase pageBase){ + RepeatingView multilineLinkPanel = new RepeatingView(componentId); + multilineLinkPanel.setOutputMarkupId(true); + if (referencesList != null){ + referencesList.forEach(reference -> { + LinkPanel assigneeLinkPanel = new LinkPanel(multilineLinkPanel.newChildId(), + Model.of(WebModelServiceUtils.resolveReferenceName(reference, pageBase))) { + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + dispatchToObjectDetailsPage(caseType.getObjectRef(), pageBase, false); + } + }; + assigneeLinkPanel.setOutputMarkupId(true); + multilineLinkPanel.add(assigneeLinkPanel); + }); + } + return multilineLinkPanel; + } + public static C unwrapRowModel(IModel> rowModel){ return rowModel.getObject().getRealValue(); } - public static String getActorsForCase(IModel> rowModel, PageBase pageBase) { - String actors = null; - SelectableBean caseModel = rowModel.getObject(); - if (caseModel != null) { - CaseType caseIntance = caseModel.getValue(); - if (caseIntance != null) { - List caseWorkItemTypes = caseIntance.getWorkItem(); - List actorsList = new ArrayList(); - for (CaseWorkItemType caseWorkItem : caseWorkItemTypes) { - List assignees = caseWorkItem.getAssigneeRef(); - for (ObjectReferenceType actor : assignees) { - actorsList.add(WebComponentUtil.getEffectiveName(actor, AbstractRoleType.F_DISPLAY_NAME, pageBase, - pageBase.getClass().getSimpleName() + "." + "loadCaseActorsNames")); - } + public static List getActorsForCase(CaseType caseType) { + List actorsList = new ArrayList<>(); + if (caseType != null) { + List caseWorkItemTypes = caseType.getWorkItem(); + for (CaseWorkItemType caseWorkItem : caseWorkItemTypes) { + if (caseWorkItem.getAssigneeRef() != null && !caseWorkItem.getAssigneeRef().isEmpty()) { + actorsList.addAll(caseWorkItem.getAssigneeRef()); + } else { + actorsList.addAll(caseWorkItem.getCandidateRef()); } - actors = String.join(", ", actorsList); } } - return actors; + return actorsList; } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java index 37ec487be56..6567862d4a7 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/cases/ChildCasesTabPanel.java @@ -80,18 +80,7 @@ protected List, String>> createColumns() { IColumn column = new PropertyColumn(createStringResource("pageCases.table.description"), "value.description"); columns.add(column); - column = new AbstractColumn, String>(createStringResource("pageCases.table.actors")){ - @Override - public void populateItem(Item>> item, String componentId, IModel> rowModel) { - item.add(new Label(componentId, new IModel() { - @Override - public String getObject() { - return ColumnUtils.getActorsForCase(rowModel, getPageBase()); - } - })); - } - }; - columns.add(column); + columns.add(ColumnUtils.createCaseActorsColumn(ChildCasesTabPanel.this.getPageBase())); column = new PropertyColumn, String>(createStringResource("pageCases.table.state"), CaseType.F_STATE.getLocalPart(), "value.state"){ @Override diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.html index 3a84a062c03..a8dc3937b47 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.html @@ -36,6 +36,12 @@ + + + + + + diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java index b7f3ca10581..c59cb62ecce 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/WorkItemDetailsPanel.java @@ -22,6 +22,7 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ApprovalContextUtil; import com.evolveum.midpoint.schema.util.CaseTypeUtil; +import com.evolveum.midpoint.schema.util.CaseWorkItemUtil; import com.evolveum.midpoint.schema.util.WorkItemTypeUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.SchemaException; @@ -45,12 +46,14 @@ import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.TextArea; +import org.apache.wicket.markup.repeater.RepeatingView; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Arrays; +import java.util.List; /** * Created by honchar @@ -69,6 +72,8 @@ public class WorkItemDetailsPanel extends BasePanel { private static final String ID_REQUESTED_BY = "requestedBy"; private static final String ID_REQUESTED_FOR = "requestedFor"; private static final String ID_APPROVER = "approver"; + private static final String ID_CANDIDATE_CONTAINER = "candidateContainer"; + private static final String ID_CANDIDATE = "candidate"; private static final String ID_PARENT_CASE_CONTAINER = "parentCaseContainer"; private static final String ID_PARENT_CASE = "parentCase"; private static final String ID_TARGET = "target"; @@ -143,13 +148,29 @@ private void initLayout(){ requestedFor.setOutputMarkupId(true); add(requestedFor); - LinkedReferencePanel approver = new LinkedReferencePanel(ID_APPROVER, getModelObject() != null && getModelObject().getAssigneeRef() != null && getModelObject().getAssigneeRef().size() > 0 ? Model.of(getModelObject().getAssigneeRef().get(0)) : Model.of()); approver.setOutputMarkupId(true); add(approver); + WebMarkupContainer candidateContainer = new WebMarkupContainer(ID_CANDIDATE_CONTAINER); + candidateContainer.setOutputMarkupId(true); + candidateContainer.add(new VisibleBehaviour(() -> CaseWorkItemUtil.isWorkItemClaimable(getModelObject()))); + add(candidateContainer); + + RepeatingView candidateLinksPanel = new RepeatingView(ID_CANDIDATE); + candidateLinksPanel.setOutputMarkupId(true); + List candidates = getModelObject() != null ? getModelObject().getCandidateRef() : null; + if (candidates != null){ + candidates.forEach(candidate -> { + LinkedReferencePanel candidatePanel = new LinkedReferencePanel(candidateLinksPanel.newChildId(), Model.of(candidate)); + candidatePanel.setOutputMarkupId(true); + candidateLinksPanel.add(candidatePanel); + }); + } + candidateContainer.add(candidateLinksPanel); + WebMarkupContainer parentCaseContainer = new WebMarkupContainer(ID_PARENT_CASE_CONTAINER); parentCaseContainer.add(new VisibleBehaviour(() -> getPageBase() instanceof PageCaseWorkItem)); parentCaseContainer.setOutputMarkupId(true); From 7723ac2a99d3a0d30e93a9fc780cbf58c015322e Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Fri, 24 Apr 2020 09:27:39 +0200 Subject: [PATCH 25/27] Adapt story test to changes in Projector --- .../testing/story/notorious/AbstractNotoriousTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/notorious/AbstractNotoriousTest.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/notorious/AbstractNotoriousTest.java index 2bb3d3c767f..a8cf7ae553d 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/notorious/AbstractNotoriousTest.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/notorious/AbstractNotoriousTest.java @@ -260,7 +260,7 @@ public void test104PreviewChangesJack() throws Exception { displayCountersAndInspector(); - assertCounterIncrement(InternalCounters.PROJECTOR_RUN_COUNT, 1); + assertCounterIncrement(InternalCounters.PROJECTOR_RUN_COUNT, 2); assertCounterIncrement(InternalCounters.ROLE_EVALUATION_COUNT, (NUMBER_OF_LEVEL_B_ROLES + 2)*2); assertCounterIncrement(InternalCounters.PRISM_OBJECT_COMPARE_COUNT, 0); } @@ -479,7 +479,7 @@ public void test124PreviewChangesJack() throws Exception { displayCountersAndInspector(); - assertCounterIncrement(InternalCounters.PROJECTOR_RUN_COUNT, 1); + assertCounterIncrement(InternalCounters.PROJECTOR_RUN_COUNT, 2); assertCounterIncrement(InternalCounters.ROLE_EVALUATION_COUNT, (NUMBER_OF_LEVEL_B_ROLES + 1 + NUMBER_OF_LEVEL_A_ROLES)*2); assertCounterIncrement(InternalCounters.PRISM_OBJECT_COMPARE_COUNT, 0); } From 8eb5475166d19685256a37cef7295aa6ec7fab3a Mon Sep 17 00:00:00 2001 From: lskublik Date: Fri, 24 Apr 2020 10:21:30 +0200 Subject: [PATCH 26/27] fix for schrodinger tests from labs of training 101 --- .../schrodinger/AbstractSchrodingerTest.java | 17 +- .../M3ResourcesAttributesAndMappingsTest.java | 6 +- .../labs/M4ProvisioningToResources.java | 8 +- .../labs/M5AccountsAssignmentsAndRoles.java | 12 +- .../M6ConfiguringMultipleAccountTypes.java | 4 +- .../labs/M7SynchronizationFlavours.java | 18 +- .../labs/M8ExtendingMidPointXMLSchema.java | 7 +- .../labs/M9OrganizationalStructure.java | 154 ++++++++++++++++-- .../FocusTableWithChoosableElements.java | 5 +- .../schrodinger/component/ProjectionsTab.java | 4 +- .../schrodinger/component/common/Popover.java | 26 ++- .../schrodinger/component/common/Search.java | 16 +- .../modal/FocusSetAssignmentsModal.java | 6 +- .../modal/FocusSetProjectionModal.java | 3 +- .../component/org/MemberPanel.java | 17 +- .../component/org/MemberTable.java | 46 ++++++ .../component/org/MemberTableDropDown.java | 51 ++++++ .../component/org/OrgHierarchyPanel.java | 2 +- .../component/org/OrgTreeNodeDropDown.java | 2 + .../resource/ResourceAccountsTab.java | 4 +- .../resource/ResourceShadowTable.java | 5 +- .../page/AssignmentHolderDetailsPage.java | 3 +- .../EditResourceConfigurationPage.java | 4 +- 23 files changed, 344 insertions(+), 76 deletions(-) create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTable.java create mode 100644 tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTableDropDown.java diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java index fb1ab47b74d..b7d6e079245 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/AbstractSchrodingerTest.java @@ -13,7 +13,9 @@ import java.io.InputStream; import java.util.Properties; +import com.codeborne.selenide.Condition; import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.ex.ElementNotFound; import com.codeborne.selenide.testng.BrowserPerClass; import com.evolveum.midpoint.schrodinger.component.AssignmentsTab; @@ -23,6 +25,7 @@ import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; import org.apache.commons.io.FileUtils; +import org.openqa.selenium.By; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; @@ -170,7 +173,19 @@ protected void importObject(File source, boolean overrideExistingObject, boolean .clickImportFileButton() .feedback(); - Assert.assertTrue(feedback.isSuccess() || (ignoreWarning && feedback.isWarning())); + boolean isSuccess = false; + try { + isSuccess = feedback.isSuccess(); + } catch (ElementNotFound e) { + if (!ignoreWarning) { + throw e; + } + // else ignoring exception but isSuccess is still false + } + if (!isSuccess && ignoreWarning) { + isSuccess = feedback.isWarning(); + } + Assert.assertTrue(isSuccess); } protected void importObject(File source, boolean overrideExistingObject) { diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M3ResourcesAttributesAndMappingsTest.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M3ResourcesAttributesAndMappingsTest.java index 17260f585b0..545e3e7d1d2 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M3ResourcesAttributesAndMappingsTest.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M3ResourcesAttributesAndMappingsTest.java @@ -49,7 +49,7 @@ public void beforeClass() throws IOException { super.beforeClass(); } - @Test + @Test(groups={"M3"}) public void test0301ViewingResources() throws Exception { initTestDirectory(DIRECTORY_CURRENT_TEST); @@ -143,7 +143,7 @@ public void test0301ViewingResources() throws Exception { changeResourceAttribute(CSV_3_RESOURCE_NAME, ScenariosCommons.CSV_RESOURCE_ATTR_FILE_PATH, csv3TargetFile.getAbsolutePath(), true); } - @Test(dependsOnMethods = {"test0301ViewingResources"}) + @Test(dependsOnMethods = {"test0301ViewingResources"}, groups={"M3"}) public void test0302BasicProvisioning() { UserPage user = basicPage.newUser(); user.selectTabBasic() @@ -191,7 +191,7 @@ public void test0302BasicProvisioning() { PrismForm accountForm = showShadow(CSV_1_RESOURCE_NAME, "Login", "kirk") .form(); - Selenide.sleep(2000); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); Assert.assertTrue(accountForm.compareInputAttributeValue("fname", "Jim T.")); showUser("kirk") diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M4ProvisioningToResources.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M4ProvisioningToResources.java index ad46cff6900..d427bf81079 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M4ProvisioningToResources.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M4ProvisioningToResources.java @@ -45,7 +45,7 @@ public class M4ProvisioningToResources extends AbstractLabTest { private static final File CSV_3_RESOURCE_FILE_4_4 = new File(LAB_OBJECTS_DIRECTORY + "resources/localhost-csvfile-3-ldap-4-4.xml"); - @Test + @Test(groups={"M4"}, dependsOnGroups={"M3"}) public void test0401BasicProvisioningToMultipleResources() { importObject(CSV_1_RESOURCE_FILE,true); @@ -181,7 +181,7 @@ public void test0401BasicProvisioningToMultipleResources() { Assert.assertFalse(existShadow(CSV_2_RESOURCE_NAME, "Login", "kirk")); } - @Test(dependsOnMethods = {"test0401BasicProvisioningToMultipleResources"}) + @Test(dependsOnMethods = {"test0401BasicProvisioningToMultipleResources"}, groups={"M4"}, dependsOnGroups={"M3"}) public void test0402AddingMappings() { importObject(CSV_1_RESOURCE_FILE_4_2,true); @@ -216,7 +216,7 @@ public void test0402AddingMappings() { } - @Test(dependsOnMethods = {"test0402AddingMappings"}) + @Test(dependsOnMethods = {"test0402AddingMappings"}, groups={"M4"}, dependsOnGroups={"M3"}) public void test0403ModifyingExistingMappings() { importObject(CSV_1_RESOURCE_FILE_4_3,true); @@ -271,7 +271,7 @@ public void test0403ModifyingExistingMappings() { } - @Test(dependsOnMethods = {"test0403ModifyingExistingMappings"}) + @Test(dependsOnMethods = {"test0403ModifyingExistingMappings"}, groups={"M4"}, dependsOnGroups={"M3"}) public void test0404AddingANewAttribute() { ((PrismFormWithActionButtons>>) ((AbstractTableWithPrismView)showUser("kirk") diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M5AccountsAssignmentsAndRoles.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M5AccountsAssignmentsAndRoles.java index 6e52b639921..466da487dd3 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M5AccountsAssignmentsAndRoles.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M5AccountsAssignmentsAndRoles.java @@ -60,7 +60,7 @@ public class M5AccountsAssignmentsAndRoles extends AbstractLabTest { private static final File ARCHETYPE_EXTERNAL_FILE = new File(LAB_OBJECTS_DIRECTORY + "archetypes/archetype-external.xml"); private static final File SYSTEM_CONFIGURATION_FILE_5_7 = new File(LAB_OBJECTS_DIRECTORY + "systemConfiguration/system-configuration-5-7.xml"); - @Test + @Test(groups={"M5"}, dependsOnGroups={"M4"}) public void test0501UsingRBAC() { importObject(INTERNAL_EMPLOYEE_ROLE_FILE,true); importObject(INCOGNITO_ROLE_FILE,true); @@ -96,7 +96,7 @@ public void test0501UsingRBAC() { } - @Test(dependsOnMethods = {"test0501UsingRBAC"}) + @Test(dependsOnMethods = {"test0501UsingRBAC"}, groups={"M5"}, dependsOnGroups={"M4"}) public void test0502SegregationOfDuties() { showUser("kirk").selectTabAssignments() .clickAddAssignemnt() @@ -115,7 +115,7 @@ public void test0502SegregationOfDuties() { .isError(); } - @Test(dependsOnMethods = {"test0502SegregationOfDuties"}) + @Test(dependsOnMethods = {"test0502SegregationOfDuties"}, groups={"M5"}, dependsOnGroups={"M4"}) public void test0504CreatingRoles() { InducementsTab tab = basicPage.newRole() .selectTabBasic() @@ -140,7 +140,7 @@ public void test0504CreatingRoles() { Utils.removeAssignments(showUser("kirk").selectTabAssignments(), "Too Many Secrets"); } - @Test(dependsOnMethods = {"test0504CreatingRoles"}) + @Test(dependsOnMethods = {"test0504CreatingRoles"}, groups={"M5"}, dependsOnGroups={"M4"}) public void test0505DisableOnUnassign() { importObject(CSV_1_RESOURCE_FILE_5_5,true); changeResourceAttribute(CSV_1_RESOURCE_NAME, ScenariosCommons.CSV_RESOURCE_ATTR_FILE_PATH, csv1TargetFile.getAbsolutePath(), true); @@ -180,7 +180,7 @@ public void test0505DisableOnUnassign() { Assert.assertTrue(accountForm.compareSelectAttributeValue("administrativeStatus", "Enabled")); } - @Test(dependsOnMethods = {"test0505DisableOnUnassign"}) + @Test(dependsOnMethods = {"test0505DisableOnUnassign"}, groups={"M5"}, dependsOnGroups={"M4"}) public void test0506InactiveAssignment() { Utils.addAsignments(showUser("kirk").selectTabAssignments(), "Too Many Secrets"); AccountPage shadow = showShadow(CSV_1_RESOURCE_NAME, "Login", "jkirk"); @@ -226,7 +226,7 @@ public void test0506InactiveAssignment() { Utils.removeAssignments(showUser("kirk").selectTabAssignments(), "Too Many Secrets"); } - @Test(dependsOnMethods = {"test0506InactiveAssignment"}) + @Test(dependsOnMethods = {"test0506InactiveAssignment"}, groups={"M5"}, dependsOnGroups={"M4"}) public void test0507ArchetypesIntroduction() { importObject(ARCHETYPE_EMPLOYEE_FILE, true); diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M6ConfiguringMultipleAccountTypes.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M6ConfiguringMultipleAccountTypes.java index 2f7218ec91b..8200bf44be5 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M6ConfiguringMultipleAccountTypes.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M6ConfiguringMultipleAccountTypes.java @@ -15,9 +15,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.io.File; +import java.io.IOException; /** * @author skublik @@ -34,7 +36,7 @@ public class M6ConfiguringMultipleAccountTypes extends AbstractLabTest { private static final String CSV1_TESTER_ROLE_NAME = "CSV-1 Tester"; private static final String CSV3_ADMIN_ROLE_NAME = "CSV-3 Admin"; - @Test + @Test(groups={"M6"}, dependsOnGroups={"M5"}) public void test0601UsingAccountIntentsForProvisioning() { importObject(CSV_1_RESOURCE_FILE_6_1,true); diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java index ed5e96a01ec..3d55b3ff12e 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M7SynchronizationFlavours.java @@ -34,7 +34,7 @@ public class M7SynchronizationFlavours extends AbstractLabTest{ private static final Logger LOG = LoggerFactory.getLogger(M7SynchronizationFlavours.class); - @Test + @Test(groups={"M7"}, dependsOnGroups={"M6"}) public void test0701RunningImportFromResource() throws IOException { hrTargetFile = new File(csvTargetDir, HR_FILE_SOURCE_NAME); FileUtils.copyFile(HR_SOURCE_FILE, hrTargetFile); @@ -87,7 +87,7 @@ public void test0701RunningImportFromResource() throws IOException { Assert.assertEquals(basicPage.listUsers(ARCHETYPE_EMPLOYEE_PLURAL_LABEL).getCountOfObjects(), 15); } - @Test(dependsOnMethods = {"test0701RunningImportFromResource"}) + @Test(dependsOnMethods = {"test0701RunningImportFromResource"}, groups={"M7"}, dependsOnGroups={"M6"}) public void test0702RunningAccountReconciliation() { Selenide.sleep(MidPoint.TIMEOUT_MEDIUM_6_S); createReconTask("CSV-1 Reconciliation", CSV_1_RESOURCE_NAME); @@ -109,7 +109,7 @@ public void test0702RunningAccountReconciliation() { Assert.assertTrue(containsProjection("X001212", CSV_3_RESOURCE_OID, "cn=John Smith,ou=ExAmPLE,dc=example,dc=com")); } - @Test(dependsOnMethods = {"test0702RunningAccountReconciliation"}) + @Test(dependsOnMethods = {"test0702RunningAccountReconciliation"}, groups={"M7"}, dependsOnGroups={"M6"}) public void test0703RunningAttributeReconciliation() throws IOException { FileUtils.copyFile(CSV_1_SOURCE_FILE_7_3, csv1TargetFile); @@ -126,7 +126,7 @@ public void test0703RunningAttributeReconciliation() throws IOException { } - @Test(dependsOnMethods = {"test0703RunningAttributeReconciliation"}) + @Test(dependsOnMethods = {"test0703RunningAttributeReconciliation"}, groups={"M7"}, dependsOnGroups={"M6"}) public void test0704RunningLiveSync() throws IOException { Selenide.sleep(MidPoint.TIMEOUT_MEDIUM_6_S); TaskPage task = basicPage.newTask(); @@ -141,6 +141,11 @@ public void test0704RunningLiveSync() throws IOException { .editRefValue("objectRef") .selectType("Resource") .table() + .search() + .byName() + .inputValue(HR_RESOURCE_NAME) + .updateSearch() + .and() .clickByName(HR_RESOURCE_NAME) .and() .and() @@ -214,6 +219,11 @@ private void createReconTask(String reconTaskName, String resource){ .editRefValue("objectRef") .selectType("Resource") .table() + .search() + .byName() + .inputValue(resource) + .updateSearch() + .and() .clickByName(resource) .and() .and() diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java index 70709d51823..5479eeafe70 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M8ExtendingMidPointXMLSchema.java @@ -61,7 +61,7 @@ protected void springTestContextPrepareTestInstance() throws Exception { super.springTestContextPrepareTestInstance(); } - @Test + @Test(groups={"M8"}, dependsOnGroups={"M7"}) public void test0801ExtendingMidPointXMLSchema() { PrismForm> form = basicPage.newUser() .selectTabBasic() @@ -80,6 +80,11 @@ public void test0801ExtendingMidPointXMLSchema() { Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); ResourceAccountsTab accountTab = basicPage.listResources() .table() + .search() + .byName() + .inputValue(HR_RESOURCE_NAME) + .updateSearch() + .and() .clickByName(HR_RESOURCE_NAME) .clickAccountsTab() .clickSearchInResource(); diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java index 4ccaceb2107..29d5ba3948c 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/labs/M9OrganizationalStructure.java @@ -6,6 +6,8 @@ */ package com.evolveum.midpoint.testing.schrodinger.labs; +import com.codeborne.selenide.Selenide; + import com.evolveum.midpoint.schrodinger.page.configuration.AboutPage; import com.evolveum.midpoint.schrodinger.page.login.FormLoginPage; import com.evolveum.midpoint.schrodinger.page.org.OrgPage; @@ -53,17 +55,7 @@ public void afterClass() { .clickYes(); } - @BeforeClass - @Override - public void beforeClass() throws IOException { - super.beforeClass(); - csv1TargetFile = new File("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-1.csv"); - csv2TargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-2.csv"); - csv3TargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/csv-3.csv"); - hrTargetFile = new File ("/home/lskublik/Documents/Evolveum/actual/master/05-02-2020/midpoint/testing/schrodingertest/target/midpoint-home/schrodinger/labTests/source.csv"); - } - - @Test + @Test(groups={"M9"}, dependsOnGroups={"M8"}) public void test0901ImportStaticOrgStructure() { importObject(ARCHETYPE_ORG_FUNCTIONAL_FILE, true, true); importObject(ARCHETYPE_ORG_COMPANY_FILE, true); @@ -93,7 +85,7 @@ public void test0901ImportStaticOrgStructure() { .containsChildOrg("Secret Operations", "Transportation and Logistics Department")); } - @Test(dependsOnMethods = {"test0901ImportStaticOrgStructure"}) + @Test(dependsOnMethods = {"test0901ImportStaticOrgStructure"}, groups={"M9"}, dependsOnGroups={"M8"}) public void test0902CreateStaticOrgStructure() { basicPage.orgStructure() .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") @@ -119,7 +111,7 @@ public void test0902CreateStaticOrgStructure() { .getOrgHierarchyPanel() .expandOrg("Secret Operations") .expandOrg("Transportation and Logistics Department") - .editOrg("Warp Speed Research") + .showTreeNodeDropDownMenu("Warp Speed Research") .edit() .selectTabBasic() .form() @@ -164,4 +156,140 @@ public void test0902CreateStaticOrgStructure() { .isSuccess(); } + @Test(dependsOnMethods = {"test0902CreateStaticOrgStructure"}, groups={"M9"}, dependsOnGroups={"M8"}) + public void test0903OrganizationActingAsARole() { + Assert.assertFalse(basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .expandOrg("Secret Operations") + .expandOrg("Transportation and Logistics Department") + .selectOrgInTree("Warp Speed Research") + .and() + .getMemberPanel() + .selectType("User") + .table() + .containsText("kirk")); + + basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .showTreeNodeDropDownMenu("Warp Speed Research") + .edit() + .selectTabInducements() + .clickAddInducement() + .table() + .search() + .byName() + .inputValue("Secret Projects I") + .updateSearch() + .and() + .selectCheckboxByName("Secret Projects I") + .and() + .clickAdd() + .and() + .clickSave() + .feedback() + .isSuccess(); + + basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .selectOrgInTree("Warp Speed Research") + .and() + .getMemberPanel() + .table() + .clickHeaderActionDropDown() + .assign() + .selectType("User") + .table() + .search() + .byName() + .inputValue("kirk") + .updateSearch() + .and() + .selectCheckboxByName("kirk") + .and() + .clickAdd() + .and() + .and() + .and() + .feedback() + .isInfo(); + + Assert.assertTrue(basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .expandOrg("Secret Operations") + .expandOrg("Transportation and Logistics Department") + .selectOrgInTree("Warp Speed Research") + .and() + .getMemberPanel() + .selectType("User") + .table() + .containsText("kirk")); + + Assert.assertTrue( + showShadow(CSV_1_RESOURCE_NAME, "Login", "jkirk") + .form() + .compareInputAttributeValues("groups", "Internal Employees", + "Essential Documents", "Teleportation", "Time Travel")); + + basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .expandOrg("Secret Operations") + .expandOrg("Transportation and Logistics Department") + .showTreeNodeDropDownMenu("Warp Speed Research") + .edit() + .selectTabInducements() + .clickAddInducement() + .selectType("Role") + .table() + .search() + .byName() + .inputValue("Secret Projects II") + .updateSearch() + .and() + .selectCheckboxByName("Secret Projects II") + .and() + .clickAdd() + .and() + .clickSave() + .feedback() + .isSuccess(); + + Assert.assertTrue( + showShadow(CSV_1_RESOURCE_NAME, "Login", "jkirk") + .form() + .compareInputAttributeValues("groups", "Internal Employees", + "Essential Documents", "Teleportation", "Time Travel")); + + basicPage.orgStructure() + .selectTabWithRootOrg("ExAmPLE, Inc. - Functional Structure") + .getOrgHierarchyPanel() + .selectOrgInTree("Warp Speed Research") + .and() + .getMemberPanel() + .table() + .clickHeaderActionDropDown() + .recompute() + .clickYes() + .and() + .and() + .and() + .feedback() + .isInfo(); + + Assert.assertTrue( + showShadow(CSV_1_RESOURCE_NAME, "Login", "jkirk") + .form() + .compareInputAttributeValues("groups", "Internal Employees", + "Essential Documents", "Teleportation", "Time Travel", "Lucky Numbers", + "Presidential Candidates Motivation")); + + Assert.assertTrue(showUser("kirk").selectTabAssignments() + .selectTypeAllDirectIndirect() + .containsIndirectAssignments("Secret Projects II")); + } + } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/FocusTableWithChoosableElements.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/FocusTableWithChoosableElements.java index 63cd0a16e90..6185565fd72 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/FocusTableWithChoosableElements.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/FocusTableWithChoosableElements.java @@ -48,9 +48,10 @@ private String constructCheckBoxIdBasedOnRow(String row) { @Override public Search> search() { - SelenideElement searchElement = $(By.cssSelector(".form-inline.pull-right.search-form")) + SelenideElement searchElement = getParentElement().$x(".//div[contains(@class, \"form-inline\") " + + "and contains(@class, \"pull-right\") and contains(@class, \"search-form\")]") .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); - return new Search<>(this, searchElement); + return new Search>(this, searchElement); } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/ProjectionsTab.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/ProjectionsTab.java index 5de0bab92a8..acd5fe462e1 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/ProjectionsTab.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/ProjectionsTab.java @@ -111,7 +111,9 @@ public FocusSetProjectionModal> clickAddProjection() { $(Schrodinger.byElementAttributeValue("i", "class", "fa fa-plus ")) .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - SelenideElement actualModal = $(Schrodinger.byElementAttributeValue("div", "aria-labelledby", "Choose object")); + SelenideElement actualModal = $(Schrodinger.byElementAttributeValue("div", "aria-labelledby", "Choose object")) + .waitUntil(Condition.exist, MidPoint.TIMEOUT_LONG_1_M) + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); return new FocusSetProjectionModal>(this, actualModal); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Popover.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Popover.java index 94d8f3c356e..6968abd6b21 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Popover.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Popover.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.schrodinger.component.common; import com.codeborne.selenide.Condition; +import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.Component; @@ -23,34 +24,41 @@ public Popover(T parent, SelenideElement parentElement) { } public Popover inputValue(String input) { - getDisplayedElement(getParentElement().$$(Schrodinger.byDataId("textInput"))).waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).setValue(input); - + SelenideElement inputField = getParentElement().parent().$x(".//input[@" + Schrodinger.DATA_S_ID + "='textInput']") + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); + if(!input.equals(inputField.getValue())) { + inputField.setValue(input); + } return this; } public Popover inputRefOid(String oid) { - getDisplayedElement(getParentElement().$$(Schrodinger.byDataId("oid"))).waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).setValue(oid); + getParentElement().parent().$x(".//input[@" + Schrodinger.DATA_S_ID + "='oid']").waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).setValue(oid); return this; } public Popover inputValueWithEnter(String input) { - SelenideElement inputField = getDisplayedElement(getParentElement().$$(Schrodinger.byDataId("textInput"))); - inputField.waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).setValue(input); - inputField.sendKeys(Keys.ENTER); + SelenideElement inputField = getParentElement().parent().$x(".//input[@" + Schrodinger.DATA_S_ID + "='textInput']") + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); + if(!input.equals(inputField.getValue())) { + inputField.setValue(input); + inputField.sendKeys(Keys.ENTER); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + } return this; } public T updateSearch() { - SelenideElement button = getDisplayedElement(getParentElement().$$(Schrodinger.byDataId("update"))); + SelenideElement button = getParentElement().parent().$x(".//a[@"+Schrodinger.DATA_S_ID+"='update']"); button.click(); - button.waitUntil(Condition.disappears, MidPoint.TIMEOUT_DEFAULT_2_S); + button.waitUntil(Condition.disappears, MidPoint.TIMEOUT_MEDIUM_6_S); return this.getParent(); } public T close() { - getDisplayedElement(getParentElement().$$(Schrodinger.byDataId("searchSimple"))).click(); + getDisplayedElement(getParentElement().$$(".//a[@"+Schrodinger.DATA_S_ID+"='searchSimple']")).click(); return this.getParent(); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Search.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Search.java index 8d20ca56f73..15f0a3499d7 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Search.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/Search.java @@ -26,8 +26,8 @@ public Popover> byName() { choiceBasicSearch(); - getParentElement().$(Schrodinger.byDataId("a", "mainButton")).waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - Selenide.sleep(2000); + getParentElement().$x(".//a[@"+Schrodinger.DATA_S_ID+"='mainButton']").waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); return new Popover<>(this, getDisplayedPopover()); } @@ -56,12 +56,12 @@ public InputBox> byFullText() { public Search addSearchItem(String name) { choiceBasicSearch(); - getParentElement().$(Schrodinger.byDataId("a", "more")).waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + getParentElement().$x(".//a[@"+Schrodinger.DATA_S_ID+"='more']").waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); SelenideElement popover = getDisplayedPopover(); - popover.$(Schrodinger.byDataId("input", "addText")).setValue(name); + popover.$x(".//input[@"+Schrodinger.DATA_S_ID+"='addText']").setValue(name); Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); - popover.$(Schrodinger.byDataId("a", "propLink")).click(); + popover.$x(".//a[@"+Schrodinger.DATA_S_ID+"='propLink']").click(); return this; } @@ -72,7 +72,7 @@ public Popover> byItem(String name) { SelenideElement item = getItemByName(name); if (item == null) { addSearchItem(name); - Selenide.sleep(2000); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); } item = getItemByName(name); if (item == null) { @@ -80,7 +80,7 @@ public Popover> byItem(String name) { } item.waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - Selenide.sleep(2000); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); return new Popover<>(this, getDisplayedPopover()); } @@ -112,7 +112,7 @@ public Search resetBasicSearch() { SelenideElement nameItem = getItemByName("Name"); if (nameItem != null) { nameItem.waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - Selenide.sleep(1000); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); new Popover<>(this, nameItem).inputValue("").updateSearch(); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java index 7863706117c..9f5082f7938 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetAssignmentsModal.java @@ -11,6 +11,8 @@ import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.component.FocusTableWithChoosableElements; import com.evolveum.midpoint.schrodinger.util.Schrodinger; +import com.evolveum.midpoint.util.aspect.MidpointInterceptor; + import org.openqa.selenium.By; import static com.codeborne.selenide.Selenide.$; @@ -56,7 +58,7 @@ public FocusSetAssignmentsModal selectIntent(String option) { public FocusTableWithChoosableElements> table() { SelenideElement resourcesBox = getParentElement().$x(".//div[@class='box boxed-table']"); - return new FocusTableWithChoosableElements<>(this, resourcesBox){ + return new FocusTableWithChoosableElements>(this, resourcesBox){ @@ -67,7 +69,7 @@ public T clickAdd() { $(Schrodinger.byDataResourceKey("userBrowserDialog.button.addButton")) .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); - + getParentElement().waitWhile(Condition.exist, MidPoint.TIMEOUT_LONG_1_M); return this.getParent(); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetProjectionModal.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetProjectionModal.java index 8a8e03fa41b..c85206d56f3 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetProjectionModal.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/modal/FocusSetProjectionModal.java @@ -24,7 +24,8 @@ public FocusSetProjectionModal(T parent, SelenideElement parentElement) { } public FocusTableWithChoosableElements> table() { - SelenideElement resourcesBox = $(By.cssSelector("box boxed-table")); + SelenideElement resourcesBox = getParentElement().$x(".//div[contains(@class, \"box\") " + + "and contains(@class, \"boxed-table\")]"); return new FocusTableWithChoosableElements<>(this, resourcesBox); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java index de0b79c6470..289257aa80b 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberPanel.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.schrodinger.component.org; import com.codeborne.selenide.Condition; +import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; @@ -14,6 +15,8 @@ import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListTable; import com.evolveum.midpoint.schrodinger.component.common.table.TableWithPageRedirect; import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; +import com.evolveum.midpoint.schrodinger.component.user.UsersPageTable; +import com.evolveum.midpoint.schrodinger.component.user.UsersTableDropDown; import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; import com.evolveum.midpoint.schrodinger.page.user.UserPage; import com.evolveum.midpoint.schrodinger.util.Schrodinger; @@ -60,18 +63,8 @@ public MemberPanel selectType(String type) { return this; } - public AssignmentHolderObjectListTable, AssignmentHolderDetailsPage> table() { + public MemberTable> table() { SelenideElement table = getParentElement().$x(".//div[@" + Schrodinger.DATA_S_ID + "='table']"); - return new AssignmentHolderObjectListTable, AssignmentHolderDetailsPage>(this, table) { - @Override - public AssignmentHolderDetailsPage getObjectDetailsPage() { - return new AssignmentHolderDetailsPage() {}; - } - - @Override - public

>> TableHeaderDropDownMenu

clickHeaderActionDropDown() { - return null; - } - }; + return new MemberTable<>(this, table); } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTable.java new file mode 100644 index 00000000000..03c6fc50f6d --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTable.java @@ -0,0 +1,46 @@ +/* + * 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.schrodinger.component.org; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.assignmentholder.AssignmentHolderObjectListTable; +import com.evolveum.midpoint.schrodinger.page.AssignmentHolderDetailsPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +import static com.codeborne.selenide.Selenide.$; + +/** + * @author skublik + */ + +public class MemberTable extends AssignmentHolderObjectListTable { + + public MemberTable(T parent, SelenideElement parentElement) { + super(parent, parentElement); + } + + @Override + public AssignmentHolderDetailsPage getObjectDetailsPage() { + return new AssignmentHolderDetailsPage() {}; + } + + @Override + public MemberTableDropDown> clickHeaderActionDropDown() { + SelenideElement dropDownButton = $(Schrodinger.bySelfOrAncestorElementAttributeValue("button", "data-toggle", "dropdown", + "class", "sortableLabel")); + dropDownButton.waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); + SelenideElement dropDown = dropDownButton.parent().$x(".//ul[@"+Schrodinger.DATA_S_ID+"='dropDownMenu']") + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); + + return new MemberTableDropDown>(this, dropDown); + } +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTableDropDown.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTableDropDown.java new file mode 100644 index 00000000000..5a1c21133ce --- /dev/null +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/MemberTableDropDown.java @@ -0,0 +1,51 @@ +/* + * 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.schrodinger.component.org; + +import com.codeborne.selenide.Condition; +import com.codeborne.selenide.SelenideElement; + +import com.evolveum.midpoint.schrodinger.MidPoint; +import com.evolveum.midpoint.schrodinger.component.AssignmentsTab; +import com.evolveum.midpoint.schrodinger.component.common.DropDown; +import com.evolveum.midpoint.schrodinger.component.modal.ConfirmationModal; +import com.evolveum.midpoint.schrodinger.component.modal.FocusSetAssignmentsModal; +import com.evolveum.midpoint.schrodinger.component.table.TableHeaderDropDownMenu; +import com.evolveum.midpoint.schrodinger.page.org.OrgPage; +import com.evolveum.midpoint.schrodinger.util.Schrodinger; + +import static com.codeborne.selenide.Selenide.$; + +/** + * @author skublik + */ + +public class MemberTableDropDown extends TableHeaderDropDownMenu { + public MemberTableDropDown(T parent, SelenideElement parentElement) { + super(parent, parentElement); + } + + public FocusSetAssignmentsModal assign(){ + getParentElement().$x(".//schrodinger[@"+ Schrodinger.DATA_S_RESOURCE_KEY +"='abstractRoleMemberPanel.menu.assign']").parent() + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + + SelenideElement modalElement = $(Schrodinger.byElementAttributeValue("div", "aria-labelledby", "Select object(s)")) + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); + + return new FocusSetAssignmentsModal((T) this.getParent(), modalElement); + } + + public ConfirmationModal recompute(){ + getParentElement().$x(".//schrodinger[@"+ Schrodinger.DATA_S_RESOURCE_KEY +"='abstractRoleMemberPanel.menu.recompute']").parent() + .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + + SelenideElement actualModal = $(Schrodinger.byElementAttributeValue("div", "aria-labelledby", "Confirm action")) + .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S); + + return new ConfirmationModal<>(this.getParent(), actualModal); + } +} diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java index c7798a701e0..5f6c5a469d5 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgHierarchyPanel.java @@ -89,7 +89,7 @@ public OrgHierarchyPanel expandOrg(String orgName) { return this; } - public OrgTreeNodeDropDown editOrg(String orgName) { + public OrgTreeNodeDropDown showTreeNodeDropDownMenu(String orgName) { SelenideElement parentNode = getParentOrgNode(orgName); SelenideElement node = parentNode.$x(".//div[@"+Schrodinger.DATA_S_ID+"='node']"); SelenideElement menuButton = node.$x(".//span[@" + Schrodinger.DATA_S_ID + "='menu']"); diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java index 1c4879b873d..97b20dee065 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/org/OrgTreeNodeDropDown.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.schrodinger.component.org; import com.codeborne.selenide.Condition; +import com.codeborne.selenide.Selenide; import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; @@ -27,6 +28,7 @@ public OrgTreeNodeDropDown(T parent, SelenideElement parentElement) { public OrgPage edit(){ getParentElement().$x(".//schrodinger[@"+ Schrodinger.DATA_S_RESOURCE_KEY +"='TreeTablePanel.edit']").parent() .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + Selenide.sleep(MidPoint.TIMEOUT_DEFAULT_2_S); return new OrgPage(); } } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java index aefa2b20f89..48e5902d6e4 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceAccountsTab.java @@ -71,7 +71,7 @@ public ResourceAccountsTab clickSearchInRepository() { .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); $(Schrodinger.byDataId("a", "repositorySearch")) - .waitUntil(Condition.enabled, MidPoint.TIMEOUT_DEFAULT_2_S); + .waitUntil(Condition.cssClass("active"), MidPoint.TIMEOUT_MEDIUM_6_S); return this; } @@ -80,7 +80,7 @@ public ResourceAccountsTab clickSearchInResource() { $(Schrodinger.byDataId("a", "resourceSearch")) .waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); $(Schrodinger.byDataId("a", "resourceSearch")) - .waitUntil(Condition.enabled, MidPoint.TIMEOUT_DEFAULT_2_S); + .waitUntil(Condition.cssClass("active"), MidPoint.TIMEOUT_MEDIUM_6_S); return this; } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java index b3f11c9a516..cedf5686602 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/resource/ResourceShadowTable.java @@ -28,8 +28,9 @@ public ResourceShadowTable(T parent, SelenideElement parentElement) { @Override public AccountPage clickByName(String name) { - getParentElement().$(Schrodinger.byElementValue("span", "data-s-id", "label", name)) - .waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + SelenideElement link = getParentElement().$(Schrodinger.byElementValue("span", "data-s-id", "label", name)); + link.waitUntil(Condition.appears, MidPoint.TIMEOUT_DEFAULT_2_S).click(); + link.waitWhile(Condition.exist, MidPoint.TIMEOUT_LONG_1_M); return new AccountPage(); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/AssignmentHolderDetailsPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/AssignmentHolderDetailsPage.java index 85513612e6f..de094491c6c 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/AssignmentHolderDetailsPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/AssignmentHolderDetailsPage.java @@ -50,7 +50,8 @@ private SelenideElement getPreviewButton() { public TabPanel getTabPanel() { SelenideElement tabPanelElement = $(Schrodinger.byDataId("div", "tabPanel")) - .waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); + .waitUntil(Condition.exist, MidPoint.TIMEOUT_DEFAULT_2_S); + tabPanelElement.waitUntil(Condition.appear, MidPoint.TIMEOUT_DEFAULT_2_S); return new TabPanel<>(this, tabPanelElement); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/resource/EditResourceConfigurationPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/resource/EditResourceConfigurationPage.java index cb3d77753c1..931b4128b63 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/resource/EditResourceConfigurationPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/resource/EditResourceConfigurationPage.java @@ -60,10 +60,10 @@ public ResourceResultsHandlersTab selectTabResultHandlers() { public TestConnectionModal clickSaveAndTestConnection() { $(Schrodinger.byDataId("testConnection")).waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S).click(); -// Selenide.sleep(MidPoint.TIMEOUT_LONG_1_M); SelenideElement testModalBox = $(Schrodinger .byElementAttributeValue("div", "aria-labelledby", "Test connection result(s)")) - .waitUntil(Condition.visible, MidPoint.TIMEOUT_EXTRA_LONG_1_M); + .waitUntil(Condition.exist, MidPoint.TIMEOUT_LONG_1_M) + .waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S); return new TestConnectionModal<>(this, testModalBox); } From eca047f84dda3a82ee95819dd8dd3c8857b3e2c1 Mon Sep 17 00:00:00 2001 From: Richard Richter Date: Mon, 27 Apr 2020 10:03:23 +0200 Subject: [PATCH 27/27] changes in comments: Schroedinger -> Schrodinger + mvn example in POM --- .../intest/manual/AbstractDirectManualResourceTest.java | 4 ++-- pom.xml | 5 ++++- .../com/evolveum/midpoint/provisioning/impl/ShadowCache.java | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractDirectManualResourceTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractDirectManualResourceTest.java index 7bfed8ed0e3..9858f65405c 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractDirectManualResourceTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/manual/AbstractDirectManualResourceTest.java @@ -1120,7 +1120,7 @@ public void test302RecomputeWill() throws Exception { /** * Case is closed. The unassign operation is complete. * However, in the semi-manual case this gets really interesting. - * We have Schroedinger's shadow here. deleted account, ticket closed, account is deleted + * We have Schrodinger's shadow here. deleted account, ticket closed, account is deleted * by administrator in the target system. But the account is still in the backing store (CSV) * because scheduled export has not refreshed the file yet. */ @@ -1237,7 +1237,7 @@ public void test320RecomputeWillAfter5min() throws Exception { } /** - * For semi-manual case this is the place where the quantum state of Schroedinger's + * For semi-manual case this is the place where the quantum state of Schrodinger's * shadow collapses. From now on we should have ordinary tombstone shadow. */ @Test diff --git a/pom.xml b/pom.xml index ad169d1d14d..5f7ffa3c0b8 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,9 @@ Running unit+IT tests of a selected module (-pl) with output to stdout: mvn clean install -DredirectTestOutputToFile=false -pl model/model-intest + + Running GUI tests from Schrodinger module only with custom location of ChromeDriver (Chrome must be istalled too): + mvn clean install -P -dist -DskipTests -DskipSchrodingerTests=false -DwebdriverLocation=/c/work/tools/bin/chromedriver.exe --> com.evolveum.midpoint @@ -1468,7 +1471,7 @@ 3.5.1 - + com.codeborne selenide diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java index 5c43eb6d2e2..a707279964a 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java @@ -408,7 +408,7 @@ private boolean canImmediatelyReturnCached(Collection