/* * Copyright (c) 2010-2015 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.schema.util; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.stream.Collectors; /** * @author mederly */ public class CertCampaignTypeUtil { public static AccessCertificationStageType getCurrentStage(AccessCertificationCampaignType campaign) { for (AccessCertificationStageType stage : campaign.getStage()) { if (stage.getNumber() == campaign.getStageNumber()) { return stage; } } return null; } public static AccessCertificationStageDefinitionType getCurrentStageDefinition(AccessCertificationCampaignType campaign) { return findStageDefinition(campaign, campaign.getStageNumber()); } @NotNull public static AccessCertificationStageDefinitionType findStageDefinition(AccessCertificationCampaignType campaign, int stageNumber) { for (AccessCertificationStageDefinitionType stage : campaign.getStageDefinition()) { if (stage.getNumber() == stageNumber) { return stage; } } throw new IllegalStateException("No stage " + stageNumber + " in " + ObjectTypeUtil.toShortString(campaign)); } public static AccessCertificationStageType findStage(AccessCertificationCampaignType campaign, int stageNumber) { for (AccessCertificationStageType stage : campaign.getStage()) { if (stage.getNumber() == stageNumber) { return stage; } } throw new IllegalStateException("No stage " + stageNumber + " in " + ObjectTypeUtil.toShortString(campaign)); } public static AccessCertificationCaseType findCase(AccessCertificationCampaignType campaign, long caseId) { for (AccessCertificationCaseType _case : campaign.getCase()) { if (_case.asPrismContainerValue().getId() != null && _case.asPrismContainerValue().getId() == caseId) { return _case; } } return null; } // to be used in tests (beware: there could be more work items) // TODO move to a test class public static AccessCertificationWorkItemType findWorkItem(AccessCertificationCaseType _case, int stageNumber, String reviewerOid) { return _case.getWorkItem().stream() .filter(wi -> wi.getStageNumber() == stageNumber && ObjectTypeUtil.containsOid(wi.getAssigneeRef(), reviewerOid)) .findFirst().orElse(null); } public static AccessCertificationWorkItemType findWorkItem(AccessCertificationCaseType _case, long workItemId) { return _case.getWorkItem().stream() .filter(wi -> wi.getId() != null && wi.getId() == workItemId) .findFirst().orElse(null); } public static int getNumberOfStages(AccessCertificationCampaignType campaign) { return campaign.getStageDefinition().size(); } public static AccessCertificationDefinitionType getDefinition(AccessCertificationCampaignType campaign) { if (campaign.getDefinitionRef() == null) { throw new IllegalStateException("No definition reference in " + ObjectTypeUtil.toShortString(campaign)); } PrismReferenceValue referenceValue = campaign.getDefinitionRef().asReferenceValue(); if (referenceValue.getObject() == null) { throw new IllegalStateException("No definition object in " + ObjectTypeUtil.toShortString(campaign)); } return (AccessCertificationDefinitionType) (referenceValue.getObject().asObjectable()); } public static boolean isRemediationAutomatic(AccessCertificationCampaignType campaign) { return campaign.getRemediationDefinition() != null && AccessCertificationRemediationStyleType.AUTOMATED.equals(campaign.getRemediationDefinition().getStyle()); } public static boolean isCampaignClosed(AccessCertificationCampaignType campaign) { int currentStage = campaign.getStageNumber(); int stages = getNumberOfStages(campaign); return AccessCertificationCampaignStateType.CLOSED.equals(campaign.getState()) || currentStage > stages; } // TODO rework signalling problems public static void checkStageDefinitionConsistency(List<AccessCertificationStageDefinitionType> stages) { int count = stages.size(); boolean[] numberPresent = new boolean[count]; for (AccessCertificationStageDefinitionType stage : stages) { int num = stage.getNumber(); if (num < 1 || num > count) { throw new IllegalStateException("Invalid stage number: " + num + " (stage count: " + count +")"); } if (numberPresent[num-1]) { throw new IllegalStateException("Stage with number " + num + " is defined multiple times"); } numberPresent[num-1] = true; } // maybe redundant test for (int i = 0; i < numberPresent.length; i++) { if (!numberPresent[i]) { throw new IllegalStateException("Stage with number " + (i+1) + " was not defined"); } } } // expects that the currentStageNumber is reasonable public static AccessCertificationStageType findCurrentStage(AccessCertificationCampaignType campaign) { return findStage(campaign, campaign.getStageNumber()); } // active cases = cases that are to be responded to in this stage public static int getActiveCases(List<AccessCertificationCaseType> caseList, int campaignStageNumber, AccessCertificationCampaignStateType state) { int open = 0; if (state == AccessCertificationCampaignStateType.IN_REMEDIATION || state == AccessCertificationCampaignStateType.CLOSED) { campaignStageNumber = campaignStageNumber - 1; // move to last campaign state } for (AccessCertificationCaseType aCase : caseList) { if (aCase.getStageNumber() != campaignStageNumber) { continue; } open++; } return open; } // unanswered cases = cases where one or more answers from reviewers are missing // "no reviewers" cases are treated as answered, because no answer can be provided // closeTimestamp is not checked here: we are NOT interested in "answerable" cases; we are interested in how many cases has an answer public static int getUnansweredCases(List<AccessCertificationCaseType> caseList, int campaignStageNumber, AccessCertificationCampaignStateType state) { int unansweredCases = 0; if (state == AccessCertificationCampaignStateType.IN_REMEDIATION || state == AccessCertificationCampaignStateType.CLOSED) { campaignStageNumber = campaignStageNumber - 1; // move to last campaign state } for (AccessCertificationCaseType aCase : caseList) { for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) { if (workItem.getStageNumber() == campaignStageNumber && WorkItemTypeUtil.getOutcome(workItem) == null) { unansweredCases++; break; } } } return unansweredCases; } // backwards compatibility (for reports) public static int getPercentComplete(List<AccessCertificationCaseType> caseList, int campaignStageNumber, AccessCertificationCampaignStateType state) { return Math.round(getCasesCompletedPercentage(caseList, campaignStageNumber, state)); } // what % of cases is fully decided? (i.e. either they have all the decisions or they are not in the current stage at all) public static float getCasesCompletedPercentage(AccessCertificationCampaignType campaign) { return getCasesCompletedPercentage(campaign.getCase(), campaign.getStageNumber(), campaign.getState()); } public static float getCasesCompletedPercentage(List<AccessCertificationCaseType> caseList, int campaignStageNumber, AccessCertificationCampaignStateType state) { int cases = caseList.size(); if (cases > 0) { int unanswered = getUnansweredCases(caseList, campaignStageNumber, state); return 100 * ((float) cases - (float) unanswered) / (float) cases; } else { return 100.0f; } } // what % of cases is effectively decided? (i.e. their preliminary outcome is ACCEPT, REJECT, or REVOKE) public static float getCasesDecidedPercentage(AccessCertificationCampaignType campaign) { return getCasesDecidedPercentage(campaign.getCase()); } public static float getCasesDecidedPercentage(List<AccessCertificationCaseType> caseList) { if (caseList.isEmpty()) { return 100.0f; } int decided = 0; for (AccessCertificationCaseType aCase : caseList) { if (QNameUtil.matchUri(aCase.getOutcome(), SchemaConstants.MODEL_CERTIFICATION_OUTCOME_ACCEPT) || QNameUtil.matchUri(aCase.getOutcome(), SchemaConstants.MODEL_CERTIFICATION_OUTCOME_REVOKE) || QNameUtil.matchUri(aCase.getOutcome(), SchemaConstants.MODEL_CERTIFICATION_OUTCOME_REDUCE)) { decided++; } } return 100.0f * (float) decided / (float) caseList.size(); } // what % of decisions is complete? public static float getDecisionsDonePercentage(AccessCertificationCampaignType campaign) { return getDecisionsDonePercentage(campaign.getCase(), campaign.getStageNumber(), campaign.getState()); } public static float getDecisionsDonePercentage(List<AccessCertificationCaseType> caseList, int campaignStageNumber, AccessCertificationCampaignStateType state) { int decisionsRequested = 0; int decisionsDone = 0; if (state == AccessCertificationCampaignStateType.IN_REMEDIATION || state == AccessCertificationCampaignStateType.CLOSED) { campaignStageNumber = campaignStageNumber - 1; // move to last campaign state } for (AccessCertificationCaseType aCase : caseList) { for (AccessCertificationWorkItemType workItem : aCase.getWorkItem()) { if (workItem.getStageNumber() != campaignStageNumber) { continue; } decisionsRequested++; if (WorkItemTypeUtil.getOutcome(workItem) != null) { decisionsDone++; } } } if (decisionsRequested == 0) { return 100.0f; } else { return 100.0f * (float) decisionsDone / (float) decisionsRequested; } } public static Date getReviewedTimestamp(List<AccessCertificationWorkItemType> workItems) { Date lastDate = null; for (AccessCertificationWorkItemType workItem : workItems) { if (hasNoResponse(workItem)) { continue; } Date responseDate = XmlTypeConverter.toDate(workItem.getOutputChangeTimestamp()); if (lastDate == null || responseDate.after(lastDate)) { lastDate = responseDate; } } return lastDate; } private static boolean hasNoResponse(AccessCertificationWorkItemType workItem) { return WorkItemTypeUtil.getOutcome(workItem) == null && StringUtils.isEmpty(WorkItemTypeUtil.getComment(workItem)); } public static List<ObjectReferenceType> getReviewedBy(List<AccessCertificationWorkItemType> workItems) { List<ObjectReferenceType> rv = new ArrayList<>(); for (AccessCertificationWorkItemType workItem : workItems) { if (hasNoResponse(workItem)) { continue; } rv.add(workItem.getPerformerRef()); } return rv; } public static List<String> getComments(List<AccessCertificationWorkItemType> workItems) { List<String> rv = new ArrayList<>(); for (AccessCertificationWorkItemType workItem : workItems) { if (StringUtils.isEmpty(WorkItemTypeUtil.getComment(workItem))) { continue; } rv.add(WorkItemTypeUtil.getComment(workItem)); } return rv; } // // TODO find a better place for this // @Deprecated // public static ItemPath getOrderBy(QName oldName) { // if (QNameUtil.match(oldName, AccessCertificationCaseType.F_TARGET_REF)) { // return new ItemPath(AccessCertificationCaseType.F_TARGET_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME); // } else if (QNameUtil.match(oldName, AccessCertificationCaseType.F_OBJECT_REF)) { // return new ItemPath(AccessCertificationCaseType.F_OBJECT_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME); // } else if (QNameUtil.match(oldName, AccessCertificationCaseType.F_TENANT_REF)) { // return new ItemPath(AccessCertificationCaseType.F_TENANT_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME); // } else if (QNameUtil.match(oldName, AccessCertificationCaseType.F_ORG_REF)) { // return new ItemPath(AccessCertificationCaseType.F_ORG_REF, PrismConstants.T_OBJECT_REFERENCE, ObjectType.F_NAME); // } else { // return new ItemPath(oldName); // } // } public static ObjectQuery createCasesForCampaignQuery(String campaignOid, PrismContext prismContext) throws SchemaException { return QueryBuilder.queryFor(AccessCertificationCaseType.class, prismContext) .ownerId(campaignOid) .build(); } public static ObjectQuery createWorkItemsForCampaignQuery(String campaignOid, PrismContext prismContext) throws SchemaException { return QueryBuilder.queryFor(AccessCertificationWorkItemType.class, prismContext) .exists(PrismConstants.T_PARENT) .ownerId(campaignOid) .build(); } // some methods, like searchOpenWorkItems, engage their own "openness" filter public static ObjectQuery createOpenWorkItemsForCampaignQuery(String campaignOid, PrismContext prismContext) throws SchemaException { return QueryBuilder.queryFor(AccessCertificationWorkItemType.class, prismContext) .item(AccessCertificationWorkItemType.F_CLOSE_TIMESTAMP).isNull() .and().exists(PrismConstants.T_PARENT) .ownerId(campaignOid) .build(); } public static String getStageOutcome(AccessCertificationCaseType aCase, int stageNumber) { StageCompletionEventType event = aCase.getEvent().stream() .filter(e -> e instanceof StageCompletionEventType && e.getStageNumber() == stageNumber) .map(e -> (StageCompletionEventType) e) .findAny().orElseThrow( () -> new IllegalStateException("No outcome registered for stage " + stageNumber + " in case " + aCase)); return event.getOutcome(); } public static List<AccessCertificationResponseType> getOutcomesToStopOn(List<AccessCertificationResponseType> stopReviewOn, List<AccessCertificationResponseType> advanceToNextStageOn) { if (!stopReviewOn.isEmpty()) { return stopReviewOn; } List<AccessCertificationResponseType> rv = new ArrayList<>(Arrays.asList(AccessCertificationResponseType.values())); rv.removeAll(advanceToNextStageOn); return rv; } // useful e.g. for tests public static Set<ObjectReferenceType> getCurrentReviewers(AccessCertificationCaseType aCase) { return aCase.getWorkItem().stream() // TODO check also with campaign stage? .filter(wi -> wi.getStageNumber() == aCase.getStageNumber()) .flatMap(wi -> wi.getAssigneeRef().stream()) .collect(Collectors.toSet()); } @NotNull public static AccessCertificationCaseType getCaseChecked(AccessCertificationWorkItemType workItem) { AccessCertificationCaseType aCase = getCase(workItem); if (aCase == null) { throw new IllegalStateException("No certification case for work item " + workItem); } return aCase; } @NotNull public static AccessCertificationCampaignType getCampaignChecked(AccessCertificationCaseType aCase) { AccessCertificationCampaignType campaign = getCampaign(aCase); if (campaign == null) { throw new IllegalStateException("No certification campaign for case " + aCase); } return campaign; } @NotNull public static AccessCertificationCampaignType getCampaignChecked(AccessCertificationWorkItemType workItem) { return getCampaignChecked(getCaseChecked(workItem)); } public static AccessCertificationCaseType getCase(AccessCertificationWorkItemType workItem) { @SuppressWarnings({"unchecked", "raw"}) PrismContainerable<AccessCertificationWorkItemType> parent = workItem.asPrismContainerValue().getParent(); if (!(parent instanceof PrismContainer)) { return null; } PrismValue parentParent = ((PrismContainer<AccessCertificationWorkItemType>) parent).getParent(); if (!(parentParent instanceof PrismContainerValue)) { return null; } @SuppressWarnings({"unchecked", "raw"}) PrismContainerValue<AccessCertificationCaseType> parentParentPcv = (PrismContainerValue<AccessCertificationCaseType>) parentParent; return parentParentPcv.asContainerable(); } public static AccessCertificationCampaignType getCampaign(AccessCertificationCaseType aCase) { @SuppressWarnings({"unchecked", "raw"}) PrismContainer<AccessCertificationCaseType> caseContainer = (PrismContainer<AccessCertificationCaseType>) aCase.asPrismContainerValue().getParent(); if (caseContainer == null) { return null; } @SuppressWarnings({"unchecked", "raw"}) PrismContainerValue<AccessCertificationCampaignType> campaignValue = (PrismContainerValue<AccessCertificationCampaignType>) caseContainer.getParent(); if (campaignValue == null) { return null; } PrismObject<AccessCertificationCampaignType> campaign = (PrismObject<AccessCertificationCampaignType>) campaignValue.getParent(); return campaign != null ? campaign.asObjectable() : null; } public static List<String> getOutcomesFromCompletedStages(AccessCertificationCaseType aCase) { return aCase.getEvent().stream() .filter(e -> e instanceof StageCompletionEventType) .map(e -> ((StageCompletionEventType) e).getOutcome()) .collect(Collectors.toList()); } @NotNull public static List<StageCompletionEventType> getCompletedStageEvents(AccessCertificationCaseType aCase) { return aCase.getEvent().stream() .filter(e -> e instanceof StageCompletionEventType) .map(e -> (StageCompletionEventType) e) .collect(Collectors.toList()); } public static int getCurrentStageEscalationLevelNumberSafe(@NotNull AccessCertificationCampaignType campaign) { AccessCertificationStageType currentStage = getCurrentStage(campaign); return currentStage != null ? getEscalationLevelNumber(currentStage) : 0; } public static int getCurrentStageEscalationLevelNumber(@NotNull AccessCertificationCampaignType campaign) { AccessCertificationStageType currentStage = getCurrentStage(campaign); if (currentStage == null) { throw new IllegalStateException("No current stage for " + campaign); } return getEscalationLevelNumber(currentStage); } private static int getEscalationLevelNumber(AccessCertificationStageType currentStage) { if (currentStage.getEscalationLevel() != null && currentStage.getEscalationLevel().getNumber() != null) { return currentStage.getEscalationLevel().getNumber(); } else { return 0; } } // see WfContextUtil.getEscalationLevelInfo @Nullable public static String getEscalationLevelInfo(AccessCertificationCampaignType campaign) { if (campaign == null) { return null; } AccessCertificationStageType stage = getCurrentStage(campaign); return stage != null ? WfContextUtil.getEscalationLevelInfo(stage.getEscalationLevel()) : null; } }