/*
* Copyright (c) 2010-2014 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.importer;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl;
import com.evolveum.midpoint.prism.PrismPropertyDefinitionImpl;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.model.impl.ModelConstants;
import com.evolveum.midpoint.model.impl.sync.SynchronizeAccountResultHandler;
import com.evolveum.midpoint.model.impl.util.AbstractSearchIterativeTaskHandler;
import com.evolveum.midpoint.model.impl.util.Utils;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.provisioning.api.ResourceObjectChangeListener;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition;
import com.evolveum.midpoint.schema.result.OperationConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskCategory;
import com.evolveum.midpoint.task.api.TaskHandler;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.task.api.TaskRunResult;
import com.evolveum.midpoint.task.api.TaskRunResult.TaskRunResultStatus;
import com.evolveum.midpoint.util.DOMUtil;
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.LayerType;
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;
/**
* Task handler for "Import from resource" task.
* <p/>
* The import task will search for all the accounts on a specific resource. It will
* pretend that all the accounts were just created and notify other components (mode)
* using the ResourceObjectChangeListener interface. This will efficiently result in
* importing all the accounts. Depending on the sync policy, appropriate user objects
* may be created, accounts may be linked to existing users, etc.
* <p/>
* The handler will execute the import in background. It is using Task Manager
* for that purpose, so the Task Manager instance needs to be injected. Most of the "import"
* action is actually done in the callbacks from provisioning searchObjectsIterative() operation.
* <p/>
* The import task may be executed on a different node (as usual for async tasks).
*
* @author Radovan Semancik
* @see TaskHandler
* @see ResourceObjectChangeListener
*/
@Component
public class ImportAccountsFromResourceTaskHandler extends AbstractSearchIterativeTaskHandler<ShadowType, SynchronizeAccountResultHandler> {
public static final String HANDLER_URI = ModelConstants.NS_SYNCHRONIZATION_TASK_PREFIX + "/import/handler-3";
// WARNING! This task handler is efficiently singleton!
// It is a spring bean and it is supposed to handle all search task instances
// Therefore it must not have task-specific fields. It can only contain fields specific to
// all tasks of a specified type
@Autowired(required = true)
private TaskManager taskManager;
@Autowired(required = true)
private ProvisioningService provisioningService;
@Autowired(required = true)
private ChangeNotificationDispatcher changeNotificationDispatcher;
private PrismPropertyDefinition<QName> objectclassPropertyDefinition;
private static final Trace LOGGER = TraceManager.getTrace(ImportAccountsFromResourceTaskHandler.class);
public ImportAccountsFromResourceTaskHandler() {
super("Import from resource", OperationConstants.IMPORT_ACCOUNTS_FROM_RESOURCE);
setLogFinishInfo(true);
setPreserveStatistics(false);
setEnableSynchronizationStatistics(true);
}
@PostConstruct
private void initialize() {
// this call must not be in the constructor, because prismContext is not yet initialized at that moment
objectclassPropertyDefinition = new PrismPropertyDefinitionImpl<>(ModelConstants.OBJECTCLASS_PROPERTY_NAME,
DOMUtil.XSD_QNAME, prismContext);
taskManager.registerHandler(HANDLER_URI, this);
}
/**
* Launch an import. Calling this method will start import in a new
* thread, possibly on a different node.
*/
public void launch(ResourceType resource, QName objectclass, Task task, OperationResult parentResult) {
LOGGER.info("Launching import from resource {} as asynchronous task", ObjectTypeUtil.toShortString(resource));
OperationResult result = parentResult.createSubresult(ImportAccountsFromResourceTaskHandler.class.getName() + ".launch");
result.addParam("resource", resource);
result.addParam("objectclass", objectclass);
// TODO
// Set handler URI so we will be called back
task.setHandlerUri(HANDLER_URI);
// Readable task name
PolyStringType polyString = new PolyStringType("Import from resource " + resource.getName());
task.setName(polyString);
// Set reference to the resource
task.setObjectRef(ObjectTypeUtil.createObjectRef(resource));
try {
PrismProperty<?> objectclassProp = objectclassPropertyDefinition.instantiate();
objectclassProp.setRealValue(objectclass);
task.setExtensionProperty(objectclassProp);
task.savePendingModifications(result); // just to be sure (if the task was already persistent)
// task.modify(modifications, result);
} catch (ObjectNotFoundException e) {
LOGGER.error("Task object not found, expecting it to exist (task {})", task, e);
result.recordFatalError("Task object not found", e);
throw new IllegalStateException("Task object not found, expecting it to exist", e);
} catch (ObjectAlreadyExistsException e) {
LOGGER.error("Task object wasn't updated (task {})", task, e);
result.recordFatalError("Task object wasn't updated", e);
throw new IllegalStateException("Task object wasn't updated", e);
} catch (SchemaException e) {
LOGGER.error("Error dealing with schema (task {})", task, e);
result.recordFatalError("Error dealing with schema", e);
throw new IllegalStateException("Error dealing with schema", e);
}
// Switch task to background. This will start new thread and call
// the run(task) method.
// Note: the thread may be actually started on a different node
taskManager.switchToBackground(task, result);
result.setBackgroundTaskOid(task.getOid());
result.computeStatus("Import launch failed");
LOGGER.trace("Import from resource {} switched to background, control thread returning with task {}", ObjectTypeUtil.toShortString(resource), task);
}
@Override
protected SynchronizeAccountResultHandler createHandler(TaskRunResult runResult, Task coordinatorTask,
OperationResult opResult) {
ResourceType resource = resolveObjectRef(ResourceType.class, runResult, coordinatorTask, opResult);
if (resource == null) {
return null;
}
return createHandler(resource, null, runResult, coordinatorTask, opResult);
}
// shadowToImport - it is used to derive objectClass/intent/kind when importing a single shadow
private SynchronizeAccountResultHandler createHandler(ResourceType resource, PrismObject<ShadowType> shadowToImport,
TaskRunResult runResult, Task coordinatorTask, OperationResult opResult) {
RefinedResourceSchema refinedSchema;
ObjectClassComplexTypeDefinition objectClass;
try {
refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, prismContext);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Refined schema:\n{}", refinedSchema.debugDump());
}
if (shadowToImport != null) {
objectClass = Utils.determineObjectClass(refinedSchema, shadowToImport);
} else {
objectClass = Utils.determineObjectClass(refinedSchema, coordinatorTask);
}
if (objectClass == null) {
LOGGER.error("Import: No objectclass specified and no default can be determined.");
opResult.recordFatalError("No objectclass specified and no default can be determined");
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
return null;
}
} catch (SchemaException e) {
LOGGER.error("Import: Schema error during processing account definition: {}",e.getMessage());
opResult.recordFatalError("Schema error during processing account definition: "+e.getMessage(),e);
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
return null;
}
LOGGER.info("Start executing import from resource {}, importing object class {}", resource, objectClass.getTypeName());
SynchronizeAccountResultHandler handler = new SynchronizeAccountResultHandler(resource, objectClass, "import",
coordinatorTask, changeNotificationDispatcher, taskManager);
handler.setSourceChannel(SchemaConstants.CHANGE_CHANNEL_IMPORT);
handler.setForceAdd(true);
handler.setStopOnError(false);
handler.setContextDesc("from "+resource);
handler.setLogObjectProgress(true);
return handler;
}
@Override
protected boolean initializeRun(SynchronizeAccountResultHandler handler,
TaskRunResult runResult, Task task, OperationResult opResult) {
return super.initializeRun(handler, runResult, task, opResult);
}
@Override
protected Class<? extends ObjectType> getType(Task task) {
return ShadowType.class;
}
@Override
protected ObjectQuery createQuery(SynchronizeAccountResultHandler handler, TaskRunResult runResult, Task task, OperationResult opResult) {
try {
return ObjectQueryUtil.createResourceAndObjectClassQuery(handler.getResourceOid(),
handler.getObjectClass().getTypeName(), prismContext);
} catch (SchemaException e) {
LOGGER.error("Import: Schema error during creating search query: {}",e.getMessage());
opResult.recordFatalError("Schema error during creating search query: "+e.getMessage(),e);
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
return null;
}
}
@Override
public String getCategoryName(Task task) {
return TaskCategory.IMPORTING_ACCOUNTS;
}
@Override
public List<String> getCategoryNames() {
return null;
}
/**
* Imports a single shadow. Synchronously. The task is NOT switched to background by default.
*/
public boolean importSingleShadow(String shadowOid, Task task, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
PrismObject<ShadowType> shadow = provisioningService.getObject(ShadowType.class, shadowOid, null, task, parentResult);
PrismObject<ResourceType> resource = provisioningService.getObject(ResourceType.class, ShadowUtil.getResourceOid(shadow), null, task, parentResult);
// Create a result handler just for one object. Invoke the handle() method manually.
TaskRunResult runResult = new TaskRunResult();
SynchronizeAccountResultHandler resultHandler = createHandler(resource.asObjectable(), shadow, runResult, task, parentResult);
if (resultHandler == null) {
return false;
}
// This is required for proper error reporting
resultHandler.setStopOnError(true);
boolean cont = initializeRun(resultHandler, runResult, task, parentResult);
if (!cont) {
return false;
}
cont = resultHandler.handle(shadow, parentResult);
if (!cont) {
return false;
}
finish(resultHandler, runResult, task, parentResult);
return true;
}
}