/*
* Copyright (c) 2015 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.util;
import java.util.Collection;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.model.api.ModelPublicConstants;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SearchResultList;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.statistics.StatisticsUtil;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.prism.xml.ns._public.query_3.QueryType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.query.ObjectPaging;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.QueryJaxbConvertor;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
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.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.PolicyViolationException;
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.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
/**
* @author semancik
*
*/
@Component
public class DeleteTaskHandler implements TaskHandler {
public static final String HANDLER_URI = ModelPublicConstants.DELETE_TASK_HANDLER_URI;
public static final long PROGRESS_UPDATE_INTERVAL = 3000L;
@Autowired(required=true)
protected TaskManager taskManager;
@Autowired(required=true)
protected ModelService modelService;
@Autowired(required = true)
protected PrismContext prismContext;
private static final transient Trace LOGGER = TraceManager.getTrace(DeleteTaskHandler.class);
@PostConstruct
private void initialize() {
taskManager.registerHandler(HANDLER_URI, this);
}
@Override
public TaskRunResult run(Task task) {
try {
task.startCollectingOperationStatsFromZero(true, false, true);
return runInternal(task);
} finally {
updateState(task);
}
}
public <O extends ObjectType> TaskRunResult runInternal(Task task) {
LOGGER.trace("Delete task run starting ({})", task);
long startTimestamp = System.currentTimeMillis();
OperationResult opResult = new OperationResult("DeleteTask.run");
opResult.setStatus(OperationResultStatus.IN_PROGRESS);
TaskRunResult runResult = new TaskRunResult();
runResult.setOperationResult(opResult);
opResult.setSummarizeErrors(true);
opResult.setSummarizePartialErrors(true);
opResult.setSummarizeSuccesses(true);
QueryType queryType;
PrismProperty<QueryType> objectQueryPrismProperty = task.getExtensionProperty(SchemaConstants.MODEL_EXTENSION_OBJECT_QUERY);
if (objectQueryPrismProperty != null && objectQueryPrismProperty.getRealValue() != null) {
queryType = objectQueryPrismProperty.getRealValue();
} else {
// For "foolproofness" reasons we really require a query. Even if it is "ALL" query.
LOGGER.error("No query parameter in {}", task);
opResult.recordFatalError("No query parameter in " + task);
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
return runResult;
}
Class<O> objectType;
QName objectTypeName;
PrismProperty<QName> objectTypePrismProperty = task.getExtensionProperty(SchemaConstants.MODEL_EXTENSION_OBJECT_TYPE);
if (objectTypePrismProperty != null && objectTypePrismProperty.getRealValue() != null) {
objectTypeName = objectTypePrismProperty.getRealValue();
objectType = (Class<O>) ObjectTypes.getObjectTypeFromTypeQName(objectTypeName).getClassDefinition();
} else {
LOGGER.error("No object type parameter in {}", task);
opResult.recordFatalError("No object type parameter in " + task);
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
return runResult;
}
ObjectQuery query;
try {
query = QueryJaxbConvertor.createObjectQuery(objectType, queryType, prismContext);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Using object query from the task: {}", query.debugDump());
}
} catch (SchemaException ex) {
LOGGER.error("Schema error while creating a search filter: {}", new Object[]{ex.getMessage(), ex});
opResult.recordFatalError("Schema error while creating a search filter: " + ex.getMessage(), ex);
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
return runResult;
}
boolean optionRaw = true;
PrismProperty<Boolean> optionRawPrismProperty = task.getExtensionProperty(SchemaConstants.MODEL_EXTENSION_OPTION_RAW);
if (optionRawPrismProperty != null && optionRawPrismProperty.getRealValue() != null && !optionRawPrismProperty.getRealValue()) {
optionRaw = false;
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Deleting {}, raw={} using query:\n{}",
new Object[]{objectType.getSimpleName(), optionRaw, query.debugDump()});
}
boolean countObjectsOnStart = true; // TODO
long progress = 0;
Integer maxSize = 100;
ObjectPaging paging = ObjectPaging.createPaging(0, maxSize);
query.setPaging(paging);
query.setAllowPartialResults(true);
Collection<SelectorOptions<GetOperationOptions>> searchOptions = null;
ModelExecuteOptions execOptions = null;
if (optionRaw) {
searchOptions = SelectorOptions.createCollection(GetOperationOptions.createRaw());
execOptions = ModelExecuteOptions.createRaw();
}
try {
// counting objects can be within try-catch block, because the handling is similar to handling errors within searchIterative
Long expectedTotal = null;
if (countObjectsOnStart) {
Integer expectedTotalInt = modelService.countObjects(objectType, query, searchOptions, task, opResult);
LOGGER.trace("Expecting {} objects to be deleted", expectedTotal);
if (expectedTotalInt != null) {
expectedTotal = (long) expectedTotalInt; // conversion would fail on null
}
}
runResult.setProgress(progress);
task.setProgress(progress);
if (expectedTotal != null) {
task.setExpectedTotal(expectedTotal);
}
try {
task.savePendingModifications(opResult);
} catch (ObjectAlreadyExistsException e) { // other exceptions are handled in the outer try block
throw new IllegalStateException("Unexpected ObjectAlreadyExistsException when updating task progress/expectedTotal", e);
}
long progressLastUpdated = 0;
SearchResultList<PrismObject<O>> objects;
while (true) {
objects = modelService.searchObjects(objectType, query, searchOptions, task, opResult);
if (objects.isEmpty()) {
break;
}
int skipped = 0;
for(PrismObject<O> object: objects) {
if (!optionRaw && ShadowType.class.isAssignableFrom(objectType)
&& Boolean.TRUE == ((ShadowType)(object.asObjectable())).isProtectedObject()) {
LOGGER.debug("Skipping delete of protected object {}", object);
skipped++;
continue;
}
ObjectDelta<?> delta = ObjectDelta.createDeleteDelta(objectType, object.getOid(), prismContext);
String objectName = PolyString.getOrig(object.getName());
String objectDisplayName = StatisticsUtil.getDisplayName(object);
String objectOid = object.getOid();
task.recordIterativeOperationStart(objectName, objectDisplayName, objectTypeName, objectOid);
long objectDeletionStarted = System.currentTimeMillis();
try {
modelService.executeChanges(MiscSchemaUtil.createCollection(delta), execOptions, task, opResult);
task.recordIterativeOperationEnd(objectName, objectDisplayName, objectTypeName, objectOid, objectDeletionStarted, null);
} catch (Throwable t) {
task.recordIterativeOperationEnd(objectName, objectDisplayName, objectTypeName, objectOid, objectDeletionStarted, t);
throw t; // TODO we don't want to continue processing if an error occurs?
}
progress++;
task.setProgressTransient(progress);
if (System.currentTimeMillis() - progressLastUpdated > PROGRESS_UPDATE_INTERVAL) {
task.setProgress(progress);
updateState(task);
progressLastUpdated = System.currentTimeMillis();
}
}
opResult.summarize();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Search returned {} objects, {} skipped, progress: {}, result:\n{}",
new Object[]{objects.size(), skipped, progress, opResult.debugDump()});
}
if (objects.size() == skipped) {
break;
}
}
} catch (ObjectAlreadyExistsException | ObjectNotFoundException | SchemaException
| ExpressionEvaluationException | ConfigurationException
| PolicyViolationException | SecurityViolationException e) {
LOGGER.error("{}", new Object[]{e.getMessage(), e});
opResult.recordFatalError("Object not found " + e.getMessage(), e);
runResult.setRunResultStatus(TaskRunResultStatus.PERMANENT_ERROR);
runResult.setProgress(progress);
return runResult;
} catch (CommunicationException e) {
LOGGER.error("{}", new Object[]{e.getMessage(), e});
opResult.recordFatalError("Object not found " + e.getMessage(), e);
runResult.setRunResultStatus(TaskRunResultStatus.TEMPORARY_ERROR);
runResult.setProgress(progress);
return runResult;
}
runResult.setProgress(progress);
runResult.setRunResultStatus(TaskRunResultStatus.FINISHED);
opResult.summarize();
opResult.recordSuccess();
long wallTime = System.currentTimeMillis() - startTimestamp;
String finishMessage = "Finished delete (" + task + "). ";
String statistics = "Processed " + progress + " objects in " + wallTime/1000 + " seconds.";
if (progress > 0) {
statistics += " Wall clock time average: " + ((float) wallTime / (float) progress) + " milliseconds";
}
opResult.createSubresult(DeleteTaskHandler.class.getName() + ".statistics").recordStatus(OperationResultStatus.SUCCESS, statistics);
LOGGER.info(finishMessage + statistics);
LOGGER.trace("Run finished (task {}, run result {})", new Object[]{task, runResult});
return runResult;
}
@Override
public Long heartbeat(Task task) {
return task.getProgress();
}
@Override
public void refreshStatus(Task task) {
// Local task. No refresh needed. The Task instance has always fresh data.
}
@Override
public String getCategoryName(Task task) {
return TaskCategory.UTIL;
}
@Override
public List<String> getCategoryNames() {
return null;
}
private void updateState(Task task) {
task.storeOperationStats();
// includes savePendingModifications - this is necessary for the progress to be immediately available in GUI
}
}