diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/tasks/recon/ReconciliationLauncher.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/tasks/recon/ReconciliationLauncher.java index 9f063679d2b..23887efa286 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/tasks/recon/ReconciliationLauncher.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/tasks/recon/ReconciliationLauncher.java @@ -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; @@ -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)); diff --git a/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportManager.java b/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportManager.java index 9cf2ba04dbd..2345fae5641 100644 --- a/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportManager.java +++ b/model/report-api/src/main/java/com/evolveum/midpoint/report/api/ReportManager.java @@ -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; @@ -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 report, PrismContainer params, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException; + void runReport(PrismObject report, PrismContainer 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 report, PrismObject reportData, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException; + void importReport(PrismObject report, PrismObject reportData, Task task, OperationResult result) + throws CommonException; /** * todo comments [lazyman] diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java index dd763b7c50c..baae09cbac4 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -83,83 +84,70 @@ private boolean isRaw(Collection> 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 report, PrismContainer paramContainer, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, - SecurityViolationException { + public void runReport( + PrismObject report, PrismContainer 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 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 report, PrismObject 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 report, PrismObject 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 diff --git a/model/report-impl/src/test/java/com/evolveum/midpoint/report/EmptyReportIntegrationTest.java b/model/report-impl/src/test/java/com/evolveum/midpoint/report/EmptyReportIntegrationTest.java index 8d7e7c82f97..8ab3ee9899e 100644 --- a/model/report-impl/src/test/java/com/evolveum/midpoint/report/EmptyReportIntegrationTest.java +++ b/model/report-impl/src/test/java/com/evolveum/midpoint/report/EmptyReportIntegrationTest.java @@ -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; @@ -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 ARCHETYPE_TASK_REPORT_EXPORT_CLASSIC = TestObject.file(TEST_DIR_COMMON, + static final TestObject 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 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 ARCHETYPE_TASK_REPORT_IMPORT_CLASSIC = TestObject.file(TEST_DIR_COMMON, + static final TestObject 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); diff --git a/model/report-impl/src/test/java/com/evolveum/midpoint/report/TestMiscellaneous.java b/model/report-impl/src/test/java/com/evolveum/midpoint/report/TestMiscellaneous.java new file mode 100644 index 00000000000..54a9e58bb49 --- /dev/null +++ b/model/report-impl/src/test/java/com/evolveum/midpoint/report/TestMiscellaneous.java @@ -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 + } +} diff --git a/model/report-impl/src/test/resources/common/archetype-task-report-export-classic.xml b/model/report-impl/src/test/resources/common/archetype-task-report-export-classic.xml index 311e72c2a5b..55b15e5b349 100644 --- a/model/report-impl/src/test/resources/common/archetype-task-report-export-classic.xml +++ b/model/report-impl/src/test/resources/common/archetype-task-report-export-classic.xml @@ -7,5 +7,18 @@ Report export task - + + + + + strong + + export + + + description + + + + diff --git a/model/report-impl/src/test/resources/common/archetype-task-report-import-classic.xml b/model/report-impl/src/test/resources/common/archetype-task-report-import-classic.xml index 6ca2d0c42db..8bf31afdaa0 100644 --- a/model/report-impl/src/test/resources/common/archetype-task-report-import-classic.xml +++ b/model/report-impl/src/test/resources/common/archetype-task-report-import-classic.xml @@ -7,5 +7,18 @@ Report import task - + + + + + strong + + import + + + description + + + + diff --git a/model/report-impl/src/test/resources/misc/report-export-users.xml b/model/report-impl/src/test/resources/misc/report-export-users.xml new file mode 100644 index 00000000000..72683741bb0 --- /dev/null +++ b/model/report-impl/src/test/resources/misc/report-export-users.xml @@ -0,0 +1,17 @@ + + + + + export-users + + + UserType + + + diff --git a/model/report-impl/src/test/resources/misc/report-import-users.xml b/model/report-impl/src/test/resources/misc/report-import-users.xml new file mode 100644 index 00000000000..4eaa9df24ac --- /dev/null +++ b/model/report-impl/src/test/resources/misc/report-import-users.xml @@ -0,0 +1,28 @@ + + + + import-users + + + + name + name + + + fullName + fullName + name + + UserType + + + + import + + diff --git a/model/report-impl/src/test/resources/misc/users-to-import.csv b/model/report-impl/src/test/resources/misc/users-to-import.csv new file mode 100644 index 00000000000..f06f47c5cec --- /dev/null +++ b/model/report-impl/src/test/resources/misc/users-to-import.csv @@ -0,0 +1,2 @@ +"name";"fullName" +"jim";"Jim Hacker" diff --git a/model/report-impl/testng-integration.xml b/model/report-impl/testng-integration.xml index 709b9d28224..2bb87f09107 100644 --- a/model/report-impl/testng-integration.xml +++ b/model/report-impl/testng-integration.xml @@ -16,6 +16,7 @@ +