/* * Copyright (c) 2010-2013 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.model.impl.sync; import javax.xml.namespace.QName; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.impl.importer.ImportAccountsFromResourceTaskHandler; import com.evolveum.midpoint.model.impl.util.AbstractSearchIterativeResultHandler; import com.evolveum.midpoint.model.impl.util.Utils; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ChangeType; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener; import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * Iterative search result handler for account synchronization. Works both for * reconciliation and import from resource. * * This class is called back from the searchObjectsIterative() operation of the * provisioning service. It does most of the work of the "import" and resource * reconciliation operations. * * @see ImportAccountsFromResourceTaskHandler * @see ReconciliationTaskHandler * * @author Radovan Semancik * */ public class SynchronizeAccountResultHandler extends AbstractSearchIterativeResultHandler<ShadowType> { private static final Trace LOGGER = TraceManager.getTrace(SynchronizeAccountResultHandler.class); private ResourceObjectChangeListener objectChangeListener; private String resourceOid; private ThreadLocal<ResourceType> resourceWorkingCopy = new ThreadLocal<>(); // because PrismContainer is not thread safe even for reading, each thread must have its own copy private ResourceType resourceReadOnly; // this is a "master copy", not to be touched by getters - its content is copied into resourceWorkingCopy content when needed private ObjectClassComplexTypeDefinition objectClass; private QName sourceChannel; private boolean forceAdd; public SynchronizeAccountResultHandler(ResourceType resource, ObjectClassComplexTypeDefinition objectClass, String processShortName, Task coordinatorTask, ResourceObjectChangeListener objectChangeListener, TaskManager taskManager) { super(coordinatorTask, SynchronizeAccountResultHandler.class.getName(), processShortName, "from "+resource, taskManager); this.objectChangeListener = objectChangeListener; this.resourceReadOnly = resource; this.resourceOid = resource.getOid(); this.objectClass = objectClass; forceAdd = false; setRecordIterationStatistics(false); // we do statistics ourselves in handler, because in case of reconciliation // we are not called via AbstractSearchIterativeResultHandler.processRequest } public boolean isForceAdd() { return forceAdd; } public void setForceAdd(boolean forceAdd) { this.forceAdd = forceAdd; } public QName getSourceChannel() { return sourceChannel; } public void setSourceChannel(QName sourceChannel) { this.sourceChannel = sourceChannel; } public String getResourceOid() { return resourceOid; } public ResourceType getResourceWorkingCopy() { ResourceType retval = resourceWorkingCopy.get(); if (retval == null) { retval = resourceReadOnly.clone(); resourceWorkingCopy.set(retval); } return retval; } public ObjectClassComplexTypeDefinition getObjectClass() { return objectClass; } /* * This methods will be called for each search result. It means it will be * called for each account on a resource. We will pretend that the account * was created and invoke notification interface. * * @see * com.evolveum.midpoint.provisioning.api.ResultHandler#handle(com.evolveum * .midpoint.xml.ns._public.common.common_1.ObjectType) */ @Override protected boolean handleObject(PrismObject<ShadowType> accountShadow, Task workerTask, OperationResult result) { long started = System.currentTimeMillis(); try { workerTask.recordIterativeOperationStart(accountShadow.asObjectable()); boolean rv = handleObjectInternal(accountShadow, workerTask, result); result.computeStatusIfUnknown(); if (result.isError()) { workerTask.recordIterativeOperationEnd(accountShadow.asObjectable(), started, getException(result)); } else { workerTask.recordIterativeOperationEnd(accountShadow.asObjectable(), started, null); } return rv; } catch (Throwable t) { workerTask.recordIterativeOperationEnd(accountShadow.asObjectable(), started, t); throw t; } } protected boolean handleObjectInternal(PrismObject<ShadowType> accountShadow, Task workerTask, OperationResult result) { ShadowType newShadowType = accountShadow.asObjectable(); if (newShadowType.isProtectedObject() != null && newShadowType.isProtectedObject()) { LOGGER.trace("{} skipping {} because it is protected", new Object[]{ getProcessShortNameCapitalized(), accountShadow}); result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Skipped because it is protected"); return true; } if (objectClass != null && (objectClass instanceof RefinedObjectClassDefinition) && !((RefinedObjectClassDefinition) objectClass).matches(newShadowType)) { LOGGER.trace("{} skipping {} because it does not match objectClass/kind/intent", new Object[]{ getProcessShortNameCapitalized(), accountShadow}); result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Skipped because it does not match objectClass/kind/intent"); return true; } if (objectChangeListener == null) { LOGGER.warn("No object change listener set for {} task, ending the task", getProcessShortName()); result.recordFatalError("No object change listener set for " + getProcessShortName() + " task, ending the task"); return false; } // We are going to pretend that all of the objects were just created. // That will efficiently import them to the IDM repository ResourceObjectShadowChangeDescription change = new ResourceObjectShadowChangeDescription(); change.setSourceChannel(QNameUtil.qNameToUri(sourceChannel)); change.setResource(getResourceWorkingCopy().asPrismObject()); if (forceAdd) { // We should provide shadow in the state before the change. But we are // pretending that it has // not existed before, so we will not provide it. ObjectDelta<ShadowType> shadowDelta = new ObjectDelta<ShadowType>( ShadowType.class, ChangeType.ADD, accountShadow.getPrismContext()); //PrismObject<AccountShadowType> shadowToAdd = refinedAccountDefinition.getObjectDefinition().parseObjectType(newShadowType); PrismObject<ShadowType> shadowToAdd = newShadowType.asPrismObject(); shadowDelta.setObjectToAdd(shadowToAdd); shadowDelta.setOid(newShadowType.getOid()); change.setObjectDelta(shadowDelta); // Need to also set current shadow. This will get reflected in "old" object in lens context change.setCurrentShadow(accountShadow); } else { // No change, therefore the delta stays null. But we will set the current change.setCurrentShadow(accountShadow); } try { change.checkConsistence(); } catch (RuntimeException ex) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Check consistence failed: {}\nChange:\n{}", ex, change.debugDump()); } throw ex; } // Invoke the change notification Utils.clearRequestee(workerTask); objectChangeListener.notifyChange(change, workerTask, result); // No exception thrown here. The error is indicated in the result. Will be processed by superclass. return workerTask.canRun(); } }