/* * 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.provisioning.consistency.impl; import java.util.Collection; import java.util.List; import com.evolveum.midpoint.schema.result.OperationResultStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; 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.delta.PropertyDelta; import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; import com.evolveum.midpoint.provisioning.api.ResourceOperationDescription; import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler; import com.evolveum.midpoint.provisioning.impl.ConstraintsChecker; import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; @Component public class ObjectNotFoundHandler extends ErrorHandler { @Autowired @Qualifier("cacheRepositoryService") private RepositoryService cacheRepositoryService; @Autowired(required = true) private ProvisioningService provisioningService; @Autowired(required = true) private TaskManager taskManager; private static final Trace LOGGER = TraceManager.getTrace(ObjectNotFoundHandler.class); /** * Get the value of repositoryService. * * @return the value of repositoryService */ public RepositoryService getCacheRepositoryService() { return cacheRepositoryService; } /** * Set the value of repositoryService * * Expected to be injected. * * @param repositoryService * new value of repositoryService */ public void setCacheRepositoryService(RepositoryService repositoryService) { this.cacheRepositoryService = repositoryService; } @Override public <T extends ShadowType> T handleError(T shadow, FailedOperation op, Exception ex, boolean doDiscovery, boolean compensate, Task task, OperationResult parentResult) throws SchemaException, GenericFrameworkException, CommunicationException, ObjectNotFoundException, ObjectAlreadyExistsException, ConfigurationException, SecurityViolationException { if (!doDiscovery) { parentResult.recordFatalError(ex); if (ex instanceof ObjectNotFoundException) { throw (ObjectNotFoundException)ex; } else { throw new ObjectNotFoundException(ex.getMessage(), ex); } } OperationResult result = parentResult .createSubresult("com.evolveum.midpoint.provisioning.consistency.impl.ObjectNotFoundHandler.handleError." + op.name()); result.addParam("shadow", shadow); result.addParam("currentOperation", op); if (ex.getMessage() != null) { result.addParam("exception", ex.getMessage()); } LOGGER.trace("Start compensating object not found situation while execution operation: {}", op.name(), ex); // Task task = taskManager.createTaskInstance(); ObjectDelta delta = null; switch (op) { case DELETE: LOGGER.debug("DISCOVERY: cannot find object {}. The operation in progress is DELETE, therefore just deleting the shadow", shadow); LOGGER.trace("Deleting shadow from the repository."); for (OperationResult subResult : parentResult.getSubresults()){ subResult.muteError(); } try { cacheRepositoryService.deleteObject(ShadowType.class, shadow.getOid(), result); } catch (ObjectNotFoundException e) { LOGGER.debug("Cannot delete {} in consistency compensation (discovery): {} - this is probably harmless", shadow, e.getMessage()); } String message = "Object was not found on the " + ObjectTypeUtil.toShortString(shadow.getResource()) + ". Shadow deleted from the repository to equalize the state on the resource and in the repository."; parentResult.recordHandledError(message); LOGGER.trace("Shadow deleted from the repository. Inconsistencies are now removed."); result.computeStatus(); result.setStatus(OperationResultStatus.HANDLED_ERROR); // parentResult status can be recomputed by the caller method delta = ObjectDelta.createDeleteDelta(shadow.getClass(), shadow.getOid(), prismContext); ResourceOperationDescription operationDescritpion = createOperationDescription(shadow, ex, shadow.getResource(), delta, task, result); changeNotificationDispatcher.notifySuccess(operationDescritpion, task, result); LOGGER.debug("DISCOVERY: cannot find object {}: DELETE operation handler done", shadow); return shadow; case MODIFY: LOGGER.debug("DISCOVERY: cannot find object {}. The operation in progress is MODIFY, therefore initiating synchronization", shadow); LOGGER.trace("Starting discovery to find out if the object should exist or not."); OperationResult handleErrorResult = result.createSubresult("com.evolveum.midpoint.provisioning.consistency.impl.ObjectNotFoundHandler.handleError.discovery"); handleErrorResult.addContext(OperationResult.CONTEXT_RESOURCE, shadow.getResource()); ObjectDeltaType shadowModifications = shadow.getObjectChange(); Collection<? extends ItemDelta> modifications = DeltaConvertor.toModifications( shadowModifications.getItemDelta(), shadow.asPrismObject().getDefinition()); shadow.setDead(true); Collection<? extends ItemDelta> deadDeltas = PropertyDelta.createModificationReplacePropertyCollection(ShadowType.F_DEAD, shadow.asPrismObject().getDefinition(), true); ConstraintsChecker.onShadowModifyOperation(deadDeltas); cacheRepositoryService.modifyObject(ShadowType.class, shadow.getOid(), deadDeltas, result); ResourceObjectShadowChangeDescription change = createResourceObjectShadowChangeDescription(shadow, result); // notify model, that the expected account doesn't exist on the // resource..(the change form resource is therefore deleted) and let // the model to decide, if the account will be revived or unlinked // form the user // TODO: task initialication try { changeNotificationDispatcher.notifyChange(change, task, handleErrorResult); } catch (RuntimeException e) { handleErrorResult.recordFatalError(e); result.computeStatus(); throw e; } handleErrorResult.computeStatus(); String oidVal = null; String oid = findCreatedAccountOid(handleErrorResult, oidVal); if (oid != null){ LOGGER.trace("Found new oid {} as a return param from model. Probably the new shadow was created.", oid); LOGGER.debug("DISCOVERY: object {} re-created, applying pending changes", shadow); LOGGER.trace("Modifying re-created object by applying pending changes:\n{}", DebugUtil.debugDump(modifications)); try { ProvisioningOperationOptions options = new ProvisioningOperationOptions(); options.setCompletePostponed(false); options.setDoNotDiscovery(true); provisioningService.modifyObject(ShadowType.class, oid, modifications, null, options, task, result); parentResult.recordHandledError( "Object was recreated and modifications were applied to newly created object."); } catch (ObjectNotFoundException e) { parentResult.recordHandledError( "Modifications were not applied, because shadow was deleted by discovery. Repository state were refreshed and unused shadow was deleted."); } } else{ LOGGER.debug("DISCOVERY: object {} deleted, application of pending changes skipped", shadow); parentResult.recordHandledError( "Object was deleted by discovery. Modification were not applied."); } // We do not need the old shadow any more. Even if the object was re-created it has a new shadow now. try { cacheRepositoryService.deleteObject(ShadowType.class, shadow.getOid(), parentResult); } catch (ObjectNotFoundException e) { // delete the old shadow that was probably deleted from the // user, or the new one was assigned LOGGER.debug("Cannot delete {} in consistency compensation (discovery): {} - this is probably harmless", shadow, e.getMessage()); } result.computeStatus(); if (parentResult.isHandledError()) { // Ugly hack. We shouldn't set parentResult status in the first place, as it can be overriden by computeStatus/recomputeStatus called in the parent. result.setStatus(OperationResultStatus.HANDLED_ERROR); } if (oid != null){ shadowModifications.setOid(oid); shadow.setOid(oid); } LOGGER.debug("DISCOVERY: cannot find object {}: MODIFY operation handler done", shadow); return shadow; case GET: if (!compensate){ LOGGER.trace("DISCOVERY: cannot find object {}, GET operation: handling skipped because discovery is disabled", shadow); result.recordFatalError(ex.getMessage(), ex); throw new ObjectNotFoundException(ex.getMessage(), ex); } LOGGER.debug("DISCOVERY: cannot find object {}. The operation in progress is GET, therefore initiating synchronization", shadow); OperationResult handleGetErrorResult = result.createSubresult("com.evolveum.midpoint.provisioning.consistency.impl.ObjectNotFoundHandler.handleError.discovery"); handleGetErrorResult.addContext(OperationResult.CONTEXT_RESOURCE, shadow.getResource()); Collection<? extends ItemDelta> deadModification = PropertyDelta.createModificationReplacePropertyCollection(ShadowType.F_DEAD, shadow.asPrismObject().getDefinition(), true); ConstraintsChecker.onShadowModifyOperation(deadModification); try { cacheRepositoryService.modifyObject(ShadowType.class, shadow.getOid(), deadModification, result); } catch (ObjectNotFoundException e) { // The shadow is not there. So we cannot mark it as dead. LOGGER.debug("Cannot modify shadow {} in consistency compensation (discovery): {} - this is probably harmless", shadow, e.getMessage()); } shadow.setDead(true); ResourceObjectShadowChangeDescription getChange = createResourceObjectShadowChangeDescription(shadow, result); // notify model, that the expected account doesn't exist on the // resource..(the change form resource is therefore deleted) and let // the model to decide, if the account will be revived or unlinked // form the user // TODO: task initialication if (task == null){ task = taskManager.createTaskInstance(); } try { changeNotificationDispatcher.notifyChange(getChange, task, handleGetErrorResult); } catch (RuntimeException e) { LOGGER.trace("DISCOVERY: synchronization invoked for {} ended with an error {}", shadow, e); handleGetErrorResult.recordFatalError(e); result.computeStatus(); throw e; } // String oidVal = null; handleGetErrorResult.computeStatus(); LOGGER.trace("DISCOVERY: synchronization invoked for {} finished with status {}", shadow, handleGetErrorResult.getStatus()); oid = findCreatedAccountOid(handleGetErrorResult, null); try { LOGGER.trace("DISCOVERY: deleting {}", shadow); cacheRepositoryService.deleteObject(ShadowType.class, shadow.getOid(), result); } catch (ObjectNotFoundException e) { // delete the old shadow that was probably deleted from the // user, or the new one was assigned LOGGER.debug("Cannot delete {} in consistency compensation (discovery): {} - this is probably harmless", shadow, e.getMessage()); } for (OperationResult subResult : parentResult.getSubresults()){ subResult.muteError(); } if (oid != null) { PrismObject newShadow; try { LOGGER.trace("DISCOVERY: retrieving shadow {}", oid); Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(GetOperationOptions.createDoNotDiscovery()); newShadow = provisioningService.getObject(shadow.getClass(), oid, options, task, result); LOGGER.trace("DISCOVERY: retrieved {}", newShadow); } catch (ObjectNotFoundException e) { String msg = "Strange thing did happen: new shadow ("+oid +") was supposedly created for old shadow "+shadow+", however the new shadow was not found: " +e.getMessage(); LOGGER.error(msg); result.recordFatalError(msg, e); parentResult.recordFatalError(msg); throw new ObjectNotFoundException(msg, ex); } finally { result.computeStatus(); } LOGGER.debug("DISCOVERY: object {} re-created as {}. GET operation handler done: returning new shadow", shadow, newShadow); shadow = (T) newShadow.asObjectable(); parentResult.recordHandledError("Object was re-created by the discovery."); result.setStatus(OperationResultStatus.HANDLED_ERROR); // parentResult status can be recomputed by the caller method return shadow; } else { parentResult.recordHandledError("Object was deleted by the discovery and the invalid link was removed from the user."); result.computeStatus(); result.setStatus(OperationResultStatus.HANDLED_ERROR); // parentResult status can be recomputed by the caller method LOGGER.debug("DISCOVERY: object {} was deleted. GET operation handler done: throwing ObjectNotFoundException", shadow); throw new ObjectNotFoundException(ex.getMessage(), ex); } default: throw new ObjectNotFoundException(ex.getMessage(), ex); } } private ResourceObjectShadowChangeDescription createResourceObjectShadowChangeDescription( ShadowType shadow, OperationResult result) { ResourceObjectShadowChangeDescription change = new ResourceObjectShadowChangeDescription(); ObjectDelta<ShadowType> objectDelta = new ObjectDelta<ShadowType>(ShadowType.class, ChangeType.DELETE, shadow.asPrismObject().getPrismContext()); objectDelta.setOid(shadow.getOid()); change.setObjectDelta(objectDelta); change.setResource(shadow.getResource().asPrismObject()); ShadowType account = (ShadowType) shadow; change.setOldShadow(account.asPrismObject()); change.setSourceChannel(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_DISCOVERY)); return change; } private String findCreatedAccountOid(OperationResult handleErrorResult, String oidVal) { if (oidVal != null) { return oidVal; } List<OperationResult> subresults = handleErrorResult.getSubresults(); for (OperationResult subresult : subresults) { String oidValue = (String) subresult.getReturn("createdAccountOid"); String oid = findCreatedAccountOid(subresult, oidValue); if (oid != null) { return oid; } } return null; } }