Skip to content

Commit

Permalink
Report tasks are created through clockwork
Browse files Browse the repository at this point in the history
When report tasks were created in GUI, they were saved in "raw" mode.
The major limitation was that the mappings defined in report archetypes
were not executed. After this commit, full clockwork processing is done,
so the tasks can be customized by specifying appropriate mappings
in the archetypes.

Also, the default names of tasks are more descriptive now, like
"Export task for Users in MidPoint (2023-09-27 23:08:34)".

This resolves MID-8364.
  • Loading branch information
mederly committed Sep 27, 2023
1 parent dfd81ae commit 5766756
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 75 deletions.
Expand Up @@ -10,6 +10,7 @@

import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

import org.jetbrains.annotations.VisibleForTesting;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -54,6 +55,8 @@ public class ReconciliationLauncher {
* Launch an import. Calling this method will start import in a new
* thread, possibly on a different node.
*/
@VisibleForTesting
@Deprecated
public void launch(ResourceType resource, QName objectclass, Task task, OperationResult parentResult) {

LOGGER.info("Launching reconciliation for resource {} as asynchronous task", ObjectTypeUtil.toShortString(resource));
Expand Down
Expand Up @@ -7,6 +7,7 @@

package com.evolveum.midpoint.report.api;

import com.evolveum.midpoint.model.api.ModelAuthorizationAction;
import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismObject;
Expand All @@ -25,25 +26,25 @@
* @author katkav
*/
public interface ReportManager {

/**
* todo comments [lazyman]
* Creates and submits a simple (classic export) task that will execute the "export" report.
* Requires {@link ModelAuthorizationAction#RUN_REPORT} authorization related to given report.
*
* @param report
* @param parentResult describes report which has to be created
* @param report The report object; it must reside in repository. Actually, only its OID is used from the parameter.
*/
void runReport(PrismObject<ReportType> report, PrismContainer<ReportParameterType> params, Task task, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException,
SecurityViolationException;
void runReport(PrismObject<ReportType> report, PrismContainer<ReportParameterType> params, Task task, OperationResult result)
throws CommonException;

/**
* todo comments [lazyman]
* Creates and submits a task that will execute the "import" report.
* Requires {@link ModelAuthorizationAction#IMPORT_REPORT} authorization related to given report.
*
* @param report
* @param parentResult describes report which has to be created
* @param report The report object; it must reside in repository. Actually, only its OID is used from the parameter.
* @param reportData Data to be imported. It must reside in repository. Actually, only its OID is used from the parameter.
*/
void importReport(PrismObject<ReportType> report, PrismObject<ReportDataType> reportData, Task task, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException,
SecurityViolationException;
void importReport(PrismObject<ReportType> report, PrismObject<ReportDataType> reportData, Task task, OperationResult result)
throws CommonException;

/**
* todo comments [lazyman]
Expand Down
Expand Up @@ -7,9 +7,7 @@

package com.evolveum.midpoint.report.impl;

import com.evolveum.midpoint.model.api.ModelAuthorizationAction;
import com.evolveum.midpoint.model.api.ModelPublicConstants;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.model.api.*;
import com.evolveum.midpoint.model.api.authentication.CompiledObjectCollectionView;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.ItemDelta;
Expand All @@ -25,7 +23,6 @@
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.*;
import com.evolveum.midpoint.util.Holder;
Expand All @@ -50,6 +47,9 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Date;
import java.util.List;
Expand All @@ -75,6 +75,7 @@ public class ReportManagerImpl implements ReportManager {
@Autowired private PrismContext prismContext;
@Autowired private ReportServiceImpl reportService;
@Autowired private ModelService modelService;
@Autowired private ModelInteractionService modelInteractionService;
@Autowired private ClusterExecutionHelper clusterExecutionHelper;
@Autowired @Qualifier("cacheRepositoryService") private RepositoryService repositoryService;
@Autowired private SecurityEnforcer securityEnforcer;
Expand All @@ -83,83 +84,70 @@ private boolean isRaw(Collection<SelectorOptions<GetOperationOptions>> options)
return GetOperationOptions.isRaw(SelectorOptions.findRootOptions(options));
}

/**
* Creates and starts task with proper handler, also adds necessary information to task
* (like ReportType reference and so on).
*
* @param report
* @param task
* @param parentResult describes report which has to be created
*/
@Override
public void runReport(PrismObject<ReportType> report, PrismContainer<ReportParameterType> paramContainer, Task task, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException,
SecurityViolationException {
public void runReport(
PrismObject<ReportType> report, PrismContainer<ReportParameterType> paramContainer, Task task, OperationResult result)
throws CommonException {

task.addArchetypeInformation(SystemObjectsType.ARCHETYPE_REPORT_EXPORT_CLASSIC_TASK.value());

if (!reportService.isAuthorizedToRunReport(report, task, parentResult)) {
if (!reportService.isAuthorizedToRunReport(report, task, result)) {
LOGGER.error("User is not authorized to run report {}", report);
throw new SecurityViolationException("Not authorized");
}

ClassicReportExportWorkDefinitionType reportConfig = new ClassicReportExportWorkDefinitionType()
.reportRef(new ObjectReferenceType().oid(report.getOid()).type(ReportType.COMPLEX_TYPE));
.reportRef(ObjectTypeUtil.createObjectRef(report));
if (paramContainer != null && !paramContainer.isEmpty()) {
reportConfig.reportParam(paramContainer.getRealValue());
}

task.getUpdatedTaskObject().getRealValue()
.activity(new ActivityDefinitionType()
.work(new WorkDefinitionsType()
.reportExport(reportConfig)
)
);
var activityDefinition = new ActivityDefinitionType()
.work(new WorkDefinitionsType()
.reportExport(reportConfig));

task.setThreadStopAction(ThreadStopActionType.CLOSE);
task.makeSingle();
TaskType taskTemplate = new TaskType()
.name(generateTaskName("Export", report))
.threadStopAction(ThreadStopActionType.CLOSE);

taskManager.switchToBackground(task, parentResult);
parentResult.setBackgroundTaskOid(task.getOid());
modelInteractionService.submit(
activityDefinition,
ActivitySubmissionOptions.create()
.withTaskTemplate(taskTemplate),
task, result);
}

/**
* Creates and starts task with proper handler, also adds necessary information to task
* (like ReportType reference and so on).
*
* @param report
* @param task
* @param parentResult describes report which has to be created
*/
private static String generateTaskName(String verb, PrismObject<ReportType> report) {
var dateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault())
.format(Instant.now());
return "%s task for %s (%s)".formatted(
verb, report.asObjectable().getName(), dateTime);
}

@Override
public void importReport(PrismObject<ReportType> report, PrismObject<ReportDataType> reportData, Task task, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException,
SecurityViolationException {

task.addArchetypeInformation(SystemObjectsType.ARCHETYPE_REPORT_IMPORT_CLASSIC_TASK.value());
public void importReport(
PrismObject<ReportType> report, PrismObject<ReportDataType> reportData, Task task, OperationResult result)
throws CommonException {

if (!reportService.isAuthorizedToImportReport(report, task, parentResult)) {
if (!reportService.isAuthorizedToImportReport(report, task, result)) {
LOGGER.error("User is not authorized to import report {}", report);
throw new SecurityViolationException("Not authorized");
}

ClassicReportImportWorkDefinitionType reportConfig = new ClassicReportImportWorkDefinitionType()
.reportRef(new ObjectReferenceType().oid(report.getOid()).type(ReportType.COMPLEX_TYPE))
.reportDataRef(new ObjectReferenceType().oid(reportData.getOid()).type(ReportDataType.COMPLEX_TYPE));

task.getUpdatedTaskObject().getRealValue()
.activity(new ActivityDefinitionType()
.work(new WorkDefinitionsType()
.reportImport(reportConfig)
)
);

task.setThreadStopAction(ThreadStopActionType.CLOSE);
task.makeSingle();

taskManager.switchToBackground(task, parentResult);
parentResult.setBackgroundTaskOid(task.getOid());
var activityDefinition = new ActivityDefinitionType()
.work(new WorkDefinitionsType()
.reportImport(new ClassicReportImportWorkDefinitionType()
.reportRef(ObjectTypeUtil.createObjectRef(report))
.reportDataRef(ObjectTypeUtil.createObjectRef(reportData))));

TaskType taskTemplate = new TaskType()
.name(generateTaskName("Import", report))
.threadStopAction(ThreadStopActionType.CLOSE);

modelInteractionService.submit(
activityDefinition,
ActivitySubmissionOptions.create()
.withTaskTemplate(taskTemplate),
task, result);
}

@Override
Expand Down
Expand Up @@ -12,9 +12,11 @@
import java.io.IOException;
import java.util.List;

import com.evolveum.midpoint.report.impl.ReportManagerImpl;
import com.evolveum.midpoint.test.DummyTestResource;
import com.evolveum.midpoint.test.TestReport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;

Expand Down Expand Up @@ -152,17 +154,19 @@ public abstract class EmptyReportIntegrationTest extends AbstractModelIntegratio
TEST_DIR_COMMON, "resource-dummy-outbound.xml", "846e4c54-cee5-4e45-b0cf-ce8914ecba54",
"outbound", (c) -> c.extendSchemaPirate());

private static final TestObject<ArchetypeType> ARCHETYPE_TASK_REPORT_EXPORT_CLASSIC = TestObject.file(TEST_DIR_COMMON,
static final TestObject<ArchetypeType> ARCHETYPE_TASK_REPORT_EXPORT_CLASSIC = TestObject.file(TEST_DIR_COMMON,
"archetype-task-report-export-classic.xml", "00000000-0000-0000-0000-000000000511");
private static final TestObject<ArchetypeType> ARCHETYPE_TASK_REPORT_EXPORT_DISTRIBUTED = TestObject.file(TEST_DIR_COMMON,
"archetype-task-report-export-distributed.xml", "00000000-0000-0000-0000-000000000512");
private static final TestObject<ArchetypeType> ARCHETYPE_TASK_REPORT_IMPORT_CLASSIC = TestObject.file(TEST_DIR_COMMON,
static final TestObject<ArchetypeType> ARCHETYPE_TASK_REPORT_IMPORT_CLASSIC = TestObject.file(TEST_DIR_COMMON,
"archetype-task-report-import-classic.xml", "00000000-0000-0000-0000-000000000510");

private static final File USER_ADMINISTRATOR_FILE = new File(TEST_DIR_COMMON, "user-administrator.xml");
private static final File ROLE_SUPERUSER_FILE = new File(TEST_DIR_COMMON, "role-superuser.xml");
protected static final File SYSTEM_CONFIGURATION_FILE = new File(TEST_DIR_COMMON, "system-configuration.xml");

@Autowired protected ReportManagerImpl reportManager;

@Override
public void initSystem(Task initTask, OperationResult initResult) throws Exception {
super.initSystem(initTask, initResult);
Expand Down
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2010-2023 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.report;

import java.io.File;
import java.util.Objects;

import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.Test;

import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.report.api.ReportManager;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.test.TestReport;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FileFormatConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FileFormatTypeType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ReportDataType;

/**
* Tests e.g. {@link ReportManager#runReport(PrismObject, PrismContainer, Task, OperationResult)} method.
*/
@ContextConfiguration(locations = { "classpath:ctx-report-test-main.xml" })
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class TestMiscellaneous extends EmptyReportIntegrationTest {

private static final File TEST_DIR = new File(TEST_RESOURCES_DIR, "misc");

private static final TestReport REPORT_EXPORT_USERS = TestReport.file(
TEST_DIR, "report-export-users.xml", "63665d73-9829-4064-a8ec-0a04c554ebcc");
private static final TestReport REPORT_IMPORT_USERS = TestReport.file(
TEST_DIR, "report-import-users.xml", "54319c28-cc40-4005-bac3-a5a9dbabd91b");

@Override
public void initSystem(Task initTask, OperationResult initResult) throws Exception {
super.initSystem(initTask, initResult);

initTestObjects(initTask, initResult,
REPORT_EXPORT_USERS,
REPORT_IMPORT_USERS);
}

/**
* Checking that {@link ReportManager#runReport(PrismObject, PrismContainer, Task, OperationResult)} correctly
* applies the archetypes, e.g. their mappings.
*
* MID-8364
*/
@Test
public void test100RunExport() throws Exception {
var task = getTestTask();
var result = task.getResult();

when("report export is run");
reportManager.runReport(REPORT_EXPORT_USERS.get(), null, task, result);

then("the task has archetype mappings applied");
var taskOid = Objects.requireNonNull(result.findTaskOid(), "no task OID");
waitForTaskCloseOrSuspend(taskOid);
assertTask(taskOid, "after")
.display()
.assertArchetypeRef(ARCHETYPE_TASK_REPORT_EXPORT_CLASSIC.oid)
.assertDescription("export")
.assertClosed()
.assertSuccess();
}

/**
* As {@link #test100RunExport()} but for import method {@link ReportManager#importReport(PrismObject, PrismObject,
* Task, OperationResult)}.
*
* MID-8364
*/
@Test
public void test110RunImport() throws Exception {
var task = getTestTask();
var result = task.getResult();

given("user data file");
ReportDataType data = new ReportDataType()
.name("users-to-import")
.fileFormat(FileFormatTypeType.CSV)
.filePath(new File(TEST_DIR, "users-to-import.csv").getAbsolutePath());
addObject(data, task, result);

when("report import is run");
reportManager.importReport(
REPORT_IMPORT_USERS.get(),
data.asPrismObject(),
task, result);

then("the task has archetype mappings applied");
var taskOid = Objects.requireNonNull(result.findTaskOid(), "no task OID");
waitForTaskCloseOrSuspend(taskOid);
assertTask(taskOid, "after")
.display()
.assertArchetypeRef(ARCHETYPE_TASK_REPORT_IMPORT_CLASSIC.oid)
.assertDescription("import")
.assertClosed()
.assertSuccess();

and("user is imported");
assertUserAfterByUsername("jim")
.assertFullName("Jim Hacker");
}

@Override
protected FileFormatConfigurationType getFileFormatConfiguration() {
return null; // unused
}
}
Expand Up @@ -7,5 +7,18 @@
<archetype oid="00000000-0000-0000-0000-000000000511"
xmlns="http://midpoint.evolveum.com/xml/ns/public/common/common-3">
<name>Report export task</name>
<!-- We need only the existence of this object. -->
<!-- We need only the existence of this object plus a single "marking" mapping. -->
<inducement>
<focusMappings>
<mapping>
<strength>strong</strength>
<expression>
<value>export</value>
</expression>
<target>
<path>description</path>
</target>
</mapping>
</focusMappings>
</inducement>
</archetype>

0 comments on commit 5766756

Please sign in to comment.