/* * Copyright (c) 2010-2016 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.certification.impl; import com.evolveum.midpoint.certification.api.OutcomeUtils; import com.evolveum.midpoint.certification.impl.handlers.CertificationHandler; import com.evolveum.midpoint.common.Clock; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.prism.marshaller.QueryConvertor; import com.evolveum.midpoint.prism.path.IdItemPathSegment; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.NameItemPathSegment; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; 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.ResultHandler; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CertCampaignTypeUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.security.api.SecurityEnforcer; 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.query_3.SearchFilterType; import org.apache.commons.lang.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import java.util.*; import java.util.Objects; import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.toShortString; import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCampaignType.F_CASE; import static com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationCaseType.*; /** * @author mederly */ @Component public class AccCertCaseOperationsHelper { private static final transient Trace LOGGER = TraceManager.getTrace(AccCertCaseOperationsHelper.class); @Autowired private AccCertReviewersHelper reviewersHelper; @Autowired private PrismContext prismContext; @Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; @Autowired private SecurityEnforcer securityEnforcer; @Autowired private AccCertGeneralHelper generalHelper; @Autowired private AccCertResponseComputationHelper computationHelper; @Autowired private AccCertQueryHelper queryHelper; @Autowired private AccCertUpdateHelper updateHelper; @Autowired private Clock clock; void recordDecision(String campaignOid, long caseId, long workItemId, AccessCertificationResponseType response, String comment, Task task, OperationResult result) throws SecurityViolationException, ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException { AccessCertificationCaseType _case = queryHelper.getCase(campaignOid, caseId, task, result); if (_case == null) { throw new ObjectNotFoundException("Case " + caseId + " was not found in campaign " + campaignOid); } AccessCertificationCampaignType campaign = CertCampaignTypeUtil.getCampaign(_case); if (campaign == null) { throw new IllegalStateException("No owning campaign present in case " + _case); } AccessCertificationWorkItemType workItem = CertCampaignTypeUtil.findWorkItem(_case, workItemId); if (workItem == null) { throw new ObjectNotFoundException("Work item " + workItemId + " was not found in campaign " + toShortString(campaign) + ", case " + caseId); } if (response == AccessCertificationResponseType.NO_RESPONSE) { response = null; } ObjectReferenceType responderRef = ObjectTypeUtil.createObjectRef(securityEnforcer.getPrincipal().getUser()); XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); ItemPath workItemPath = new ItemPath(F_CASE, caseId, F_WORK_ITEM, workItemId); Collection<ItemDelta<?,?>> deltaList = DeltaBuilder.deltaFor(AccessCertificationCampaignType.class, prismContext) .item(workItemPath.subPath(AccessCertificationWorkItemType.F_OUTPUT)).replace(new AbstractWorkItemOutputType() .outcome(OutcomeUtils.toUri(response)) .comment(comment)) .item(workItemPath.subPath(AccessCertificationWorkItemType.F_OUTPUT_CHANGE_TIMESTAMP)).replace(now) .item(workItemPath.subPath(AccessCertificationWorkItemType.F_PERFORMER_REF)).replace(responderRef) .asItemDeltas(); ItemDelta.applyTo(deltaList, campaign.asPrismContainerValue()); String newCurrentOutcome = OutcomeUtils.toUri(computationHelper.computeOutcomeForStage(_case, campaign, campaign.getStageNumber())); if (!ObjectUtils.equals(newCurrentOutcome, _case.getCurrentStageOutcome())) { deltaList.add(DeltaBuilder.deltaFor(AccessCertificationCampaignType.class, prismContext) .item(F_CASE, _case.asPrismContainerValue().getId(), F_CURRENT_STAGE_OUTCOME).replace(newCurrentOutcome) .asItemDelta()); } String newOverallOutcome = OutcomeUtils.toUri(computationHelper.computeOverallOutcome(_case, campaign, newCurrentOutcome)); if (!ObjectUtils.equals(newOverallOutcome, _case.getOutcome())) { deltaList.add(DeltaBuilder.deltaFor(AccessCertificationCampaignType.class, prismContext) .item(F_CASE, _case.asPrismContainerValue().getId(), F_OUTCOME).replace(newOverallOutcome) .asItemDelta()); } updateHelper.modifyObjectViaModel(AccessCertificationCampaignType.class, campaignOid, deltaList, task, result); } <F extends FocusType> List<ItemDelta<?,?>> getDeltasToCreateCases( final AccessCertificationCampaignType campaign, AccessCertificationStageType stage, final CertificationHandler handler, final Task task, final OperationResult result) throws SchemaException, ObjectNotFoundException { final List<ItemDelta<?,?>> rv = new ArrayList<>(); final String campaignShortName = toShortString(campaign); final AccessCertificationScopeType scope = campaign.getScopeDefinition(); LOGGER.trace("Creating cases for scope {} in campaign {}", scope, campaignShortName); if (scope != null && !(scope instanceof AccessCertificationObjectBasedScopeType)) { throw new IllegalStateException("Unsupported access certification scope type: " + scope.getClass() + " for campaign " + campaignShortName); } final AccessCertificationObjectBasedScopeType objectBasedScope = (AccessCertificationObjectBasedScopeType) scope; final List<AccessCertificationCaseType> existingCases = queryHelper.searchCases(campaign.getOid(), null, null, result); if (!existingCases.isEmpty()) { throw new IllegalStateException("Unexpected " + existingCases.size() + " certification case(s) in campaign object " + campaignShortName + ". At this time there should be none."); } // create a query to find target objects from which certification cases will be created final ObjectQuery query = new ObjectQuery(); final QName scopeDeclaredObjectType; if (objectBasedScope != null) { scopeDeclaredObjectType = objectBasedScope.getObjectType(); } else { scopeDeclaredObjectType = null; } final QName objectType; if (scopeDeclaredObjectType != null) { objectType = scopeDeclaredObjectType; } else { objectType = handler.getDefaultObjectType(); } if (objectType == null) { throw new IllegalStateException("Unspecified object type (and no default one provided) for campaign " + campaignShortName); } @SuppressWarnings({ "unchecked", "raw" }) final Class<F> objectClass = (Class<F>) prismContext.getSchemaRegistry().getCompileTimeClassForObjectType(objectType); if (objectClass == null) { throw new IllegalStateException("Object class not found for object type " + objectType + " in campaign " + campaignShortName); } final SearchFilterType searchFilter = objectBasedScope != null ? objectBasedScope.getSearchFilter() : null; if (searchFilter != null) { ObjectFilter filter = QueryConvertor.parseFilter(searchFilter, objectClass, prismContext); query.setFilter(filter); } final List<AccessCertificationCaseType> caseList = new ArrayList<>(); // create certification cases by executing the query and caseExpression on its results // here the subclasses of this class come into play ResultHandler<F> resultHandler = (object, parentResult) -> { try { caseList.addAll(handler.createCasesForObject(object, campaign, task, parentResult)); } catch (ExpressionEvaluationException|ObjectNotFoundException|SchemaException e) { // TODO process the exception more intelligently throw new SystemException("Cannot create certification case for object " + toShortString(object.asObjectable()) + ": " + e.getMessage(), e); } return true; }; repositoryService.searchObjectsIterative(objectClass, query, resultHandler, null, false, result); AccessCertificationReviewerSpecificationType reviewerSpec = reviewersHelper.findReviewersSpecification(campaign, 1, task, result); ContainerDelta<AccessCertificationCaseType> caseDelta = ContainerDelta.createDelta(F_CASE, AccessCertificationCampaignType.class, prismContext); for (AccessCertificationCaseType _case : caseList) { _case.setStageNumber(1); _case.setCurrentStageCreateTimestamp(stage.getStartTimestamp()); _case.setCurrentStageDeadline(stage.getDeadline()); List<ObjectReferenceType> reviewers = reviewersHelper.getReviewersForCase(_case, campaign, reviewerSpec, task, result); _case.getWorkItem().addAll(createWorkItems(reviewers, 1)); String currentStageOutcome = OutcomeUtils.toUri(computationHelper.computeOutcomeForStage(_case, campaign, 1)); _case.setCurrentStageOutcome(currentStageOutcome); _case.setOutcome(OutcomeUtils.toUri(computationHelper.computeOverallOutcome(_case, campaign, currentStageOutcome))); @SuppressWarnings({ "raw", "unchecked" }) PrismContainerValue<AccessCertificationCaseType> caseCVal = _case.asPrismContainerValue(); caseDelta.addValueToAdd(caseCVal); LOGGER.trace("Adding certification case:\n{}", caseCVal.debugDumpLazily()); } rv.add(caseDelta); LOGGER.trace("Created {} deltas to create {} cases for campaign {}", rv.size(), caseList.size(), campaignShortName); return rv; } private List<AccessCertificationWorkItemType> createWorkItems(List<ObjectReferenceType> forReviewers, int forStage) { List<AccessCertificationWorkItemType> workItems = new ArrayList<>(); for (ObjectReferenceType reviewer : forReviewers) { AccessCertificationWorkItemType workItem = new AccessCertificationWorkItemType(prismContext) .stageNumber(forStage) .assigneeRef(reviewer.clone()) .originalAssigneeRef(reviewer.clone()); workItems.add(workItem); } return workItems; } List<ItemDelta<?,?>> getDeltasToAdvanceCases(AccessCertificationCampaignType campaign, AccessCertificationStageType stage, Task task, OperationResult result) throws SchemaException, ObjectAlreadyExistsException, ObjectNotFoundException { LOGGER.trace("Advancing reviewers and timestamps for cases in {}", toShortString(campaign)); List<AccessCertificationCaseType> caseList = queryHelper.searchCases(campaign.getOid(), null, null, result); List<ItemDelta<?,?>> rv = new ArrayList<>(caseList.size()); int stageToBe = campaign.getStageNumber() + 1; List<AccessCertificationResponseType> outcomesToStopOn = computationHelper.getOutcomesToStopOn(campaign); AccessCertificationReviewerSpecificationType reviewerSpec = reviewersHelper.findReviewersSpecification(campaign, stageToBe, task, result); for (AccessCertificationCaseType _case : caseList) { if (!computationHelper.computeEnabled(campaign, _case, outcomesToStopOn)) { continue; } Long caseId = _case.asPrismContainerValue().getId(); assert caseId != null; List<ObjectReferenceType> reviewers = reviewersHelper.getReviewersForCase(_case, campaign, reviewerSpec, task, result); List<AccessCertificationWorkItemType> workItems = createWorkItems(reviewers, stageToBe); _case.getWorkItem().addAll(CloneUtil.cloneCollectionMembers(workItems)); AccessCertificationResponseType currentOutcome = computationHelper.computeOutcomeForStage(_case, campaign, stageToBe); AccessCertificationResponseType overallOutcome = computationHelper.computeOverallOutcome(_case, campaign, currentOutcome); rv.addAll(DeltaBuilder.deltaFor(AccessCertificationCampaignType.class, prismContext) .item(F_CASE, caseId, F_WORK_ITEM).add(PrismContainerValue.toPcvList(workItems)) .item(F_CASE, caseId, F_CURRENT_STAGE_CREATE_TIMESTAMP).replace(stage.getStartTimestamp()) .item(F_CASE, caseId, F_CURRENT_STAGE_DEADLINE).replace(stage.getDeadline()) .item(F_CASE, caseId, F_CURRENT_STAGE_OUTCOME).replace(OutcomeUtils.toUri(currentOutcome)) .item(F_CASE, caseId, F_OUTCOME).replace(OutcomeUtils.toUri(overallOutcome)) .item(F_CASE, caseId, F_STAGE_NUMBER).replace(stageToBe) .asItemDeltas()); } LOGGER.debug("Created {} deltas to advance {} cases for campaign {}", rv.size(), caseList.size(), toShortString(campaign)); return rv; } // computes outcomes at stage close (stage-level and overall) and creates appropriate deltas List<ItemDelta<?,?>> createOutcomeDeltas(AccessCertificationCampaignType campaign, OperationResult result) throws ObjectNotFoundException, SchemaException { List<ItemDelta<?,?>> rv = new ArrayList<>(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Updating current outcome for cases in {}", toShortString(campaign)); } List<AccessCertificationCaseType> caseList = queryHelper.searchCases(campaign.getOid(), null, null, result); for (AccessCertificationCaseType _case : caseList) { if (_case.getStageNumber() != campaign.getStageNumber()) { continue; } String newStageOutcome = OutcomeUtils.toUri(computationHelper.computeOutcomeForStage(_case, campaign, campaign.getStageNumber())); if (!Objects.equals(newStageOutcome, _case.getCurrentStageOutcome())) { rv.add(DeltaBuilder.deltaFor(AccessCertificationCampaignType.class, prismContext) .item(F_CASE, _case.asPrismContainerValue().getId(), F_CURRENT_STAGE_OUTCOME).replace(newStageOutcome) .asItemDelta()); } rv.add(DeltaBuilder.deltaFor(AccessCertificationCampaignType.class, prismContext) .item(F_CASE, _case.asPrismContainerValue().getId(), F_EVENT).add(new StageCompletionEventType() .timestamp(clock.currentTimeXMLGregorianCalendar()) .stageNumber(campaign.getStageNumber()) .outcome(newStageOutcome)) .asItemDelta()); String newOverallOutcome = OutcomeUtils.toUri(computationHelper.computeOverallOutcome(_case, campaign, newStageOutcome)); if (!Objects.equals(newOverallOutcome, _case.getOutcome())) { rv.add(DeltaBuilder.deltaFor(AccessCertificationCampaignType.class, prismContext) .item(F_CASE, _case.asPrismContainerValue().getId(), F_OUTCOME).replace(newOverallOutcome) .asItemDelta()); } } return rv; } // TODO temporary implementation - should be done somehow in batches in order to improve performance void markCaseAsRemedied(@NotNull String campaignOid, long caseId, Task task, OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, SecurityViolationException { PropertyDelta<XMLGregorianCalendar> reviewRemediedDelta = PropertyDelta.createModificationReplaceProperty( new ItemPath( new NameItemPathSegment(F_CASE), new IdItemPathSegment(caseId), new NameItemPathSegment(AccessCertificationCaseType.F_REMEDIED_TIMESTAMP)), generalHelper.getCampaignObjectDefinition(), XmlTypeConverter.createXMLGregorianCalendar(new Date())); updateHelper.modifyObjectViaModel(AccessCertificationCampaignType.class, campaignOid, Collections.singletonList(reviewRemediedDelta), task, parentResult); } }