/* * 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.ArrayList; import java.util.Collection; import com.evolveum.midpoint.provisioning.impl.ConstraintsChecker; import com.evolveum.midpoint.provisioning.impl.ResourceManager; import com.evolveum.midpoint.schema.util.ShadowUtil; import org.apache.commons.lang.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.provisioning.api.ResourceOperationDescription; import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler; 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.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; 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.ObjectAlreadyExistsException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; 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.xml.ns._public.common.common_3.AvailabilityStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FailedOperationTypeType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; @Component public class CommunicationExceptionHandler extends ErrorHandler { @Autowired @Qualifier("cacheRepositoryService") private RepositoryService cacheRepositoryService; @Autowired private ResourceManager resourceManager; public CommunicationExceptionHandler() { cacheRepositoryService = null; } private static final Trace LOGGER = TraceManager.getTrace(CommunicationExceptionHandler.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 { if (!doDiscovery) { parentResult.recordFatalError(ex); if (ex instanceof CommunicationException) { throw (CommunicationException)ex; } else { throw new CommunicationException(ex.getMessage(), ex); } } Validate.notNull(shadow, "Shadow must not be null."); OperationResult operationResult = parentResult.createSubresult("com.evolveum.midpoint.provisioning.consistency.impl.CommunicationExceptionHandler.handleError." + op.name()); operationResult.addParam("shadow", shadow); operationResult.addParam("currentOperation", op); operationResult.addParam("exception", ex.getMessage()); // first modify last availability status in the resource, so by others // operations, we can know that it is down resourceManager.modifyResourceAvailabilityStatus(shadow.getResource().asPrismObject(), AvailabilityStatusType.DOWN, operationResult); if ((!isPostpone(shadow.getResource()) || !compensate) && !FailedOperation.GET.equals(op)){ LOGGER.trace("Postponing operation turned off."); operationResult.recordFatalError(ex.getMessage(), ex); throw new CommunicationException(ex.getMessage(), ex); } // Task task = null; ObjectDelta delta = null; ResourceOperationDescription operationDescription = null; switch (op) { case ADD: // if it is first time, just store the whole account to the repo LOGGER.trace("Postponing ADD operation for {}", ObjectTypeUtil.toShortString(shadow)); ResourceType resource = shadow.getResource(); if (shadow.getFailedOperationType() == null) { // ResourceType resource = shadow.getResource(); if (shadow.getName() == null) { shadow.setName(new PolyStringType(ShadowUtil.determineShadowName(shadow.asPrismObject()))); } if (shadow.getResourceRef() == null || shadow.getResourceRef().getOid() == null){ if (resource != null){ shadow.getResourceRef().setOid(shadow.getResource().getOid()); } } if (shadow.getResourceRef() != null && resource != null){ shadow.setResource(null); } shadow.setAttemptNumber(getAttemptNumber(shadow)); shadow.setFailedOperationType(FailedOperationTypeType.ADD); ConstraintsChecker.onShadowAddOperation(shadow); // Unlike addObject calls during normal provisioning, here we preserve all activation information, including e.g. administrativeStatus. // It is needed for shadow creation during error recovery. String oid = cacheRepositoryService.addObject(shadow.asPrismObject(), null, operationResult); shadow.setOid(oid); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Stored new shadow for unfinished operation:\n{}", shadow.asPrismObject().debugDump(1)); } // if it is seccond time ,just increade the attempt number } else { if (FailedOperationTypeType.ADD == shadow.getFailedOperationType()) { Collection<? extends ItemDelta> attemptdelta = createAttemptModification(shadow, null); ConstraintsChecker.onShadowModifyOperation(attemptdelta); cacheRepositoryService.modifyObject(ShadowType.class, shadow.getOid(), attemptdelta, operationResult); } } // if the shadow was successfully stored in the repo, just mute the // error for (OperationResult subRes : parentResult.getSubresults()) { subRes.muteError(); } operationResult.computeStatus(); parentResult .recordHandledError("Could not create object=" +shadow.getName().getOrig()+" on the resource, because " + ObjectTypeUtil.toShortString(resource) + " is unreachable at the moment. Shadow is stored in the repository and the resource object will be created when the resource goes online"); // there will be something like ": Add object failed" appended, so the final dot was a bit ugly here // task = taskManager.createTaskInstance(); delta = ObjectDelta.createAddDelta(shadow.asPrismObject()); operationDescription = createOperationDescription(shadow, ex, resource, delta, task, operationResult); changeNotificationDispatcher.notifyInProgress(operationDescription, task, parentResult); return shadow; case MODIFY: if (shadow.getFailedOperationType() == null || shadow.getFailedOperationType() == FailedOperationTypeType.MODIFY) { shadow.setFailedOperationType(FailedOperationTypeType.MODIFY); Collection<ItemDelta> modifications = createShadowModification(shadow); ConstraintsChecker.onShadowModifyOperation(modifications); getCacheRepositoryService().modifyObject(ShadowType.class, shadow.getOid(), modifications, operationResult); delta = ObjectDelta.createModifyDelta(shadow.getOid(), modifications, shadow.asPrismObject().getCompileTimeClass(), prismContext); // operationResult.recordSuccess(); // return shadow; } else { if (FailedOperationTypeType.ADD == shadow.getFailedOperationType()) { if (shadow.getObjectChange() != null && shadow.getOid() != null) { Collection<? extends ItemDelta> deltas = DeltaConvertor.toModifications(shadow .getObjectChange().getItemDelta(), shadow.asPrismObject().getDefinition()); ConstraintsChecker.onShadowModifyOperation(deltas); cacheRepositoryService.modifyObject(ShadowType.class, shadow.getOid(), deltas, operationResult); delta = ObjectDelta.createModifyDelta(shadow.getOid(), deltas, shadow.asPrismObject().getCompileTimeClass(), prismContext); // return shadow; // operationResult.recordSuccess(); } } } for (OperationResult subRes : parentResult.getSubresults()) { subRes.muteError(); } operationResult.computeStatus(); parentResult .recordHandledError("Could not apply modifications to "+ObjectTypeUtil.toShortString(shadow)+" on the " + ObjectTypeUtil.toShortString(shadow.getResource()) + ", because resource is unreachable. Modifications will be applied when the resource goes online"); // task = taskManager.createTaskInstance(); // operationDescription = createOperationDescription(shadow, ex, shadow.getResource(), delta, task, operationResult); changeNotificationDispatcher.notifyInProgress(operationDescription, task, parentResult); return shadow; case DELETE: shadow.setFailedOperationType(FailedOperationTypeType.DELETE); Collection<ItemDelta> modifications = createShadowModification(shadow); ConstraintsChecker.onShadowModifyOperation(modifications); getCacheRepositoryService().modifyObject(ShadowType.class, shadow.getOid(), modifications, operationResult); for (OperationResult subRes : parentResult.getSubresults()) { subRes.muteError(); } parentResult .recordHandledError("Could not delete " +ObjectTypeUtil.getShortTypeName(shadow)+ " from the resource " + ObjectTypeUtil.toShortString(shadow.getResource()) + ", because resource is unreachable. Resource object will be delete when the resource goes online"); // operationResult.recordSuccess(); operationResult.computeStatus(); // task = taskManager.createTaskInstance(); // task.setChannel(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_DISCOVERY)); delta = ObjectDelta.createDeleteDelta(shadow.asPrismObject().getCompileTimeClass(), shadow.getOid(), prismContext); operationDescription = createOperationDescription(shadow, ex, shadow.getResource(), delta, task, operationResult); changeNotificationDispatcher.notifyInProgress(operationDescription, task, parentResult); return shadow; case GET: // nothing to do, just return the shadow from the repo and set fetch // result.. for (OperationResult subRes : parentResult.getSubresults()) { subRes.muteError(); } operationResult.recordPartialError("Could not get "+ObjectTypeUtil.toShortString(shadow)+" from the resource " + ObjectTypeUtil.toShortString(shadow.getResource()) + ", because resource is unreachable. Returning shadow from the repository"); shadow.setFetchResult(operationResult.createOperationResultType()); // operationResult.recordSuccess(); // operationResult.computeStatus(); return shadow; default: throw new CommunicationException(ex); } } private <T extends ShadowType> Collection<ItemDelta> createShadowModification(T shadow) throws ObjectNotFoundException, SchemaException { Collection<ItemDelta> modifications = new ArrayList<ItemDelta>(); PropertyDelta propertyDelta = PropertyDelta.createReplaceDelta(shadow.asPrismObject() .getDefinition(), ShadowType.F_RESULT, shadow.getResult()); modifications.add(propertyDelta); propertyDelta = PropertyDelta.createReplaceDelta(shadow.asPrismObject().getDefinition(), ShadowType.F_FAILED_OPERATION_TYPE, shadow.getFailedOperationType()); modifications.add(propertyDelta); if (shadow.getObjectChange() != null) { propertyDelta = PropertyDelta.createReplaceDelta(shadow.asPrismObject().getDefinition(), ShadowType.F_OBJECT_CHANGE, shadow.getObjectChange()); modifications.add(propertyDelta); } modifications = createAttemptModification(shadow, modifications); return modifications; } // private Integer getAttemptNumber(ResourceObjectShadowType shadow) { // Integer attemptNumber = (shadow.getAttemptNumber() == null ? 0 : shadow.getAttemptNumber()+1); // return attemptNumber; // } }