diff --git a/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataClassComponentController.groovy b/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataClassComponentController.groovy index 769c35a04..0b41a5cbd 100644 --- a/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataClassComponentController.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataClassComponentController.groovy @@ -36,7 +36,7 @@ import org.maurodata.web.PaginationParams @Secured(SecurityRule.IS_AUTHENTICATED) class DataClassComponentController extends AdministeredItemController implements DataClassComponentApi { - AdministeredItemCacheableRepository.DataClassCacheableRepository dataClassRepository + DataClassCacheableRepository dataClassRepository AdministeredItemCacheableRepository.DataClassComponentCacheableRepository dataClassComponentRepository diff --git a/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataFlowController.groovy b/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataFlowController.groovy index dc67575dc..11c0d9269 100644 --- a/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataFlowController.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/controller/dataflow/DataFlowController.groovy @@ -41,7 +41,7 @@ import org.maurodata.plugin.exporter.DataFlowExporterPlugin import org.maurodata.plugin.exporter.ModelItemExporterPlugin import org.maurodata.plugin.importer.DataFlowImporterPlugin import org.maurodata.plugin.importer.json.JsonDataFlowImporterPlugin -import org.maurodata.service.dataflow.DataflowService +import org.maurodata.service.dataflow.DataFlowService import org.maurodata.util.exporter.ExporterUtils import org.maurodata.web.ListResponse import org.maurodata.web.PaginationParams @@ -55,12 +55,11 @@ class DataFlowController extends AdministeredItemController AdministeredItemCacheableRepository.DataFlowCacheableRepository dataFlowRepository ModelCacheableRepository.DataModelCacheableRepository dataModelRepository DataFlowContentRepository dataFlowContentRepository - DataflowService dataFlowService - + DataFlowService dataFlowService DataFlowController(AdministeredItemCacheableRepository.DataFlowCacheableRepository dataFlowRepository, ModelCacheableRepository.DataModelCacheableRepository dataModelRepository, - DataFlowContentRepository dataFlowContentRepository, DataflowService dataFlowService) { + DataFlowContentRepository dataFlowContentRepository, DataFlowService dataFlowService) { super(DataFlow, dataFlowRepository, dataModelRepository, dataFlowContentRepository) this.dataFlowRepository = dataFlowRepository this.dataModelRepository = dataModelRepository diff --git a/mauro-api/src/main/groovy/org/maurodata/controller/datamodel/DataModelController.groovy b/mauro-api/src/main/groovy/org/maurodata/controller/datamodel/DataModelController.groovy index 771848cd5..1fb565ed7 100644 --- a/mauro-api/src/main/groovy/org/maurodata/controller/datamodel/DataModelController.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/controller/datamodel/DataModelController.groovy @@ -56,7 +56,6 @@ import org.maurodata.domain.search.dto.SearchRequestDTO import org.maurodata.domain.search.dto.SearchResultsDTO import org.maurodata.domain.security.Role import org.maurodata.persistence.cache.AdministeredItemCacheableRepository.DataElementCacheableRepository -import org.maurodata.persistence.cache.AdministeredItemCacheableRepository.DataTypeCacheableRepository import org.maurodata.persistence.cache.ModelCacheableRepository.DataModelCacheableRepository import org.maurodata.persistence.cache.ModelCacheableRepository.FolderCacheableRepository import org.maurodata.persistence.datamodel.DataElementRepository @@ -65,8 +64,10 @@ import org.maurodata.persistence.datamodel.DataTypeContentRepository import org.maurodata.persistence.search.SearchRepository import org.maurodata.plugin.datatype.DefaultDataTypeProviderPlugin import org.maurodata.plugin.exporter.DataModelExporterPlugin +import org.maurodata.plugin.exporter.ModelExporterPlugin import org.maurodata.plugin.importer.DataModelImporterPlugin import org.maurodata.service.plugin.PluginService +import org.maurodata.util.exporter.ExporterUtils import org.maurodata.web.ListResponse @Slf4j @@ -81,27 +82,21 @@ class DataModelController extends ModelController implements DataMode @Inject SearchRepository searchRepository - @Inject - DataModelService dataModelService - @Inject DataElementCacheableRepository dataElementCacheableRepository - @Inject - DataTypeCacheableRepository dataTypeCacheableRepository - @Inject DataTypeContentRepository dataTypeContentRepository @Inject DataElementRepository dataElementRepository + DataModelController(DataModelCacheableRepository dataModelRepository, FolderCacheableRepository folderRepository, DataModelContentRepository dataModelContentRepository, - DataModelService dataModelService) { - super(DataModel, dataModelRepository, folderRepository, dataModelContentRepository, dataModelService) + DataModelService domainDataModelService) { + super(DataModel, dataModelRepository, folderRepository, dataModelContentRepository, domainDataModelService) this.dataModelRepository = dataModelRepository this.dataModelContentRepository = dataModelContentRepository - this.dataModelService = dataModelService } @Audit @@ -116,14 +111,14 @@ class DataModelController extends ModelController implements DataMode DataModel create(UUID folderId, @Body @NonNull DataModel dataModel, @Nullable @QueryValue String defaultDataTypeProvider = null) { // First try and get the default datatypes if applicable List importedDataTypes = [] - if(defaultDataTypeProvider) { + if (defaultDataTypeProvider) { DefaultDataTypeProviderPlugin defaultDataTypeProviderPlugin = mauroPluginService.getPlugin(DefaultDataTypeProviderPlugin, defaultDataTypeProvider) PluginService.handlePluginNotFound(defaultDataTypeProviderPlugin, DefaultDataTypeProviderPlugin, defaultDataTypeProvider) importedDataTypes.addAll(defaultDataTypeProviderPlugin.dataTypes) } DataModel newDataModel = super.create(folderId, dataModel) as DataModel // If we previously got datatypes, now save them into the model - if(importedDataTypes.size() > 0) { + if (importedDataTypes.size() > 0) { newDataModel.dataTypes = importedDataTypes newDataModel.dataTypes.each {dataType -> dataType.dataModel = newDataModel @@ -199,7 +194,12 @@ class DataModelController extends ModelController implements DataMode @Audit @Get(Paths.DATA_MODEL_EXPORT) HttpResponse exportModel(UUID id, @Nullable String namespace, @Nullable String name, @Nullable String version) { - super.exportModel(id, namespace, name, version) + ModelExporterPlugin mauroPlugin = mauroPluginService.getPlugin(ModelExporterPlugin, namespace, name, version) + PluginService.handlePluginNotFound(mauroPlugin, namespace, name) + + DataModel existing = getModelContent(id) + super.dataModelImportService.updateModelPaths(existing) + ExporterUtils.createExportResponse(mauroPlugin, existing) } @Transactional @@ -208,10 +208,12 @@ class DataModelController extends ModelController implements DataMode @Consumes(MediaType.MULTIPART_FORM_DATA) @Post(Paths.DATA_MODEL_IMPORT) ListResponse importModel(@Body MultipartBody body, String namespace, String name, @Nullable String version) { - super.importModel(body, namespace, name, version) + List imported = super.consumeExportFile(body, namespace, name, version) + List saved = super.dataModelImportService.saveImportedModels(imported) + smallerResponse(saved) } - @Audit + @Audit @Get(Paths.DATA_MODEL_DIFF) ObjectDiff diffModels(@NonNull UUID id, @NonNull UUID otherId) { DataModel dataModel = modelContentRepository.findWithContentById(id) @@ -519,4 +521,6 @@ class DataModelController extends ModelController implements DataMode List dataModelTypes() { return DataModelType.labels() } + + } diff --git a/mauro-api/src/main/groovy/org/maurodata/controller/folder/FolderController.groovy b/mauro-api/src/main/groovy/org/maurodata/controller/folder/FolderController.groovy index f37784761..987ede10f 100644 --- a/mauro-api/src/main/groovy/org/maurodata/controller/folder/FolderController.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/controller/folder/FolderController.groovy @@ -1,25 +1,5 @@ package org.maurodata.controller.folder -import org.maurodata.ErrorHandler -import org.maurodata.api.Paths -import org.maurodata.api.folder.FolderApi -import org.maurodata.api.model.PermissionsDTO -import org.maurodata.audit.Audit -import org.maurodata.controller.model.ModelController -import org.maurodata.domain.datamodel.DataModel -import org.maurodata.domain.facet.EditType -import org.maurodata.domain.folder.Folder -import org.maurodata.persistence.cache.ModelCacheableRepository.FolderCacheableRepository -import org.maurodata.persistence.folder.FolderContentRepository -import org.maurodata.domain.search.dto.SearchRequestDTO -import org.maurodata.domain.search.dto.SearchResultsDTO -import org.maurodata.plugin.exporter.FolderExporterPlugin -import org.maurodata.plugin.exporter.ModelExporterPlugin -import org.maurodata.plugin.importer.DataModelImporterPlugin -import org.maurodata.plugin.importer.FolderImporterPlugin -import org.maurodata.service.plugin.PluginService -import org.maurodata.web.ListResponse - import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import io.micronaut.core.annotation.NonNull @@ -43,7 +23,22 @@ import io.micronaut.security.annotation.Secured import io.micronaut.security.rules.SecurityRule import io.micronaut.transaction.annotation.Transactional import jakarta.inject.Inject -import jakarta.inject.Named +import org.maurodata.ErrorHandler +import org.maurodata.api.Paths +import org.maurodata.api.folder.FolderApi +import org.maurodata.api.model.PermissionsDTO +import org.maurodata.audit.Audit +import org.maurodata.controller.model.ModelController +import org.maurodata.domain.facet.EditType +import org.maurodata.domain.folder.Folder +import org.maurodata.domain.search.dto.SearchRequestDTO +import org.maurodata.domain.search.dto.SearchResultsDTO +import org.maurodata.persistence.cache.ModelCacheableRepository.FolderCacheableRepository +import org.maurodata.persistence.folder.FolderContentRepository +import org.maurodata.plugin.exporter.FolderExporterPlugin +import org.maurodata.plugin.importer.FolderImporterPlugin +import org.maurodata.service.core.AllFolderService +import org.maurodata.web.ListResponse @Slf4j @CompileStatic @@ -51,11 +46,14 @@ import jakarta.inject.Named @Secured(SecurityRule.IS_ANONYMOUS) class FolderController extends ModelController implements FolderApi { - @Inject FolderContentRepository folderContentRepository + @Inject + AllFolderService allFolderService + FolderController(FolderCacheableRepository folderRepository, FolderContentRepository folderContentRepository) { super(Folder, folderRepository, folderRepository, folderContentRepository) + this.folderContentRepository = folderContentRepository } @Audit @@ -171,7 +169,9 @@ class FolderController extends ModelController implements FolderApi { @Audit(title = EditType.IMPORT, description = 'Import folder') @Post(Paths.FOLDER_IMPORT) ListResponse importModel(@Body MultipartBody body, String namespace, String name, @Nullable String version) { - super.importModel(body, namespace, name, version) + List imported = super.consumeExportFile(body, namespace, name, version) as List + List saved = allFolderService.importModel(imported, folderContentRepository) + smallerResponse(saved) } @Get('/api/folder/search{?requestDTO}') @@ -238,16 +238,4 @@ class FolderController extends ModelController implements FolderApi { List folderExporters() { mauroPluginService.listPlugins(FolderExporterPlugin) } - - /* - @Transactional - @ExecuteOn(TaskExecutors.IO) - @Audit(title = EditType.IMPORT, description = "Import folder") - @Consumes(MediaType.MULTIPART_FORM_DATA) - @Post(Paths.FOLDER_IMPORT) - ListResponse importFolder(@Body MultipartBody body, String namespace, String name, @Nullable String version) { - super.importModel(body, namespace, name, version) - } -*/ - } \ No newline at end of file diff --git a/mauro-api/src/main/groovy/org/maurodata/controller/folder/VersionedFolderController.groovy b/mauro-api/src/main/groovy/org/maurodata/controller/folder/VersionedFolderController.groovy index eb40014d7..622db3c00 100644 --- a/mauro-api/src/main/groovy/org/maurodata/controller/folder/VersionedFolderController.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/controller/folder/VersionedFolderController.groovy @@ -6,7 +6,9 @@ import io.micronaut.core.annotation.NonNull import io.micronaut.core.annotation.Nullable import io.micronaut.http.HttpResponse import io.micronaut.http.HttpStatus +import io.micronaut.http.MediaType import io.micronaut.http.annotation.Body +import io.micronaut.http.annotation.Consumes import io.micronaut.http.annotation.Controller import io.micronaut.http.annotation.Delete import io.micronaut.http.annotation.Get @@ -31,12 +33,14 @@ import org.maurodata.api.model.ModelVersionedWithTargetsRefDTO import org.maurodata.api.model.PermissionsDTO import org.maurodata.audit.Audit import org.maurodata.controller.model.ModelController +import org.maurodata.domain.facet.EditType import org.maurodata.domain.folder.Folder import org.maurodata.domain.folder.FolderService import org.maurodata.domain.model.version.CreateNewVersionData import org.maurodata.domain.model.version.FinaliseData import org.maurodata.persistence.cache.ModelCacheableRepository.FolderCacheableRepository import org.maurodata.persistence.folder.FolderContentRepository +import org.maurodata.service.core.AllFolderService import org.maurodata.web.ListResponse @Slf4j @@ -47,15 +51,14 @@ class VersionedFolderController extends ModelController implements Versi private static final String MY_CLASS_TYPE = "VersionedFolder" - @Inject FolderContentRepository folderContentRepository @Inject - FolderService folderService + AllFolderService allFolderService VersionedFolderController(FolderCacheableRepository folderRepository, FolderContentRepository folderContentRepository, FolderService folderService) { super(Folder, folderRepository, folderRepository, folderContentRepository, folderService) - this.folderService = folderService + this.folderContentRepository = folderContentRepository } @Get(Paths.VERSIONED_FOLDER_ID) @@ -86,8 +89,15 @@ class VersionedFolderController extends ModelController implements Versi @Transactional @Post(Paths.CHILD_VERSIONED_FOLDER_LIST) Folder create(UUID parentId, @Body @NonNull Folder folder) { + folder.authority = super.authorityService.getDefaultAuthority() + Folder cleanItem = cleanBody(folder) + + Folder parent = validate(cleanItem, parentId) folder.setVersionable(true) - super.create(parentId, folder) + + Folder created = createEntity(parent, cleanItem) + created = validateAndAddClassifiers(created) + created } @Audit @@ -139,11 +149,27 @@ class VersionedFolderController extends ModelController implements Versi } + @Audit(title = EditType.EXPORT, description = 'Export Versioned folder') + @Get(Paths.VERSIONED_FOLDER_EXPORT) + @Override + HttpResponse exportModel(UUID id, @Nullable String namespace, @Nullable String name, @Nullable String version) { + super.exportModel(id,namespace, name, version) + + } + + @Transactional + @ExecuteOn(TaskExecutors.IO) + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Audit(title = EditType.IMPORT, description = 'Import folder') + @Post(Paths.VERSIONED_FOLDER_IMPORT) @Override ListResponse importModel(@Body MultipartBody body, String namespace, String name, @Nullable String version) { - super.importModel(body, namespace, name, version) + List imported = super.consumeExportFile(body, namespace, name, version) + List saved = allFolderService.importModel(imported, folderContentRepository) + smallerResponse(saved) } + @Audit @Transactional @Delete(Paths.VERSIONED_FOLDER_ID) diff --git a/mauro-api/src/main/groovy/org/maurodata/controller/model/AdministeredItemController.groovy b/mauro-api/src/main/groovy/org/maurodata/controller/model/AdministeredItemController.groovy index 04ea59771..7b6de925a 100644 --- a/mauro-api/src/main/groovy/org/maurodata/controller/model/AdministeredItemController.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/controller/model/AdministeredItemController.groovy @@ -50,7 +50,7 @@ abstract class AdministeredItemController itemClass, AdministeredItemRepository administeredItemRepository, AdministeredItemRepository

parentItemRepository, AdministeredItemContentRepository administeredItemContentRepository) { super(administeredItemRepository) @@ -257,4 +257,5 @@ abstract class AdministeredItemController extends AdministeredItemControll @Inject ImporterUtils importerUtils + @Inject + DataModelImportService dataModelImportService + ModelController(Class modelClass, AdministeredItemCacheableRepository modelRepository, FolderCacheableRepository folderRepository, ModelContentRepository modelContentRepository) { super(modelClass, modelRepository, folderRepository, modelContentRepository) @@ -363,8 +367,8 @@ abstract class ModelController extends AdministeredItemControll ExporterUtils.createExportResponse(mauroPlugin, existing) } - ListResponse importModel(@Body MultipartBody body, String namespace, String name, @Nullable String version) { + List consumeExportFile(@Body MultipartBody body, String namespace, String name, @Nullable String version) { ModelImporterPlugin mauroPlugin = mauroPluginService.getPlugin(ModelImporterPlugin, namespace, name, version) PluginService.handlePluginNotFound(mauroPlugin, namespace, name) @@ -373,22 +377,33 @@ abstract class ModelController extends AdministeredItemControll Folder folder = null if (importParameters.folderId != null) { folder = folderRepository.readById(importParameters.folderId) - } else if( !(mauroPlugin instanceof FolderImporterPlugin)) { // folderId is null and plugin is not a folder import + } else if (!(mauroPlugin instanceof FolderImporterPlugin)) { + // folderId is null and plugin is not a folder import ErrorHandler.handleErrorOnNullObject(HttpStatus.NOT_FOUND, importParameters.folderId, "Please choose the folder into which the Model/s should be imported.") } - - List imported = (List) mauroPlugin.importModels(importParameters) - accessControlService.checkRole(Role.EDITOR, folder) - List saved = imported.collect { M imp -> + + mauroPlugin.importModels(importParameters).collect {M imp-> imp.folder = folder + imp + } + } + + ListResponse importModel(@Body MultipartBody body, String namespace, String name, @Nullable String version) { + List imported = consumeExportFile(body, namespace, name, version) + List saved = [] + saved = imported.collect {M imp -> log.info '** about to saveWithContentBatched... **' updateCreationProperties(imp) M savedImported = modelContentRepository.saveWithContent(imp) log.info '** finished saveWithContentBatched **' savedImported } - List smallerResponse = saved.collect { model -> + smallerResponse(saved) + } + + ListResponse smallerResponse(List saved) { + List smallerResponse = saved.collect {model -> show(model.id) } ListResponse.from(smallerResponse) @@ -720,29 +735,12 @@ abstract class ModelController extends AdministeredItemControll return permissions } - protected M saveDataModel(DataModel dataModel) { - DataModel savedImport = modelContentRepository.saveWithContent(dataModel as M) as DataModel - savedImport as M - } - - protected M saveFolder(Folder folder) { - Folder savedImport = modelContentRepository.saveWithContent(folder as M) as Folder - savedImport as M - } - - protected M saveCodeSet(CodeSet codeSet) { - CodeSet savedImport = modelContentRepository.saveWithContent(codeSet as M) as CodeSet - savedImport as M - } - - protected M saveTerminology(Terminology terminology) { - Terminology savedImport = modelContentRepository.saveWithContent(terminology as M) as Terminology - savedImport as M + M getModelContent( UUID modelId) { + M existing = modelContentRepository.findWithContentById(modelId) + existing.setAssociations() + existing } - protected M saveModel(M model) { - modelContentRepository.saveWithContent(model) - } protected ModelVersionDTO latestModelVersion(UUID id) { final List allModels = populateVersionTree(id, false, null) diff --git a/mauro-api/src/main/groovy/org/maurodata/service/core/AdministeredItemService.groovy b/mauro-api/src/main/groovy/org/maurodata/service/core/AdministeredItemService.groovy index f65d0a614..3024fb9ef 100644 --- a/mauro-api/src/main/groovy/org/maurodata/service/core/AdministeredItemService.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/service/core/AdministeredItemService.groovy @@ -1,9 +1,11 @@ package org.maurodata.service.core +import groovy.transform.CompileStatic import jakarta.inject.Inject import org.maurodata.domain.model.AdministeredItem import org.maurodata.persistence.model.PathRepository +@CompileStatic abstract class AdministeredItemService { @Inject @@ -16,11 +18,12 @@ abstract class AdministeredItemService { item } - protected void updateCreationProperties(AdministeredItem item) { + protected AdministeredItem updateCreationProperties(AdministeredItem item) { item.id = null item.version = null item.dateCreated = null item.lastUpdated = null + item } AdministeredItem updatePaths(AdministeredItem administeredItem) { diff --git a/mauro-api/src/main/groovy/org/maurodata/service/core/AllFolderService.groovy b/mauro-api/src/main/groovy/org/maurodata/service/core/AllFolderService.groovy new file mode 100644 index 000000000..fdab30238 --- /dev/null +++ b/mauro-api/src/main/groovy/org/maurodata/service/core/AllFolderService.groovy @@ -0,0 +1,39 @@ +package org.maurodata.service.core + +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.maurodata.domain.folder.Folder +import org.maurodata.persistence.folder.FolderContentRepository +import org.maurodata.security.AccessControlService +import org.maurodata.service.datamodel.DataModelImportService + +@CompileStatic +@Slf4j +@Singleton +class AllFolderService extends AdministeredItemService { + + @Inject + DataModelImportService dataModelImportService + + @Inject + AccessControlService accessControlService + + List importModel(List exported, FolderContentRepository folderContentRepository) { + exported.collect {imp -> + imp.dataModels.each {dataModelImp -> + dataModelImp = dataModelImportService.preProcessDataFlows(dataModelImp) + } + + imp = updateCreationProperties(imp) + imp.catalogueUser = accessControlService.getUser() ?: null + log.info '** about to saveWithContentBatched... **' + Folder savedImported = folderContentRepository.saveWithContent(imp as Folder) + + savedImported.dataModels = dataModelImportService.saveDataFlowModelItems(savedImported.dataModels) + log.info '** finished saveWithContentBatched **' + savedImported + } + } +} diff --git a/mauro-api/src/main/groovy/org/maurodata/service/dataflow/DataflowService.groovy b/mauro-api/src/main/groovy/org/maurodata/service/dataflow/DataFlowService.groovy similarity index 96% rename from mauro-api/src/main/groovy/org/maurodata/service/dataflow/DataflowService.groovy rename to mauro-api/src/main/groovy/org/maurodata/service/dataflow/DataFlowService.groovy index 9431be1de..fa623846e 100644 --- a/mauro-api/src/main/groovy/org/maurodata/service/dataflow/DataflowService.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/service/dataflow/DataFlowService.groovy @@ -14,7 +14,6 @@ import org.maurodata.domain.datamodel.DataClass import org.maurodata.domain.datamodel.DataElement import org.maurodata.domain.datamodel.DataModel import org.maurodata.domain.folder.Folder -import org.maurodata.domain.model.AdministeredItem import org.maurodata.domain.model.ModelItem import org.maurodata.domain.security.Role import org.maurodata.persistence.cache.ModelCacheableRepository @@ -32,7 +31,7 @@ import org.maurodata.utils.importer.ImporterUtils @CompileStatic @Slf4j -class DataflowService extends AdministeredItemService { +class DataFlowService extends AdministeredItemService { AccessControlService accessControlService ModelCacheableRepository.FolderCacheableRepository folderRepository @@ -43,7 +42,7 @@ class DataflowService extends AdministeredItemService { ImporterUtils importerUtils @Inject - DataflowService(AccessControlService accessControlService, ModelCacheableRepository.FolderCacheableRepository folderRepository, + DataFlowService(AccessControlService accessControlService, ModelCacheableRepository.FolderCacheableRepository folderRepository, ModelCacheableRepository.DataModelCacheableRepository dataModelRepository, MauroPluginService mauroPluginService, PathService pathService, DataModelContentRepository dataModelContentRepository, ImporterUtils importerUtils) { this.accessControlService = accessControlService @@ -105,13 +104,14 @@ class DataflowService extends AdministeredItemService { it.targetDataClasses = getImportDataClass(it.targetDataClasses, target) it.dataElementComponents = findImportDataElements(it.dataElementComponents, source, target) } - updateCreationProperties(imp) + imp = updateCreationProperties(imp) + imp.catalogueUser = accessControlService.getUser() } imported } - @Override - AdministeredItem updatePaths(AdministeredItem dataFlow) { + + DataFlow updatePaths(DataFlow dataFlow) { updateDerivedProperties(dataFlow) (dataFlow as DataFlow).dataClassComponents.each { updateDerivedProperties(it) diff --git a/mauro-api/src/main/groovy/org/maurodata/service/datamodel/DataClassService.groovy b/mauro-api/src/main/groovy/org/maurodata/service/datamodel/DataClassService.groovy index 3bc01f7a2..995814f1c 100644 --- a/mauro-api/src/main/groovy/org/maurodata/service/datamodel/DataClassService.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/service/datamodel/DataClassService.groovy @@ -12,6 +12,7 @@ import org.maurodata.domain.model.AdministeredItem import org.maurodata.persistence.cache.AdministeredItemCacheableRepository import org.maurodata.persistence.datamodel.DataClassContentRepository import org.maurodata.persistence.model.PathRepository +import org.maurodata.security.AccessControlService import org.maurodata.service.core.AdministeredItemService @CompileStatic @@ -23,6 +24,8 @@ class DataClassService extends AdministeredItemService{ AdministeredItemCacheableRepository.DataTypeCacheableRepository dataTypeCacheableRepository AdministeredItemCacheableRepository.DataClassCacheableRepository dataClassCacheableRepository DataTypeService dataTypeService + @Inject + AccessControlService accessControlService @Inject DataClassService(PathRepository pathRepository, DataClassContentRepository dataClassContentRepository, @@ -77,11 +80,12 @@ class DataClassService extends AdministeredItemService{ //target model does not have existing dataType? copy new DataType in target model copiedDE.dataType = setOrCreateNewDataType(target, copiedDE.dataType) } - updateCreationProperties(copiedDE as AdministeredItem) + copiedDE = updateCreationProperties(copiedDE) + copiedDE.catalogueUser = accessControlService.getUser() updateDerivedProperties(copiedDE) - copiedDE.dataModel = target - copiedDE.dataClass = dataClass - copiedDataElements.add(copiedDE) + (copiedDE as DataElement).dataModel = target + (copiedDE as DataElement).dataClass = dataClass + copiedDataElements.add(copiedDE as DataElement) } } dataElementCacheableRepository.saveAll(copiedDataElements) diff --git a/mauro-api/src/main/groovy/org/maurodata/service/datamodel/DataModelImportService.groovy b/mauro-api/src/main/groovy/org/maurodata/service/datamodel/DataModelImportService.groovy new file mode 100644 index 000000000..782b8e8c1 --- /dev/null +++ b/mauro-api/src/main/groovy/org/maurodata/service/datamodel/DataModelImportService.groovy @@ -0,0 +1,258 @@ +package org.maurodata.service.datamodel + +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j +import jakarta.inject.Inject +import jakarta.inject.Singleton +import org.maurodata.domain.dataflow.DataFlow +import org.maurodata.domain.datamodel.DataClass +import org.maurodata.domain.datamodel.DataElement +import org.maurodata.domain.datamodel.DataModel +import org.maurodata.domain.datamodel.ModelType +import org.maurodata.domain.model.ModelItem +import org.maurodata.persistence.cache.AdministeredItemCacheableRepository +import org.maurodata.persistence.cache.ModelCacheableRepository +import org.maurodata.persistence.datamodel.DataModelContentRepository +import org.maurodata.persistence.model.PathRepository +import org.maurodata.security.AccessControlService +import org.maurodata.service.core.AdministeredItemService + +import static org.maurodata.util.PathStringUtils.getPathFrom + +@CompileStatic +@Singleton +@Slf4j +class DataModelImportService extends AdministeredItemService { + + DataModelContentRepository dataModelContentRepository + ModelCacheableRepository.DataModelCacheableRepository dataModelCacheableRepository + AdministeredItemCacheableRepository.DataClassComponentCacheableRepository dataClassComponentCacheableRepository + AdministeredItemCacheableRepository.DataElementComponentCacheableRepository dataElementComponentCacheableRepository + + @Inject + AccessControlService accessControlService + + DataModelImportService(ModelCacheableRepository.DataModelCacheableRepository dataModelCacheableRepository, + DataModelContentRepository dataModelContentRepository, + AdministeredItemCacheableRepository.DataClassComponentCacheableRepository dataClassComponentCacheableRepository, + AdministeredItemCacheableRepository.DataElementComponentCacheableRepository dataElementComponentCacheableRepository, + PathRepository pathRepository) { + this.pathRepository = pathRepository + this.dataModelCacheableRepository = dataModelCacheableRepository + this.dataModelContentRepository = dataModelContentRepository + this.dataClassComponentCacheableRepository = dataClassComponentCacheableRepository + this.dataElementComponentCacheableRepository = dataElementComponentCacheableRepository + } + + List saveImportedModels(List dataModels) { + dataModels.collect {imp -> + imp = preProcessDataFlows(imp) + DataModel savedImported = saveModelContents(imp) + savedImported + } + } + + DataModel preProcessDataFlows(DataModel imp) { + List validatedTargets = [] + imp.targetDataFlows.each { + DataModel matchedSource = matchedModel(it.source, ModelType.SOURCE) + if (!matchedSource) { + log.warn("Import dataflow - no match found for source model: $it.source.id, with label : $it.source.label. Not importing dataflow") + } else { + it.source = matchedSource + validatedTargets.add(it) + } + } + imp.targetDataFlows = validatedTargets + + List validatedSources = [] + imp.sourceDataFlows.each { + DataModel matchedTarget = matchedModel(it.target, ModelType.TARGET) + if (!matchedTarget) { + log.warn("Import dataflow - no match found for source model: $it.target.id, with label : $it.target.label. Not importing dataflow") + } else { + it.target = matchedTarget + validatedSources.add(it) + } + } + imp.sourceDataFlows = validatedSources + imp + } + + List saveDataFlowModelItems(List saved) { + saved.collect {savedImported -> + saveDataFlowModelItems(savedImported) + } + } + + DataModel saveDataFlowModelItems(DataModel saveImported) { + List savedImportedDataClasses = saveImported.allDataClasses.each {updateDerivedProperties(it)} as List + List saveImportedDataElements = saveImported.dataElements.each {updateDerivedProperties(it)} + + saveImported.targetDataFlows.each { + DataModel matchedSource = dataModelContentRepository.findWithContentById(it.source.id) + List matchedSourceDataClasses = matchedSource.allDataClasses.each { + updateDerivedProperties(it) + } as List + List matchedSourceDataElements = matchedSource.dataElements.each { + updateDerivedProperties(it) + } + + it.dataClassComponents.each {dataClassComponent -> + updateDerivedProperties(dataClassComponent) + dataClassComponent.targetDataClasses.each {impDataClass -> + updateDerivedProperties(impDataClass) + DataClass saveImportedDataClass = savedImportedDataClasses.find { + getPathFrom('dc', it.path.pathString) == getPathFrom('dc', impDataClass.path.pathString) + } + if (saveImportedDataClass) { + dataClassComponentCacheableRepository.addTargetDataClass(dataClassComponent.id, saveImportedDataClass.id) + } + } + dataClassComponent.sourceDataClasses.each {exportSourceDC -> + updateDerivedProperties(exportSourceDC) + DataClass matchedDC = matchedSourceDataClasses.find { + subPathsMatch(it, exportSourceDC) + } + if (matchedDC) { + dataClassComponentCacheableRepository.addSourceDataClass(dataClassComponent.id, matchedDC.id) + } + } + + dataClassComponent.dataElementComponents.each {dEC -> + dEC.targetDataElements.each {impDataElement -> + updateDerivedProperties(impDataElement) + DataElement saveImportedDataElement = + saveImportedDataElements.find {getPathFrom('dc', it.path.pathString) == getPathFrom('dc', impDataElement.path.pathString)} + if (saveImportedDataElement) { + dataElementComponentCacheableRepository.addTargetDataElement(dEC.id, saveImportedDataElement.id) + } + } + + dEC.sourceDataElements.each {exportSourceDE -> + updateDerivedProperties(exportSourceDE) + DataElement matchedSourceDE = + matchedSourceDataElements.find { + subPathsMatch(it, exportSourceDE) + } + if (matchedSourceDE) { + dataElementComponentCacheableRepository.addSourceDataElement(dEC.id, matchedSourceDE.id) + } + } + } + } + } + saveImported + } + + DataModel saveModelContents(DataModel imp) { + log.info '** about to saveWithContentBatched... **' + imp = updateCreationProperties(imp) as DataModel + imp.catalogueUser = accessControlService.getUser() + DataModel savedImported = dataModelContentRepository.saveWithContent(imp) + saveDataFlowModelItems(savedImported) + + log.info '** finished saveWithContentBatched **' + savedImported + } + + DataModel updateModelPaths(DataModel dataModel) { + updatePaths(dataModel) + dataModel.dataClasses.each { + updatePaths(it) + it.dataClasses.each { + updatePaths(it) + } + } + dataModel.dataElements.each { + updatePaths(it) + } + dataModel.dataTypes.each { + updatePaths(it) + } + dataModel.sourceDataFlows = updateDataFlowPaths(dataModel.sourceDataFlows) + dataModel.targetDataFlows = updateDataFlowPaths(dataModel.targetDataFlows) + dataModel + } + + + protected DataModel findByLabelAndBranchNameExcludeId(String label, String branchName, UUID id) { + //dataflow branch name and label. Excluding self source datamodel from list + List importedSources = dataModelCacheableRepository.findAllByLabelAndBranchName(label, branchName).findAll { + it.id != id + } + if (importedSources.size() > 1) { + log.warn("Multiple models found with label ${label} and branchName ${branchName}. Returning 1st match") + } + importedSources.isEmpty() ? null : importedSources.first() + } + + /** + find matching model for modelToMatch + */ + protected DataModel matchedModel(DataModel modelToMatch, ModelType modelType) { + DataModel matchedModel = findByLabelAndBranchNameExcludeId(modelToMatch.label, modelToMatch.branchName, modelToMatch.id) + if (canImportModel(modelToMatch, matchedModel, modelType)) { + matchedModel + } else { + null + } + } + + protected boolean canImportModel(DataModel exportModel, DataModel matchedModel, ModelType modelType) { + if (!matchedModel) { + return false + } + exportModel = dataModelContentRepository.findWithContentById(exportModel.id) + //get full model contents + matchedModel = dataModelContentRepository.findWithContentById(matchedModel.id) + List matchedModelDataClasses = matchedModel.allDataClasses as List + List exportModelDataClasses = + modelType == ModelType.SOURCE ? exportModel.sourceDataFlows.dataClassComponents.sourceDataClasses.flatten() as List : + exportModel.targetDataFlows.dataClassComponents.targetDataClasses.flatten() as List + + boolean canImportDataClasses = + exportModelDataClasses.isEmpty() ? true : exportModelDataClasses.every {matchedModelDataClasses.find {dataClass -> dataClass.label == it.label}} + + List matchedDataElements = matchedModel.dataElements as List + + List exportDataElements = + modelType == ModelType.SOURCE ? exportModel.sourceDataFlows.dataClassComponents.dataElementComponents.sourceDataElements.flatten() as List : + exportModel.targetDataFlows.dataClassComponents.dataElementComponents.targetDataElements.flatten() as List + + boolean canImportDataElements = + exportDataElements.isEmpty() ? true : exportDataElements.every {matchedDataElements.find {dataElement -> dataElement.label == it.label}} + + return canImportDataClasses && canImportDataElements + } + + + protected List updateDataFlowPaths(List dataFlows) { + return dataFlows.each { + pathRepository.readParentItems(it) + it.updatePath() + updatePaths(it.source) + updatePaths(it.target) + it.dataClassComponents.each {dCC -> + updatePaths(dCC) + dCC.sourceDataClasses.each {dC -> updatePaths(dC) + } + dCC.targetDataClasses.each {tDC -> updatePaths(tDC) + } + dCC.dataElementComponents.each {dEC -> + updatePaths(dEC) + dEC.sourceDataElements.each {dE -> updatePaths(dE) + } + dEC.targetDataElements.each {tDE -> updatePaths(tDE) + } + } + } + } + } + + + protected static boolean subPathsMatch(ModelItem it, ModelItem modelItemSource) { + return getPathFrom(it.pathPrefix, it.path.pathString) == + getPathFrom(it.pathPrefix, modelItemSource.path.pathString) + } +} diff --git a/mauro-api/src/main/groovy/org/maurodata/service/path/PathService.groovy b/mauro-api/src/main/groovy/org/maurodata/service/path/PathService.groovy index 94bff6221..95b5bb042 100644 --- a/mauro-api/src/main/groovy/org/maurodata/service/path/PathService.groovy +++ b/mauro-api/src/main/groovy/org/maurodata/service/path/PathService.groovy @@ -18,7 +18,7 @@ import static org.maurodata.util.PathStringUtils.getCOLON import static org.maurodata.util.PathStringUtils.getDISCARD_AFTER_VERSION import static org.maurodata.util.PathStringUtils.getItemSubPath import static org.maurodata.util.PathStringUtils.getREMOVE_VERSION_DELIM -import static org.maurodata.util.PathStringUtils.getVersionFromPath +import static org.maurodata.util.PathStringUtils.getBranchNameFromPath import static org.maurodata.util.PathStringUtils.lastSubPath import static org.maurodata.util.PathStringUtils.splitBy @@ -80,7 +80,7 @@ class PathService implements AdministeredItemReader { protected AdministeredItem findResourceByPath(String domainType, String path) { String pathPrefix = getPathPrefixForDomainType(domainType) String domainPath = getItemSubPath(pathPrefix, path) - String versionString = getVersionFromPath(path) + String versionString = getBranchNameFromPath(path) return findItemForPath(domainType, domainPath, versionString, path) } diff --git a/mauro-api/src/test/groovy/org/maurodata/importexport/DataFlowImportExportIntegrationSpec.groovy b/mauro-api/src/test/groovy/org/maurodata/importexport/DataFlowImportExportIntegrationSpec.groovy index 12ee126c0..98e47642a 100644 --- a/mauro-api/src/test/groovy/org/maurodata/importexport/DataFlowImportExportIntegrationSpec.groovy +++ b/mauro-api/src/test/groovy/org/maurodata/importexport/DataFlowImportExportIntegrationSpec.groovy @@ -203,7 +203,7 @@ class DataFlowImportExportIntegrationSpec extends CommonDataSpec { response.summaryMetadata[0].summaryMetadataReports.size() == 1 response.summaryMetadata[0].summaryMetadataReports[0].id != summaryMetadataReportId - //proof new source/target rows created -DataClassComponent->dataClass, dataElementComponent->dataElement + //proof new source/target rows created -DataClassComponent->dataClass, dataElementComponent->dataElement and: HttpResponse httpResponse = dataClassComponentApi.deleteSource(sourceBranch.id, dataFlowResponse.items[0].id, dataClassComponentId, dataClassId1) then: diff --git a/mauro-api/src/test/groovy/org/maurodata/importexport/DataModelJsonImportExportIntegrationSpec.groovy b/mauro-api/src/test/groovy/org/maurodata/importexport/DataModelJsonImportExportIntegrationSpec.groovy index 94194ae21..848ff430a 100644 --- a/mauro-api/src/test/groovy/org/maurodata/importexport/DataModelJsonImportExportIntegrationSpec.groovy +++ b/mauro-api/src/test/groovy/org/maurodata/importexport/DataModelJsonImportExportIntegrationSpec.groovy @@ -5,7 +5,12 @@ import groovy.json.JsonSlurper import io.micronaut.http.HttpResponse import io.micronaut.http.MediaType import io.micronaut.http.client.multipart.MultipartBody +import io.micronaut.test.annotation.Sql import jakarta.inject.Singleton +import org.maurodata.domain.dataflow.DataClassComponent +import org.maurodata.domain.dataflow.DataElementComponent +import org.maurodata.domain.dataflow.DataFlow +import org.maurodata.domain.dataflow.Type import org.maurodata.domain.datamodel.DataModel import org.maurodata.domain.datamodel.DataType import org.maurodata.domain.facet.Annotation @@ -30,7 +35,10 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { @Shared UUID dataModelId - + @Shared + DataModel source + @Shared + DataModel importedSource @Shared UUID metadataId @@ -48,6 +56,24 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { @Shared DataType dataType + @Shared + DataType dataType2 + @Shared + UUID dataElementId1 + @Shared + UUID dataElementId2 + @Shared + UUID dataClassId1 + @Shared + UUID dataClassId2 + + @Shared + DataFlow dataFlow + @Shared + UUID dataClassComponentId + @Shared + UUID dataElementComponentId + JsonSlurper jsonSlurper = new JsonSlurper() @@ -74,13 +100,37 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { Annotation childResp = annotationApi.create("dataModel", dataModelId, annotationId, annotationPayload('child label', 'child description')) childAnnotationId = childResp.id + dataClassId1 = dataClassApi.create(dataModelId, dataClassPayload('dataClass label')).id + dataType = dataTypeApi.create(dataModelId, - new DataType(label: 'string', description: 'character string of variable length', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)) + new DataType(label: 'string', description: 'character string of variable length', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)) + + dataElementId1 = dataElementApi.create(dataModelId, dataClassId1, dataElementPayload('data element label', dataType)).id + + source = dataModelApi.create(folderId, dataModelPayload('source label')) + + //same label and branchName as source + importedSource = dataModelApi.create(folderId, dataModelPayload('source label')) + + dataFlow = dataFlowApi.create(dataModelId, new DataFlow( + label: 'test label', + description: 'dataflow payload description ', + source: source)) + + dataClassComponentId = dataClassComponentApi.create(dataModelId, dataFlow.id, + new DataClassComponent( + label: 'data class component test label')).id + dataClassComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataClassId1) + + dataElementComponentId = + dataElementComponentApi.create(dataModelId, dataFlow.id, dataClassComponentId, new DataElementComponent(label: 'test data element component')).id + + dataElementComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataElementComponentId, dataElementId1) + } void 'test get export data model - should export model'() { - when: - HttpResponse response = dataModelApi.exportModel(dataModelId, 'org.maurodata.plugin.exporter.json', 'JsonDataModelExporterPlugin', '4.0.0') + when:HttpResponse response = dataModelApi.exportModel(dataModelId, 'org.maurodata.plugin.exporter.json', 'JsonDataModelExporterPlugin', '4.0.0') then: response.body() @@ -89,6 +139,7 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { parsedJson.exportMetadata parsedJson.dataModel + parsedJson.dataModel.path parsedJson.dataModel.id == dataModelId.toString() parsedJson.dataModel.metadata.id == List.of(metadataId.toString()) parsedJson.dataModel.summaryMetadata.id == List.of(summaryMetadataId.toString()) @@ -98,6 +149,30 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { parsedJson.dataModel.annotations.childAnnotations[0].id == List.of(childAnnotationId.toString()) parsedJson.dataModel.dataTypes.id == List.of(dataType.id.toString()) parsedJson.dataModel.dataTypes.domainType == List.of(dataType.domainType) + + + parsedJson.dataModel.targetDataFlows + parsedJson.dataModel.targetDataFlows.size() == 1 + parsedJson.dataModel.targetDataFlows[0].path + parsedJson.dataModel.targetDataFlows[0].dataClassComponents + parsedJson.dataModel.targetDataFlows[0].dataClassComponents.size() == 1 + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].path + + !parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].sourceDataClasses + + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].targetDataClasses + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].targetDataClasses.size() == 1 + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].targetDataClasses[0].id == dataClassId1.toString() + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].targetDataClasses[0].path + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents.size() == 1 + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].id == dataElementComponentId.toString() + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].path + !parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].sourceDataElements + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements.size() == 1 + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements[0].id == dataElementId1.toString() + parsedJson.dataModel.targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements[0].path } @@ -108,15 +183,17 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { and: MultipartBody importRequest = MultipartBody.builder() - .addPart('folderId', folderId.toString()) - .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, response.body()) - .build() + .addPart('folderId', folderId.toString()) + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, response.body()) + .build() when: ListResponse dataModelResponse = dataModelApi.importModel(importRequest, 'org.maurodata.plugin.importer.json', 'JsonDataModelImporterPlugin', '4.0.0') then: dataModelResponse UUID importedDataModelId = dataModelResponse.items.first().id + importedDataModelId != dataModelId + when: DataModel importedDataModel = dataModelApi.show(importedDataModelId) @@ -133,7 +210,7 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { importedDataModel.annotations.size() == 1 UUID parentId = importedDataModel.annotations[0].id - List children = importedDataModel.annotations[0].childAnnotations + List children = importedDataModel.annotations[0].childAnnotations children.size() == 1 children.first().parentAnnotationId == parentId @@ -143,6 +220,40 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { importedDataTypes importedDataTypes.count == 1 importedDataTypes.items.domainType.first() == dataType.domainType + + //check imported dataModel includes dataFlows + when: + ListResponse dataFlowResponse = dataFlowApi.list(importedDataModelId, Type.TARGET) + + then: + dataFlowResponse + dataFlowResponse.items.size() == 1 + dataFlowResponse.items[0].target.id == importedDataModelId + + when: + DataFlow importedDataFlow = dataFlowApi.show(importedDataModelId, dataFlowResponse.items[0].id) + then: + importedDataFlow + importedDataFlow.id != dataFlow.id + + when: + ListResponse importedDataClassComponents = dataClassComponentApi.list(importedDataModel.id, importedDataFlow.id) + then: + importedDataClassComponents + importedDataClassComponents.items.size() == 1 + + when: + DataClassComponent importedDataClassComponent = dataClassComponentApi.show(importedDataModel.id, importedDataFlow.id, importedDataClassComponents.items[0].id) + then: + importedDataClassComponent + importedDataClassComponent.id != dataClassComponentId + + when: + ListResponse importedDataElementComponents = dataElementComponentApi.list(importedDataModel.id, importedDataFlow.id, importedDataClassComponent.id) + then: + importedDataElementComponents + importedDataElementComponents.items.size() == 1 + importedDataElementComponents.items[0].id != dataElementComponentId } // TODO: Is this used, or could it be? @@ -170,5 +281,4 @@ class DataModelJsonImportExportIntegrationSpec extends CommonDataSpec { response.items[0].id } - } diff --git a/mauro-api/src/test/groovy/org/maurodata/importexport/FolderJsonImportExportIntegrationSpec.groovy b/mauro-api/src/test/groovy/org/maurodata/importexport/FolderJsonImportExportIntegrationSpec.groovy index 58af4f1f0..969e5b131 100644 --- a/mauro-api/src/test/groovy/org/maurodata/importexport/FolderJsonImportExportIntegrationSpec.groovy +++ b/mauro-api/src/test/groovy/org/maurodata/importexport/FolderJsonImportExportIntegrationSpec.groovy @@ -1,5 +1,15 @@ package org.maurodata.importexport +import groovy.json.JsonSlurper +import io.micronaut.http.HttpResponse +import io.micronaut.http.MediaType +import io.micronaut.http.client.multipart.MultipartBody +import io.micronaut.test.annotation.Sql +import jakarta.inject.Singleton +import org.maurodata.domain.dataflow.DataClassComponent +import org.maurodata.domain.dataflow.DataElementComponent +import org.maurodata.domain.dataflow.DataFlow +import org.maurodata.domain.dataflow.Type import org.maurodata.domain.datamodel.DataClass import org.maurodata.domain.datamodel.DataModel import org.maurodata.domain.datamodel.DataType @@ -16,30 +26,35 @@ import org.maurodata.domain.terminology.Terminology import org.maurodata.persistence.ContainerizedTest import org.maurodata.testing.CommonDataSpec import org.maurodata.web.ListResponse - -import groovy.json.JsonSlurper -import io.micronaut.http.HttpResponse -import io.micronaut.http.MediaType -import io.micronaut.http.client.multipart.MultipartBody -import io.micronaut.test.annotation.Sql -import jakarta.inject.Singleton +import spock.lang.Ignore import spock.lang.Shared @ContainerizedTest @Singleton -@Sql(scripts = ["classpath:sql/tear-down-annotation.sql", "classpath:sql/tear-down-metadata.sql", - "classpath:sql/tear-down-summary-metadata.sql", "classpath:sql/tear-down-datamodel.sql", - "classpath:sql/tear-down.sql", "classpath:sql/tear-down-folder.sql"], phase = Sql.Phase.AFTER_EACH) +@Sql(scripts = [ + "classpath:sql/tear-down-annotation.sql", + "classpath:sql/tear-down-summary-metadata.sql", + "classpath:sql/tear-down-metadata.sql", + "classpath:sql/tear-down-dataflow.sql", + "classpath:sql/tear-down-dataelement.sql", + "classpath:sql/tear-down-datamodel.sql", + "classpath:sql/tear-down.sql", + "classpath:sql/tear-down-folder.sql"], phase = Sql.Phase.AFTER_EACH) class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { + static final String EXPORTER_NAMESPACE = 'org.maurodata.plugin.exporter.json' + static final String FOLDER_EXPORTER_NAME = 'JsonFolderExporterPlugin' + static final String EXPORTER_VERSION = '4.0.0' + static final String IMPORTER_NAMESPACE = 'org.maurodata.plugin.importer.json' + static final String FOLDER_IMPORTER_NAME = 'JsonFolderImporterPlugin' + static final String IMPORTER_VERSION = '4.0.0' + @Shared UUID folderId @Shared UUID dataModelId - @Shared - UUID codeSetId JsonSlurper jsonSlurper = new JsonSlurper() @@ -48,6 +63,7 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { dataModelId = dataModelApi.create(folderId, new DataModel(label: 'Test data model')).id } + void 'create folder, dataModels, metadata, summaryMetadata, summaryMetadataReports, annotations, terminology, codeset and export'() { given: UUID dataClass1Id = dataClassApi.create(dataModelId, new DataClass(label: 'TEST-1', description: 'first data class')).id @@ -64,7 +80,6 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { Annotation childAnnotation = annotationApi.create("folder", folderId, annotation.id, annotationPayload('childLabel', 'child-description')) CodeSet codeSet = codeSetApi.create(folderId, codeSet()) - codeSetId = codeSet.id Terminology terminology = terminologyApi.create(folderId, terminologyPayload()) @@ -73,10 +88,9 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { Term term = termApi.create(terminology.id, term()) when: - HttpResponse exportResponse = folderApi.exportModel(folderId, 'org.maurodata.plugin.exporter.json', 'JsonFolderExporterPlugin', '4.0.0') + HttpResponse exportResponse = folderApi.exportModel(folderId, EXPORTER_NAMESPACE, FOLDER_EXPORTER_NAME, EXPORTER_VERSION) then: - exportResponse.body() Map parsedJson = jsonSlurper.parseText(new String(exportResponse.body())) as Map parsedJson.exportMetadata parsedJson.folder @@ -119,6 +133,7 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { parsedJson.folder.codeSets[0].id == codeSet.id.toString() } + void 'get export folder -should export folder, with nested data and deep nesting of child folders'() { given: UUID childFolderId = folderApi.create(folderId, new Folder(label: 'Test child folder 1st level')).id @@ -132,7 +147,7 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { new DataType(label: 'Test data type childData Model', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)).id when: - HttpResponse exportResponse = folderApi.exportModel(folderId, 'org.maurodata.plugin.exporter.json', 'JsonFolderExporterPlugin', '4.0.0') + HttpResponse exportResponse = folderApi.exportModel(folderId, EXPORTER_NAMESPACE, FOLDER_EXPORTER_NAME, EXPORTER_VERSION) then: exportResponse.body() @@ -191,7 +206,7 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { targetTerm : new Term(id: targetTerm.id))) - HttpResponse exportResponse = folderApi.exportModel(folderId, 'org.maurodata.plugin.exporter.json', 'JsonFolderExporterPlugin', '4.0.0') + HttpResponse exportResponse = folderApi.exportModel(folderId, EXPORTER_NAMESPACE, FOLDER_EXPORTER_NAME, EXPORTER_VERSION) MultipartBody importRequest = MultipartBody.builder() @@ -200,7 +215,7 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { .build() when: - ListResponse response = folderApi.importModel(importRequest, 'org.maurodata.plugin.importer.json', 'JsonFolderImporterPlugin', '4.0.0') + ListResponse response = folderApi.importModel(importRequest, IMPORTER_NAMESPACE, FOLDER_IMPORTER_NAME, IMPORTER_VERSION) then: response @@ -208,10 +223,15 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { when: Folder importedFolder = folderApi.show(importedFolderId) - then: importedFolder + when: + ListResponse listResponse = folderApi.listAll() + then: + listResponse.items.size() == 4 + importedFolder.id in listResponse.items.id + when: ListResponse importedDataModelListResponse = dataModelApi.list(importedFolderId) then: @@ -329,6 +349,9 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { importedTermRelationship.items[0].sourceTerm.code == 'source code' importedTermRelationship.items[0].targetTerm.code == 'target code' importedTermRelationship.items[0].relationshipType.id == importedTermRelationshipTypeId + + + } @@ -368,8 +391,7 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { .build() when: - ListResponse response = folderApi.importModel(importRequest, 'org.maurodata.plugin.importer.json', 'JsonFolderImporterPlugin', '4.0.0') - + ListResponse response = folderApi.importModel(importRequest, IMPORTER_NAMESPACE, FOLDER_IMPORTER_NAME, IMPORTER_VERSION) then: response UUID importedFolderId = response.items.first().id @@ -508,7 +530,8 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { .addPart('folderId', folderId.toString()) .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, objectMapper.writeValueAsBytes(export)) .build() - ListResponse response = folderApi.importModel(importRequest, 'org.maurodata.plugin.importer.json', 'JsonFolderImporterPlugin', '4.0.0') + ListResponse response = folderApi.importModel(importRequest, IMPORTER_NAMESPACE, FOLDER_IMPORTER_NAME, IMPORTER_VERSION) + UUID importedFolderId = response.items.first().id then: @@ -578,4 +601,209 @@ class FolderJsonImportExportIntegrationSpec extends CommonDataSpec { importedTermRelationships.items.first().targetTerm.code == 'TEST' importedTermRelationships.items.first().relationshipType.label == 'TEST' } + + + void 'import folder with datamodel and dataflow -dataflow item not imported'() { + given: + UUID dataClass1Id = dataClassApi.create(dataModelId, new DataClass(label: 'TEST-1', description: 'first data class')).id + DataType dataType = dataTypeApi.create(dataModelId, new DataType(label: 'Test data type', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)) + UUID dataElementId1 = dataElementApi.create(dataModelId, dataClass1Id, dataElementPayload('dataElement1 label', dataType)).id + + DataModel source = dataModelApi.create(folderId, new DataModel(label: 'Test data model source')) + + DataFlow dataFlow = dataFlowApi.create(dataModelId, new DataFlow( + label: 'test label', + description: 'dataflow payload description ', + source: source)) + + UUID dataClassComponentId = dataClassComponentApi.create(dataModelId, dataFlow.id, + new DataClassComponent( + label: 'data class component test label')).id + dataClassComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataClass1Id) + + UUID dataElementComponentId = + dataElementComponentApi.create(dataModelId, dataFlow.id, dataClassComponentId, new DataElementComponent(label: 'test data element component')).id + + dataElementComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataElementComponentId, dataElementId1) + + when: + HttpResponse exportResponse = folderApi.exportModel(folderId, 'org.maurodata.plugin.exporter.json', 'JsonFolderExporterPlugin', '4.0.0') + + then: + exportResponse + Map parsedJson = jsonSlurper.parseText(new String(exportResponse.body())) as Map + parsedJson.folder.dataModels.size() == 2 + parsedJson.folder.dataModels[0].targetDataFlows.size() == 1 + parsedJson.folder.dataModels[0].targetDataFlows[0].dataClassComponents.size() == 1 + parsedJson.folder.dataModels[0].targetDataFlows[0].dataClassComponents[0].dataElementComponents.size() == 1 + parsedJson.folder.dataModels[0].targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements + !parsedJson.folder.dataModels[0].sourceDataFlows + + !parsedJson.folder.dataModels[1].targetDataFlows + parsedJson.folder.dataModels[1].sourceDataFlows.size() == 1 + parsedJson.folder.dataModels[1].sourceDataFlows[0].dataClassComponents.size() == 1 + parsedJson.folder.dataModels[1].sourceDataFlows[0].dataClassComponents[0].dataElementComponents.size() == 1 + parsedJson.folder.dataModels[1].sourceDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements.size() == 1 + + + MultipartBody importRequest = MultipartBody.builder() + .addPart('folderId', folderId.toString()) + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, exportResponse.body()) + .build() + + when: + ListResponse importResponse = folderApi.importModel(importRequest, IMPORTER_NAMESPACE, FOLDER_IMPORTER_NAME, IMPORTER_VERSION) + + then: + importResponse + importResponse.items.size() == 1 + + UUID importedFolderId = importResponse.items[0].id + importedFolderId != folderId + + when: + ListResponse originalModels = dataModelApi.list(folderId) + + then: + originalModels.items.size() == 2 + + when: + ListResponse dataModels = dataModelApi.list(importedFolderId) + + then: + dataModels.items.size() == 2 + + DataModel importedTargetDataModel = dataModels.items.find {!it.path.toString().contains('source') && it.id != source.id} + when: + ListResponse dataFlowListResponse = dataFlowApi.list(importedTargetDataModel.id, Type.TARGET) + + then: + dataFlowListResponse + dataFlowListResponse.items.isEmpty() + } + + + void 'import folder with datamodel and dataflow -happy path'() { + given: + UUID dataClass1Id = dataClassApi.create(dataModelId, new DataClass(label: 'TEST-1', description: 'first data class')).id + DataType dataType = dataTypeApi.create(dataModelId, new DataType(label: 'Test data type', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)) + UUID dataElementId1 = dataElementApi.create(dataModelId, dataClass1Id, dataElementPayload('dataElement1 label', dataType)).id + + DataModel source = dataModelApi.create(folderId, new DataModel(label: 'Test data model source')) + DataModel copySource = dataModelApi.create(folderId, new DataModel(label: 'Test data model source')) + + DataFlow dataFlow = dataFlowApi.create(dataModelId, new DataFlow( + label: 'test label', + description: 'dataflow payload description ', + source: source)) + + UUID dataClassComponentId = dataClassComponentApi.create(dataModelId, dataFlow.id, + new DataClassComponent( + label: 'data class component test label')).id + dataClassComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataClass1Id) + + UUID dataElementComponentId = + dataElementComponentApi.create(dataModelId, dataFlow.id, dataClassComponentId, new DataElementComponent(label: 'test data element component')).id + + dataElementComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataElementComponentId, dataElementId1) + + when: + HttpResponse exportResponse = folderApi.exportModel(folderId, 'org.maurodata.plugin.exporter.json', 'JsonFolderExporterPlugin', '4.0.0') + + then: + exportResponse + Map parsedJson = jsonSlurper.parseText(new String(exportResponse.body())) as Map + parsedJson.folder.dataModels.size() == 3 + + MultipartBody importRequest = MultipartBody.builder() + .addPart('folderId', folderId.toString()) + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, exportResponse.body()) + .build() + + when: + ListResponse importResponse = folderApi.importModel(importRequest, IMPORTER_NAMESPACE, FOLDER_IMPORTER_NAME, IMPORTER_VERSION) + + then: + importResponse + importResponse.items.size() == 1 + + UUID importedFolderId = importResponse.items[0].id + importedFolderId != folderId + + when: + ListResponse originalModels = dataModelApi.list(folderId) + + then: + originalModels.items.size() == 3 + + when: + ListResponse dataModels = dataModelApi.list(importedFolderId) + + then: + dataModels.items.size() == 3 + + DataModel importedTargetDataModel = dataModels.items.find {!it.path.toString().contains('source')} + when: + ListResponse dataFlowListResponse = dataFlowApi.list(importedTargetDataModel.id, Type.TARGET) + + then: + dataFlowListResponse + !dataFlowListResponse.items.isEmpty() + + DataFlow importedDataFlow = dataFlowListResponse.items[0] + importedDataFlow.target.id != dataModelId + importedDataFlow.source.id != source.id + + when: + ListResponse dataClassComponents = dataClassComponentApi.list(importedTargetDataModel.id, importedDataFlow.id) + + then: + dataClassComponents + dataClassComponents.items.size() == 1 + + when: + DataClassComponent dataClassComponent = dataClassComponentApi.show(importedTargetDataModel.id, importedDataFlow.id, dataClassComponents.items[0].id) + + then: + dataClassComponent + !dataClassComponent.sourceDataClasses + dataClassComponent.targetDataClasses.size() == 1 + dataClassComponent.targetDataClasses[0].id != dataClass1Id + + + when: + ListResponse dataElementComponentList = + dataElementComponentApi.list(importedTargetDataModel.id, importedDataFlow.id, dataClassComponents.items[0].id) + + then: + dataElementComponentList.items.size() == 1 + dataElementComponentList.items[0].id != dataElementId1 + + when: + DataElementComponent dataElementComponent = + dataElementComponentApi.show(importedTargetDataModel.id, importedDataFlow.id, dataClassComponents.items[0].id, dataElementComponentList.items[0].id) + + then: + dataElementComponent + !dataElementComponent.sourceDataElements + dataElementComponent.targetDataElements.size() == 1 + dataElementComponent.targetDataElements[0].id != dataElementId1 + } + + void 'test import folder without folder_id in params -should import as root folder'() { + given: + HttpResponse exportResponse = folderApi.exportModel(folderId, EXPORTER_NAMESPACE, FOLDER_EXPORTER_NAME, EXPORTER_VERSION) + + MultipartBody importRequest = MultipartBody.builder() + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, exportResponse.body()) + .build() + + when: + ListResponse importResponse = folderApi.importModel(importRequest, IMPORTER_NAMESPACE, FOLDER_IMPORTER_NAME, IMPORTER_VERSION) + + then: + importResponse + importResponse.items.size() == 1 + !importResponse.items[0].parent + } } diff --git a/mauro-api/src/test/groovy/org/maurodata/importexport/VersionedFolderJsonImportExportIntegrationSpec.groovy b/mauro-api/src/test/groovy/org/maurodata/importexport/VersionedFolderJsonImportExportIntegrationSpec.groovy new file mode 100644 index 000000000..231a05fd2 --- /dev/null +++ b/mauro-api/src/test/groovy/org/maurodata/importexport/VersionedFolderJsonImportExportIntegrationSpec.groovy @@ -0,0 +1,756 @@ +package org.maurodata.importexport + +import groovy.json.JsonSlurper +import io.micronaut.http.HttpResponse +import io.micronaut.http.MediaType +import io.micronaut.http.client.multipart.MultipartBody +import io.micronaut.test.annotation.Sql +import jakarta.inject.Singleton +import org.maurodata.FieldConstants +import org.maurodata.domain.dataflow.DataClassComponent +import org.maurodata.domain.dataflow.DataElementComponent +import org.maurodata.domain.dataflow.DataFlow +import org.maurodata.domain.dataflow.Type +import org.maurodata.domain.datamodel.DataClass +import org.maurodata.domain.datamodel.DataModel +import org.maurodata.domain.datamodel.DataType +import org.maurodata.domain.facet.Annotation +import org.maurodata.domain.facet.Metadata +import org.maurodata.domain.facet.SummaryMetadata +import org.maurodata.domain.facet.SummaryMetadataReport +import org.maurodata.domain.folder.Folder +import org.maurodata.domain.terminology.CodeSet +import org.maurodata.domain.terminology.Term +import org.maurodata.domain.terminology.TermRelationship +import org.maurodata.domain.terminology.TermRelationshipType +import org.maurodata.domain.terminology.Terminology +import org.maurodata.persistence.ContainerizedTest +import org.maurodata.testing.CommonDataSpec +import org.maurodata.web.ListResponse +import spock.lang.Shared + +@ContainerizedTest +@Singleton +@Sql(scripts = [ + "classpath:sql/tear-down-annotation.sql", + "classpath:sql/tear-down-summary-metadata.sql", + "classpath:sql/tear-down-metadata.sql", + "classpath:sql/tear-down-dataflow.sql", + "classpath:sql/tear-down-dataelement.sql", + "classpath:sql/tear-down-datamodel.sql", + "classpath:sql/tear-down.sql", + "classpath:sql/tear-down-folder.sql"], phase = Sql.Phase.AFTER_EACH) +class VersionedFolderJsonImportExportIntegrationSpec extends CommonDataSpec { + + @Shared + UUID folderId + + @Shared + UUID dataModelId + + + JsonSlurper jsonSlurper = new JsonSlurper() + + void setup() { + folderId = versionedFolderApi.create(new Folder(label: 'VersionedFolder top level')).id + dataModelId = dataModelApi.create(folderId, new DataModel(label: 'Test data model')).id + } + + + void 'create versionedFolder, dataModels, metadata, summaryMetadata, summaryMetadataReports, annotations, terminology, codeset and export'() { + given: + UUID dataClass1Id = dataClassApi.create(dataModelId, new DataClass(label: 'TEST-1', description: 'first data class')).id + UUID dataClass2Id = dataClassApi.create(dataModelId, new DataClass(label: 'TEST-2', description: 'second data class')).id + UUID dataTypeId = dataTypeApi.create(dataModelId, new DataType(label: 'Test data type', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)).id + + Metadata metadataResponse = metadataApi.create(FieldConstants.VERSIONED_FOLDER, folderId, metadataPayload()) + + SummaryMetadata summaryMetadataResponse = summaryMetadataApi.create(FieldConstants.VERSIONED_FOLDER, folderId, summaryMetadataPayload()) + + SummaryMetadataReport reportResponse = summaryMetadataReportApi.create(FieldConstants.VERSIONED_FOLDER, folderId, summaryMetadataResponse.id, summaryMetadataReport()) + + Annotation annotation = annotationApi.create(FieldConstants.VERSIONED_FOLDER, folderId, annotationPayload()) + Annotation childAnnotation = annotationApi.create(FieldConstants.VERSIONED_FOLDER, folderId, annotation.id, annotationPayload('childLabel', 'child-description')) + + CodeSet codeSet = codeSetApi.create(folderId, codeSet()) + // codeSetId = codeSet.id + + Terminology terminology = terminologyApi.create(folderId, terminologyPayload()) + + TermRelationshipType termRelationshipType = termRelationshipTypeApi.create(terminology.id, termRelationshipType()) + + Term term = termApi.create(terminology.id, term()) + + when: + HttpResponse exportResponse = + folderApi + .exportModel(folderId, FolderJsonImportExportIntegrationSpec.EXPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_EXPORTER_NAME, + FolderJsonImportExportIntegrationSpec.EXPORTER_VERSION) + + then: + Map parsedJson = jsonSlurper.parseText(new String(exportResponse.body())) as Map + parsedJson.exportMetadata + parsedJson.folder + parsedJson.folder.dataModels.size() == 1 + parsedJson.folder.dataModels[0].id == dataModelId.toString() + parsedJson.folder.dataModels[0].label == 'Test data model' + parsedJson.folder.dataModels[0].dataTypes.size() == 1 + parsedJson.folder.dataModels[0].dataTypes[0].label == 'Test data type' + parsedJson.folder.dataModels[0].dataTypes[0].id == dataTypeId.toString() + parsedJson.folder.dataModels[0].dataClasses.size() == 2 + List dataClasses = parsedJson.folder.dataModels[0].dataClasses + dataClasses.label.sort().collect {it.toString()} == ['TEST-1', 'TEST-2'] + dataClasses.id.sort().collect {it.toString()} == [dataClass1Id.toString(), dataClass2Id.toString()].sort() + + parsedJson.folder.metadata.size() == 1 + parsedJson.folder.metadata[0].id == metadataResponse.id.toString() + parsedJson.folder.summaryMetadata.size() == 1 + parsedJson.folder.summaryMetadata[0].id == summaryMetadataResponse.id.toString() + parsedJson.folder.summaryMetadata[0].summaryMetadataReports.size() == 1 + parsedJson.folder.summaryMetadata[0].summaryMetadataReports[0].id == reportResponse.id.toString() + + parsedJson.folder.annotations.size() == 1 + parsedJson.folder.annotations[0].id == annotation.id.toString() + List childAnnotations = parsedJson.folder.annotations[0].childAnnotations + childAnnotations.size() == 1 + childAnnotations[0].id == childAnnotation.id.toString() + + parsedJson.folder.terminologies.size() == 1 + parsedJson.folder.terminologies[0].id == terminology.id.toString() + + List termRelationshipTypes = parsedJson.folder.terminologies[0].termRelationshipTypes + termRelationshipTypes.size() == 1 + termRelationshipTypes[0].id == termRelationshipType.id.toString() + List terminologies = parsedJson.folder.terminologies + terminologies.size() == 1 + terminologies[0].terms.size() == 1 + terminologies[0].terms[0].id == term.id.toString() + + parsedJson.folder.codeSets.size() == 1 + parsedJson.folder.codeSets[0].id == codeSet.id.toString() + } + + void 'get export folder -should export folder, with nested data and deep nesting of child folders'() { + given: + UUID childFolderId = folderApi.create(folderId, new Folder(label: 'Test child folder 1st level')).id + UUID nestedChildFolderId = folderApi.create(childFolderId, new Folder(label: 'Test nested child 2nd level folder')).id + + and: + UUID childDataModelId = dataModelApi.create(childFolderId, new DataModel(label: 'Test child 1st level folder')).id + UUID nestedChildDataModelId = dataModelApi.create(nestedChildFolderId, new DataModel(label: 'Test nested child 2nd level folder')).id + + UUID childDataModelTypeId = dataTypeApi.create(childDataModelId, + new DataType(label: 'Test data type childData Model', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)).id + + when: + HttpResponse exportResponse = + folderApi + .exportModel(folderId, FolderJsonImportExportIntegrationSpec.EXPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_EXPORTER_NAME, + FolderJsonImportExportIntegrationSpec.EXPORTER_VERSION) + then: + exportResponse.body() + + Map parsedJson = jsonSlurper.parseText(new String(exportResponse.body())) as Map + parsedJson.exportMetadata + parsedJson.folder + parsedJson.folder.dataModels.size() == 1 + parsedJson.folder.dataModels[0].id == dataModelId.toString() + + parsedJson.folder.childFolders.size() == 1 + parsedJson.folder.childFolders[0].id == childFolderId.toString() + parsedJson.folder.childFolders[0].dataModels.size() == 1 + parsedJson.folder.childFolders[0].dataModels[0].id == childDataModelId.toString() + parsedJson.folder.childFolders[0].dataModels[0].dataTypes.size() == 1 + parsedJson.folder.childFolders[0].dataModels[0].dataTypes[0].id == childDataModelTypeId.toString() + + parsedJson.folder.childFolders[0].childFolders.size() == 1 + parsedJson.folder.childFolders[0].childFolders[0].id == nestedChildFolderId.toString() + parsedJson.folder.childFolders[0].childFolders[0].dataModels.size() == 1 + parsedJson.folder.childFolders[0].childFolders[0].dataModels[0].id == nestedChildDataModelId.toString() + } + + + void 'test consume export folders - should import'() { + given: + UUID dataClassId = dataClassApi.create(dataModelId, new DataClass(label: 'TEST-1', description: 'first data class')).id + UUID dataTypeId = dataTypeApi.create(dataModelId, new DataType(label: 'Test data type', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)).id + + UUID childFolderId = folderApi.create(folderId, new Folder(label: 'child folder')).id + + UUID childCodeSetId = codeSetApi.create(childFolderId, new CodeSet(label: 'codeset in child folder')).id + + Metadata metadataResponse = metadataApi.create("folder", folderId, metadataPayload()) + + SummaryMetadata summaryMetadataResponse = summaryMetadataApi.create("folder", folderId, summaryMetadataPayload()) + + SummaryMetadataReport reportResponse = summaryMetadataReportApi.create("folder", folderId, summaryMetadataResponse.id, summaryMetadataReport()) + + Annotation annotation = annotationApi.create("folder", folderId, annotationPayload()) + Annotation childAnnotation = annotationApi.create("folder", folderId, annotation.id, annotationPayload('childLabel', 'child-description')) + + CodeSet codeSet = codeSetApi.create(folderId, codeSet()) + + Terminology terminology = terminologyApi.create(folderId, terminologyPayload()) + + TermRelationshipType termRelationshipType = termRelationshipTypeApi.create(terminology.id, termRelationshipType()) + + Term sourceTerm = termApi.create(terminology.id, new Term(code: 'source code', definition: 'source term')) + + Term targetTerm = termApi.create(terminology.id, new Term(code: 'target code', definition: 'target term')) + + termRelationshipApi.create(terminology.id, + new TermRelationship( + relationshipType: new TermRelationshipType(id: termRelationshipType.id), + sourceTerm: new Term(id: sourceTerm.id), + targetTerm: new Term(id: targetTerm.id))) + + + HttpResponse exportResponse = + folderApi + .exportModel(folderId, FolderJsonImportExportIntegrationSpec.EXPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_EXPORTER_NAME, + FolderJsonImportExportIntegrationSpec.EXPORTER_VERSION) + + + MultipartBody importRequest = MultipartBody.builder() + .addPart('folderId', folderId.toString()) + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, exportResponse.body()) + .build() + + when: + ListResponse response = folderApi.importModel(importRequest, FolderJsonImportExportIntegrationSpec.IMPORTER_NAMESPACE, + FolderJsonImportExportIntegrationSpec.FOLDER_IMPORTER_NAME, + FolderJsonImportExportIntegrationSpec.IMPORTER_VERSION) + + then: + response + UUID importedFolderId = response.items.id.first() + + when: + Folder importedFolder = folderApi.show(importedFolderId) + then: + importedFolder + + when: + ListResponse listResponse = folderApi.listAll() + then: + listResponse.items.size() == 4 + importedFolder.id in listResponse.items.id + + when: + ListResponse importedDataModelListResponse = dataModelApi.list(importedFolderId) + then: + importedDataModelListResponse + importedDataModelListResponse.items.size() == 1 + importedDataModelListResponse.items[0].id != dataModelId + UUID importedDataModelId = importedDataModelListResponse.items[0].id + when: + ListResponse importedDataTypesListResponse = dataTypeApi.list(importedDataModelId) + then: + importedDataTypesListResponse + importedDataTypesListResponse.items.size() == 1 + importedDataTypesListResponse.items[0].id != dataTypeId + + when: + ListResponse importedDataClassesListResponse = dataClassApi.list(importedDataModelId) + then: + importedDataClassesListResponse.items.size() == 1 + importedDataClassesListResponse.items[0].label == 'TEST-1' + importedDataClassesListResponse.items[0].id != dataClassId + + when: + ListResponse importedMetadataResponse = metadataApi.list("folder", importedFolderId) + then: + importedMetadataResponse + importedMetadataResponse.items.size() == 1 + importedMetadataResponse.items[0].id != metadataResponse.id + + when: + ListResponse importedSummaryMetadataResponse = summaryMetadataApi.list("folder", importedFolderId) + then: + importedSummaryMetadataResponse + importedSummaryMetadataResponse.items.size() == 1 + importedSummaryMetadataResponse.items[0].id != summaryMetadataResponse.id + + when: + ListResponse importedReportResponse = + summaryMetadataReportApi.list("folder", importedFolderId, importedSummaryMetadataResponse.items[0].id) + then: + importedReportResponse + importedReportResponse.items.size() == 1 + importedReportResponse.items[0].id != reportResponse.id + + when: + ListResponse importedChildFolders = folderApi.list(importedFolderId) + then: + importedChildFolders + importedChildFolders.items.size() == 1 + UUID importedChildFolderId = importedChildFolders.items[0].id + + when: + ListResponse importedChildCodeSet = codeSetApi.list(importedChildFolderId) + then: + importedChildCodeSet + importedChildCodeSet.items.size() == 1 + importedChildCodeSet.items[0].id != childCodeSetId + + when: + ListResponse importedAnnotationResponse = annotationApi.list("folder", importedFolderId) + + then: + importedAnnotationResponse + importedAnnotationResponse.items.size() == 1 + UUID importedAnnotationId = importedAnnotationResponse.items[0].id + importedAnnotationResponse.items[0]?.id != annotation.id + importedAnnotationResponse.items[0]?.childAnnotations + importedAnnotationResponse.items[0]?.childAnnotations?.size() == 1 + importedAnnotationResponse.items[0]?.childAnnotations[0].parentAnnotationId == importedAnnotationId + importedAnnotationResponse.items[0]?.childAnnotations[0].id != childAnnotation.id + + when: + ListResponse importedCodeSetResponse = codeSetApi.list(importedFolderId) + + then: + importedCodeSetResponse + importedCodeSetResponse.items.size() == 1 + importedCodeSetResponse.items[0].id != codeSet.id + + when: + ListResponse importedTerminologyResponse = terminologyApi.list(importedFolderId) + + then: + importedTerminologyResponse + importedTerminologyResponse.items.size() == 1 + UUID importedTerminologyId = importedTerminologyResponse.items[0].id + importedTerminologyId != terminology.id + + when: + ListResponse importedTermRelationshipTypeResponse = + termRelationshipTypeApi.list(importedTerminologyId) + + then: + importedTermRelationshipTypeResponse + importedTermRelationshipTypeResponse.items.size() == 1 + UUID importedTermRelationshipTypeId = importedTermRelationshipTypeResponse.items[0].id + importedTermRelationshipTypeId != termRelationshipType.id + + when: + ListResponse importedTermsResponse = termApi.list(importedTerminologyId) + + then: + importedTermsResponse + importedTermsResponse.items.size() == 2 + + importedTermsResponse.items.code.sort() == ['source code', 'target code'] + importedTermsResponse.items.id.sort() != [sourceTerm.id, targetTerm.id] + + when: + ListResponse importedTermRelationship = + termRelationshipApi.list(importedTerminologyId) + then: + importedTermRelationship + importedTermRelationship.items.size() == 1 + importedTermRelationship.items[0].id != termRelationshipType.id + importedTermRelationship.items[0].sourceTerm.code == 'source code' + importedTermRelationship.items[0].targetTerm.code == 'target code' + importedTermRelationship.items[0].relationshipType.id == importedTermRelationshipTypeId + + + } + + + void 'test consume export folder- folder is not parent - should import'() { + given: + + UUID childFolderId = folderApi.create(folderId, new Folder(label: 'child folder')).id + + UUID childCodeSetId = codeSetApi.create(childFolderId, new CodeSet(label: 'codeset in child folder')).id + + Annotation annotation = annotationApi.create("folder", folderId, annotationPayload()) + Annotation childAnnotation = + annotationApi.create("folder", folderId, annotation.id, annotationPayload('childLabel', 'child-description')) + + CodeSet codeSet = codeSetApi.create(folderId, codeSet()) + + Terminology terminology = terminologyApi.create(folderId, terminologyPayload()) + + TermRelationshipType termRelationshipType = termRelationshipTypeApi.create(terminology.id, termRelationshipType()) + + Term sourceTerm = termApi.create(terminology.id, new Term(code: 'source code', definition: 'source term')) + + Term targetTerm = termApi.create(terminology.id, new Term(code: 'target code', definition: 'target term')) + + termRelationshipApi.create(terminology.id, + new TermRelationship( + relationshipType: new TermRelationshipType(id: termRelationshipType.id), + sourceTerm: new Term(id: sourceTerm.id), + targetTerm: new Term(id: targetTerm.id))) + + + HttpResponse exportResponse = + folderApi.exportModel(folderId, FolderJsonImportExportIntegrationSpec.EXPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_EXPORTER_NAME, + FolderJsonImportExportIntegrationSpec.EXPORTER_VERSION) + + MultipartBody importRequest = MultipartBody.builder() + .addPart('folderId', childFolderId.toString()) + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, exportResponse.body()) + .build() + + when: + ListResponse response = + folderApi.importModel(importRequest, FolderJsonImportExportIntegrationSpec.IMPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_IMPORTER_NAME, + FolderJsonImportExportIntegrationSpec.IMPORTER_VERSION) + then: + response + UUID importedFolderId = response.items.first().id + + when: + Folder importedFolder = folderApi.show(importedFolderId) + + then: + importedFolder + + + when: + ListResponse importedChildFolders = folderApi.list(importedFolderId) + then: + importedChildFolders + importedChildFolders.items.size() == 1 + UUID importedChildFolderId = importedChildFolders.items[0].id + + when: + ListResponse importedChildCodeSet = codeSetApi.list(importedChildFolderId) + then: + importedChildCodeSet + importedChildCodeSet.items.size() == 1 + importedChildCodeSet.items[0].id != childCodeSetId + + + when: + ListResponse importedAnnotationResponse = annotationApi.list("folder", importedFolderId) + + then: + importedAnnotationResponse + importedAnnotationResponse.items.size() == 1 + UUID importedAnnotationId = importedAnnotationResponse.items[0].id + importedAnnotationResponse.items[0]?.id != annotation.id + importedAnnotationResponse.items[0]?.childAnnotations + importedAnnotationResponse.items[0]?.childAnnotations?.size() == 1 + importedAnnotationResponse.items[0]?.childAnnotations[0].parentAnnotationId == importedAnnotationId + importedAnnotationResponse.items[0]?.childAnnotations[0].id != childAnnotation.id + + when: + ListResponse importedCodeSetResponse = codeSetApi.list(importedFolderId) + + then: + importedCodeSetResponse + importedCodeSetResponse.items.size() == 1 + importedCodeSetResponse.items[0].id != codeSet.id + + when: + ListResponse importedTerminologyResponse = terminologyApi.list(importedFolderId) + + then: + importedTerminologyResponse + importedTerminologyResponse.items.size() == 1 + UUID importedTerminologyId = importedTerminologyResponse.items[0].id + importedTerminologyId != terminology.id + + when: + ListResponse importedTermRelationshipTypeResponse = + termRelationshipTypeApi.list(importedTerminologyId) + + then: + importedTermRelationshipTypeResponse + importedTermRelationshipTypeResponse.items.size() == 1 + UUID importedTermRelationshipTypeId = importedTermRelationshipTypeResponse.items[0].id + importedTermRelationshipTypeId != termRelationshipType.id + + when: + ListResponse importedTermsResponse = termApi.list(importedTerminologyId) + + then: + importedTermsResponse + importedTermsResponse.items.size() == 2 + + importedTermsResponse.items.code.sort() == ['source code', 'target code'] + importedTermsResponse.items.id.sort() != [sourceTerm.id, targetTerm.id].sort() + + when: + ListResponse importedTermRelationship = termRelationshipApi.list(importedTerminologyId) + then: + importedTermRelationship + importedTermRelationship.items.size() == 1 + importedTermRelationship.items[0].id != termRelationshipType.id + importedTermRelationship.items[0].sourceTerm.code == 'source code' + importedTermRelationship.items[0].targetTerm.code == 'target code' + importedTermRelationship.items[0].relationshipType.id == importedTermRelationshipTypeId + } + + + void 'export and import folder with two terminologies with overlapping codes'() { + given: + // create two terminologies each with different Terms with code TEST + UUID folderId = folderApi.create(new Folder(label: 'Two terminologies folder')).id + UUID terminology1Id = terminologyApi.create(folderId, new Terminology(label: 'First Terminology')).id + UUID term1Id = termApi.create(terminology1Id, new Term(code: 'TEST', definition: 'first term')).id + UUID terminology2Id = terminologyApi.create(folderId, new Terminology(label: 'Second Terminology')).id + UUID term2Id = termApi.create(terminology2Id, new Term(code: 'TEST', definition: 'second term')).id + + // also create two different term relationship types with label TEST + UUID termRelationshipType1Id = termRelationshipTypeApi.create( + terminology1Id, new TermRelationshipType(label: 'TEST', childRelationship: true)).id + UUID termRelationshipType2Id = termRelationshipTypeApi.create( + terminology2Id, new TermRelationshipType(label: 'TEST', childRelationship: false)).id + termRelationshipApi.create(terminology1Id, new TermRelationship( + relationshipType: new TermRelationshipType(id: termRelationshipType1Id), + sourceTerm: new Term(id: term1Id), + targetTerm: new Term(id: term1Id) + )) + termRelationshipApi.create(terminology2Id, new TermRelationship( + relationshipType: new TermRelationshipType(id: termRelationshipType2Id), + sourceTerm: new Term(id: term2Id), + targetTerm: new Term(id: term2Id) + )) + + when: + HttpResponse exportResponse = folderApi.exportModel(folderId, 'org.maurodata.plugin.exporter.json', 'JsonFolderExporterPlugin', '4.0.0') + Map export = jsonSlurper.parseText(new String(exportResponse.body())) as Map + + then: + export.folder.terminologies.size() == 2 + export.folder.terminologies.find {it.label == 'First Terminology'}.terms.first().code == 'TEST' + export.folder.terminologies.find {it.label == 'First Terminology'}.terms.first().definition == 'first term' + export.folder.terminologies.find {it.label == 'Second Terminology'}.terms.first().code == 'TEST' + export.folder.terminologies.find {it.label == 'Second Terminology'}.terms.first().definition == 'second term' + export.folder.terminologies.find {it.label == 'First Terminology'}.termRelationshipTypes.first().label == 'TEST' + export.folder.terminologies.find {it.label == 'First Terminology'}.termRelationshipTypes.first().childRelationship == true + export.folder.terminologies.find {it.label == 'Second Terminology'}.termRelationshipTypes.first().label == 'TEST' + export.folder.terminologies.find {it.label == 'Second Terminology'}.termRelationshipTypes.first().childRelationship == false + export.folder.terminologies.find {it.label == 'First Terminology'}.termRelationships.first().sourceTerm == 'TEST' + export.folder.terminologies.find {it.label == 'First Terminology'}.termRelationships.first().targetTerm == 'TEST' + export.folder.terminologies.find {it.label == 'First Terminology'}.termRelationships.first().relationshipType == 'TEST' + export.folder.terminologies.find {it.label == 'Second Terminology'}.termRelationships.first().sourceTerm == 'TEST' + export.folder.terminologies.find {it.label == 'Second Terminology'}.termRelationships.first().targetTerm == 'TEST' + export.folder.terminologies.find {it.label == 'Second Terminology'}.termRelationships.first().relationshipType == 'TEST' + + when: + MultipartBody importRequest = MultipartBody.builder() + .addPart('folderId', folderId.toString()) + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, objectMapper.writeValueAsBytes(export)) + .build() + ListResponse response = + folderApi.importModel(importRequest, FolderJsonImportExportIntegrationSpec.IMPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_IMPORTER_NAME, + FolderJsonImportExportIntegrationSpec.IMPORTER_VERSION) + + UUID importedFolderId = response.items.first().id + + then: + response.count == 1 + response.items.size() == 1 + response.items.first().label == 'Two terminologies folder' + importedFolderId + + when: + ListResponse importedTerminologies = terminologyApi.list(importedFolderId) + UUID importedTerminology1Id = importedTerminologies.items.find {it.label == 'First Terminology'}.id + UUID importedTerminology2Id = importedTerminologies.items.find {it.label == 'Second Terminology'}.id + + then: + importedTerminologies.count == 2 + importedTerminologies.items.find {it.label == 'First Terminology'} + importedTerminologies.items.find {it.label == 'Second Terminology'} + + when: + ListResponse importedTerms = termApi.list(importedTerminology1Id) + + then: + importedTerms.count == 1 + importedTerms.items.first().code == 'TEST' + importedTerms.items.first().definition == 'first term' + + when: + importedTerms = termApi.list(importedTerminology2Id) + + then: + importedTerms.count == 1 + importedTerms.items.first().code == 'TEST' + importedTerms.items.first().definition == 'second term' + + when: + ListResponse importedTermRelationshipTypes = + termRelationshipTypeApi.list(importedTerminology1Id) + + then: + importedTermRelationshipTypes.count == 1 + importedTermRelationshipTypes.items.first().label == 'TEST' + importedTermRelationshipTypes.items.first().childRelationship == true + + when: + importedTermRelationshipTypes = termRelationshipTypeApi.list(importedTerminology2Id) + + then: + importedTermRelationshipTypes.count == 1 + importedTermRelationshipTypes.items.first().label == 'TEST' + importedTermRelationshipTypes.items.first().childRelationship == false + + when: + ListResponse importedTermRelationships = termRelationshipApi.list(importedTerminology1Id) + + then: + importedTermRelationships.count == 1 + importedTermRelationships.items.first().sourceTerm.code == 'TEST' + importedTermRelationships.items.first().targetTerm.code == 'TEST' + importedTermRelationships.items.first().relationshipType.label == 'TEST' + + when: + importedTermRelationships = termRelationshipApi.list(importedTerminology2Id) + + then: + importedTermRelationships.count == 1 + importedTermRelationships.items.first().sourceTerm.code == 'TEST' + importedTermRelationships.items.first().targetTerm.code == 'TEST' + importedTermRelationships.items.first().relationshipType.label == 'TEST' + } + + void 'test export and import folder with datamodel and dataflow '() { + given: + UUID dataClass1Id = dataClassApi.create(dataModelId, new DataClass(label: 'TEST-1', description: 'first data class')).id + DataType dataType = dataTypeApi.create(dataModelId, new DataType(label: 'Test data type', dataTypeKind: DataType.DataTypeKind.PRIMITIVE_TYPE)) + UUID dataElementId1 = dataElementApi.create(dataModelId, dataClass1Id, dataElementPayload('dataElement1 label', dataType)).id + + DataModel source = dataModelApi.create(folderId, new DataModel(label: 'Test data model source')) + // same label and branchName(version) + DataModel importSource = dataModelApi.create(folderId, new DataModel(label: 'Test data model source')) + + DataFlow dataFlow = dataFlowApi.create(dataModelId, new DataFlow( + label: 'test label', + description: 'dataflow payload description ', + source: source)) + + UUID dataClassComponentId = dataClassComponentApi.create(dataModelId, dataFlow.id, + new DataClassComponent( + label: 'data class component test label')).id + dataClassComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataClass1Id) + + UUID dataElementComponentId = + dataElementComponentApi.create(dataModelId, dataFlow.id, dataClassComponentId, new DataElementComponent(label: 'test data element component')).id + + dataElementComponentApi.updateTarget(dataModelId, dataFlow.id, dataClassComponentId, dataElementComponentId, dataElementId1) + + when: + HttpResponse exportResponse = folderApi.exportModel(folderId, FolderJsonImportExportIntegrationSpec.EXPORTER_NAMESPACE, + FolderJsonImportExportIntegrationSpec.FOLDER_EXPORTER_NAME, + FolderJsonImportExportIntegrationSpec.EXPORTER_VERSION) + + + then: + exportResponse + Map parsedJson = jsonSlurper.parseText(new String(exportResponse.body())) as Map + parsedJson.folder.dataModels.size() == 3 + parsedJson.folder.dataModels[0].targetDataFlows.size() == 1 + parsedJson.folder.dataModels[0].targetDataFlows[0].dataClassComponents.size() == 1 + parsedJson.folder.dataModels[0].targetDataFlows[0].dataClassComponents[0].dataElementComponents.size() == 1 + parsedJson.folder.dataModels[0].targetDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements + !parsedJson.folder.dataModels[0].sourceDataFlows + + !parsedJson.folder.dataModels[1].targetDataFlows + parsedJson.folder.dataModels[1].sourceDataFlows.size() == 1 + parsedJson.folder.dataModels[1].sourceDataFlows[0].dataClassComponents.size() == 1 + parsedJson.folder.dataModels[1].sourceDataFlows[0].dataClassComponents[0].dataElementComponents.size() == 1 + parsedJson.folder.dataModels[1].sourceDataFlows[0].dataClassComponents[0].dataElementComponents[0].targetDataElements.size() == 1 + + !parsedJson.folder.dataModels[2].targetDataFlows + !parsedJson.folder.dataModels[2].sourceDataFlows + + + MultipartBody importRequest = MultipartBody.builder() + .addPart('folderId', folderId.toString()) + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, exportResponse.body()) + .build() + + when: + ListResponse importResponse = folderApi.importModel(importRequest, FolderJsonImportExportIntegrationSpec.IMPORTER_NAMESPACE, + FolderJsonImportExportIntegrationSpec.FOLDER_IMPORTER_NAME, + FolderJsonImportExportIntegrationSpec.IMPORTER_VERSION) + + then: + importResponse + importResponse.items.size() == 1 + + UUID importedFolderId = importResponse.items[0].id + importedFolderId != folderId + + when: + ListResponse dataModels = dataModelApi.list(importedFolderId) + + then: + dataModels.items.size() == 3 + + UUID importedDataModelId = dataModels.items.find {!it.path.toString().contains('source')}.id + when: + ListResponse dataFlowListResponse = dataFlowApi.list(importedDataModelId, Type.TARGET) + + then: + dataFlowListResponse + dataFlowListResponse.items.size() == 1 + DataFlow importedDataFlow = dataFlowListResponse.items[0] + importedDataFlow.target.id != dataModelId + importedDataFlow.source.id == importSource.id + + when: + ListResponse dataClassComponents = dataClassComponentApi.list(importedDataModelId, importedDataFlow.id) + + then: + dataClassComponents + dataClassComponents.items.size() == 1 + + when: + DataClassComponent dataClassComponent = dataClassComponentApi.show(importedDataModelId, importedDataFlow.id, dataClassComponents.items[0].id) + + then: + dataClassComponent + !dataClassComponent.sourceDataClasses + dataClassComponent.targetDataClasses.size() == 1 + dataClassComponent.targetDataClasses.size() == 1 + dataClassComponent.targetDataClasses[0].id != dataClass1Id + + when: + ListResponse dataElementComponentList = dataElementComponentApi.list(importedDataModelId, importedDataFlow.id, dataClassComponents.items[0].id) + + then: + dataElementComponentList.items.size() == 1 + dataElementComponentList.items[0].id != dataElementId1 + + when: + DataElementComponent dataElementComponent = + dataElementComponentApi.show(importedDataModelId, importedDataFlow.id, dataClassComponents.items[0].id, dataElementComponentList.items[0].id) + + then: + dataElementComponent + !dataElementComponent.sourceDataElements + dataElementComponent.targetDataElements.size() == 1 + dataElementComponent.targetDataElements[0].id != dataElementId1 + } + + + void 'test import folder without folder_id in params -should import as root folder'() { + given: + HttpResponse exportResponse = + folderApi.exportModel(folderId, FolderJsonImportExportIntegrationSpec.EXPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_EXPORTER_NAME, + FolderJsonImportExportIntegrationSpec.EXPORTER_VERSION) + + MultipartBody importRequest = MultipartBody.builder() + .addPart('importFile', 'file.json', MediaType.APPLICATION_JSON_TYPE, exportResponse.body()) + .build() + + when: + ListResponse importResponse = + folderApi.importModel(importRequest, FolderJsonImportExportIntegrationSpec.IMPORTER_NAMESPACE, FolderJsonImportExportIntegrationSpec.FOLDER_IMPORTER_NAME, + FolderJsonImportExportIntegrationSpec.IMPORTER_VERSION) + + then: + importResponse + importResponse.items.size() == 1 + !importResponse.items[0].parent + } +} diff --git a/mauro-api/src/test/groovy/org/maurodata/testing/CommonDataSpec.groovy b/mauro-api/src/test/groovy/org/maurodata/testing/CommonDataSpec.groovy index dd442fcbf..8a89e8563 100644 --- a/mauro-api/src/test/groovy/org/maurodata/testing/CommonDataSpec.groovy +++ b/mauro-api/src/test/groovy/org/maurodata/testing/CommonDataSpec.groovy @@ -28,6 +28,7 @@ import org.maurodata.api.federation.PublishApi import org.maurodata.api.federation.SubscribedCatalogueApi import org.maurodata.api.federation.SubscribedModelApi import org.maurodata.api.folder.FolderApi +import org.maurodata.api.folder.VersionedFolderApi import org.maurodata.api.importer.ImporterApi import org.maurodata.api.path.PathApi import org.maurodata.api.profile.ProfileApi @@ -52,7 +53,6 @@ import org.maurodata.domain.datamodel.DataClass import org.maurodata.domain.datamodel.DataElement import org.maurodata.domain.datamodel.DataModel import org.maurodata.domain.datamodel.DataType -import org.maurodata.domain.datamodel.EnumerationValue import org.maurodata.domain.facet.Annotation import org.maurodata.domain.facet.Metadata import org.maurodata.domain.facet.ReferenceFile @@ -155,6 +155,7 @@ class CommonDataSpec extends Specification { @Shared @Inject PublishApi publishApi @Shared @Inject SemanticLinksApi semanticLinksApi @Shared @Inject PathApi pathApi + @Shared @Inject VersionedFolderApi versionedFolderApi @Inject SessionHandlerClientFilter sessionHandlerClientFilter diff --git a/mauro-client/src/main/groovy/org/maurodata/api/Paths.groovy b/mauro-client/src/main/groovy/org/maurodata/api/Paths.groovy index e16d91629..9ec5a7dba 100644 --- a/mauro-client/src/main/groovy/org/maurodata/api/Paths.groovy +++ b/mauro-client/src/main/groovy/org/maurodata/api/Paths.groovy @@ -254,14 +254,15 @@ interface Paths { String FOLDER_EXPORTERS = '/api/folders/providers/exporters' + /* * VersionedFolderApi */ String VERSIONED_FOLDER_LIST = '/api/versionedFolders' String VERSIONED_FOLDER_ID = '/api/versionedFolders/{id}' - String CHILD_VERSIONED_FOLDER_LIST = '/api/folders/{parentId}/versionedFolders' - String FOLDER_CHILD_VERSIONED_FOLDER_ID = '/api/folders/{parentId}/versionedFolders/{id}' + String CHILD_VERSIONED_FOLDER_LIST = '/api/versionedFolders/{parentId}/folders' + String FOLDER_CHILD_VERSIONED_FOLDER_ID = '/api/versionedFolders/{parentId}/folders/{id}' String VERSIONED_FOLDER_FINALISE = '/api/versionedFolders/{id}/finalise' String VERSIONED_FOLDER_NEW_BRANCH_MODEL_VERSION = '/api/versionedFolders/{id}/newBranchModelVersion' @@ -279,7 +280,8 @@ interface Paths { String VERSIONED_FOLDER_COMMON_ANCESTOR = '/api/versionedFolders/{id}/commonAncestor/{other_model_id}' String VERSIONED_FOLDER_MERGE_DIFF = '/api/versionedFolders/{id}/mergeDiff/{otherId}' String VERSIONED_FOLDER_MERGE_INTO = '/api/versionedFolders/{id}/mergeInto/{otherId}' - + String VERSIONED_FOLDER_IMPORT = '/api/versionedFolders/import/{namespace}/{name}{/version}' + String VERSIONED_FOLDER_EXPORT ='/api/versionedFolders/{id}/export{/namespace}{/name}{/version}' /* * ImporterApi */ diff --git a/mauro-client/src/main/groovy/org/maurodata/api/folder/VersionedFolderApi.groovy b/mauro-client/src/main/groovy/org/maurodata/api/folder/VersionedFolderApi.groovy index 6c8dbacfe..d292c3c12 100644 --- a/mauro-client/src/main/groovy/org/maurodata/api/folder/VersionedFolderApi.groovy +++ b/mauro-client/src/main/groovy/org/maurodata/api/folder/VersionedFolderApi.groovy @@ -1,5 +1,7 @@ package org.maurodata.api.folder +import io.micronaut.http.MediaType +import io.micronaut.http.client.multipart.MultipartBody import org.maurodata.api.model.MergeIntoDTO import io.micronaut.core.annotation.NonNull @@ -96,4 +98,13 @@ interface VersionedFolderApi extends ModelApi { @Delete(Paths.VERSIONED_FOLDER_READ_BY_AUTHENTICATED) HttpResponse revokeReadByAuthenticated(UUID id) + @Get(value = Paths.VERSIONED_FOLDER_EXPORT) + HttpResponse exportModel(UUID id, @Nullable String namespace, @Nullable String name, @Nullable String version) + + @Produces(MediaType.MULTIPART_FORM_DATA) + @Post(Paths.VERSIONED_FOLDER_IMPORT) + ListResponse importModel(@Body MultipartBody body, String namespace, String name, @Nullable String version) + + // This is the version that will be implemented by the controller + ListResponse importModel(@Body io.micronaut.http.server.multipart.MultipartBody body, String namespace, String name, @Nullable String version) } \ No newline at end of file diff --git a/mauro-domain/src/main/groovy/org/maurodata/FieldConstants.groovy b/mauro-domain/src/main/groovy/org/maurodata/FieldConstants.groovy index d80e11924..79145115a 100644 --- a/mauro-domain/src/main/groovy/org/maurodata/FieldConstants.groovy +++ b/mauro-domain/src/main/groovy/org/maurodata/FieldConstants.groovy @@ -8,9 +8,13 @@ class FieldConstants { static final String CODESETS_LOWERCASE = "{$CODESET_LOWERCASE}s" static final String DATAMODEL_LOWERCASE = 'datamodel' static final String DATAMODELS_LOWERCASE = "${DATAMODEL_LOWERCASE}s" + static final String FOLDER_LOWERCASE = 'folder' + static final String FOLDERS_LOWERCASE = "${FOLDER_LOWERCASE}s" + static final String VERSIONED_FOLDER_LOWERCASE = 'versionedfolder' static final String TERMINOLOGY_LOWERCASE = 'terminology' static final String TERMINOLOGIES_LOWERCASE = 'terminologies' - static final String DEFAULT_MODEL_VERSION = '0.0.0' + static final String FOLDER = 'Folder' + static final String VERSIONED_FOLDER = 'VersionedFolder' } diff --git a/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataClassComponent.groovy b/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataClassComponent.groovy index feb471b65..ad0ae5c94 100644 --- a/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataClassComponent.groovy +++ b/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataClassComponent.groovy @@ -78,11 +78,6 @@ class DataClassComponent extends ModelItem { 'dcc' } - @Transient - @JsonIgnore - List>> getAllAssociations() { - [dataElementComponents] as List>> - } @Override void copyInto(Item into) { diff --git a/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataFlow.groovy b/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataFlow.groovy index bc509f11b..70ed3526e 100644 --- a/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataFlow.groovy +++ b/mauro-domain/src/main/groovy/org/maurodata/domain/dataflow/DataFlow.groovy @@ -94,12 +94,6 @@ class DataFlow extends ModelItem { this.copyInto(dataFlowShallowCopy) return dataFlowShallowCopy } - @Override - @Transient - @JsonIgnore - List>> getAllAssociations() { - [dataClassComponents] as List>> - } /** * Builder methods diff --git a/mauro-domain/src/main/groovy/org/maurodata/domain/datamodel/DataModel.groovy b/mauro-domain/src/main/groovy/org/maurodata/domain/datamodel/DataModel.groovy index 0a5ac22e0..29b9be20a 100644 --- a/mauro-domain/src/main/groovy/org/maurodata/domain/datamodel/DataModel.groovy +++ b/mauro-domain/src/main/groovy/org/maurodata/domain/datamodel/DataModel.groovy @@ -1,12 +1,5 @@ package org.maurodata.domain.datamodel - -import org.maurodata.domain.model.Item -import org.maurodata.domain.model.ItemReference -import org.maurodata.domain.model.ItemReferencer -import org.maurodata.domain.model.ItemReferencerUtils -import org.maurodata.domain.model.ItemUtils - import com.fasterxml.jackson.annotation.JsonAlias import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty @@ -14,10 +7,17 @@ import groovy.transform.AutoClone import groovy.transform.CompileStatic import groovy.transform.MapConstructor import io.micronaut.core.annotation.Introspected +import io.micronaut.core.annotation.Nullable import io.micronaut.data.annotation.MappedEntity import io.micronaut.data.annotation.MappedProperty import io.micronaut.data.annotation.Relation import jakarta.persistence.Transient +import org.maurodata.domain.dataflow.DataFlow +import org.maurodata.domain.model.Item +import org.maurodata.domain.model.ItemReference +import org.maurodata.domain.model.ItemReferencer +import org.maurodata.domain.model.ItemReferencerUtils +import org.maurodata.domain.model.ItemUtils import org.maurodata.domain.model.Model import org.maurodata.domain.model.ModelItem @@ -51,6 +51,14 @@ class DataModel extends Model implements ItemReferencer { @JsonIgnore Set enumerationValues = [] + @Transient + @Nullable + List targetDataFlows = [] + + @Transient + @Nullable + List sourceDataFlows = [] + @Transient String modelType = domainType @@ -95,22 +103,22 @@ class DataModel extends Model implements ItemReferencer { Map clonedDataTypeLookup = [:] Map clonedEnumerationValueLookup = [:] - cloned.dataTypes = dataTypes.collect {it -> - it.clone().tap {clonedDT -> + cloned.dataTypes = dataTypes.collect {it-> + it.clone().tap { clonedDT -> clonedDataTypeLookup.put(it.id, clonedDT) clonedDT.parent = cloned clonedDT.enumerationValues.clear() } } List clonedDataClasses = dataClasses.collect { - it.clone().tap {clonedDC -> + it.clone().tap { clonedDC -> clonedDataClassLookup.put(it.id, clonedDC) clonedDC.dataModel = cloned } } clonedDataClasses.each { - List clonedChildList = it.dataClasses.collect {child -> - child.clone().tap {clonedChild -> + List clonedChildList = it.dataClasses.collect { child -> + child.clone().tap { clonedChild -> clonedChildDataClassLookup.put(child.id, clonedChild) clonedChild.parentDataClass = it clonedChild.dataModel = cloned @@ -124,7 +132,7 @@ class DataModel extends Model implements ItemReferencer { cloned.allDataClasses = clonedChildren as Set cloned.dataElements = dataElements.collect { - it.clone().tap {clonedDataElement -> + it.clone().tap { clonedDataElement -> clonedDataElementLookup.put(it.id, clonedDataElement) Map allDataClassLookup = clonedDataClassLookup allDataClassLookup.putAll(clonedChildDataClassLookup) @@ -134,7 +142,7 @@ class DataModel extends Model implements ItemReferencer { } } cloned.allDataClasses.each {dataClass -> - dataClass.dataElements = dataClass.dataElements.collect {dataElementIt -> + dataClass.dataElements = dataClass.dataElements.collect { dataElementIt -> clonedDataElementLookup[dataElementIt.id] } dataClass.extendsDataClasses = dataClass.extendsDataClasses.collect {extendedDataClass -> @@ -145,7 +153,7 @@ class DataModel extends Model implements ItemReferencer { } } cloned.enumerationValues = enumerationValues.collect { - it.clone().tap {clonedEV -> + it.clone().tap { clonedEV -> clonedEnumerationValueLookup.put(it.id, clonedEV) clonedEV.dataModel = cloned clonedEV.parent = clonedDataTypeLookup[it.parent.id] @@ -171,9 +179,9 @@ class DataModel extends Model implements ItemReferencer { Map dataTypesMap = dataTypes.collectEntries {[it.label, it]} List referenceTypes = dataTypeReferenceTypes() - dataTypes.each {dataType -> + dataTypes.each { dataType -> dataType.parent = this - dataType.enumerationValues.each {enumerationValue -> + dataType.enumerationValues.each { enumerationValue -> enumerationValue.parent = dataType enumerationValues.add(enumerationValue) enumerationValue.dataModel = this @@ -181,14 +189,14 @@ class DataModel extends Model implements ItemReferencer { } } - dataClasses.each {dataClass -> + dataClasses.each { dataClass -> setDataClassAssociations(dataClass, dataTypesMap, referenceTypes) } this } void setDataClassAssociations(DataClass dataClass, Map dataTypesMap, - List referenceTypes) { + List referenceTypes) { allDataClasses.add(dataClass) dataClass.dataModel = this dataClass.dataClasses.each {childDataClass -> @@ -203,10 +211,10 @@ class DataModel extends Model implements ItemReferencer { this.dataElements.add(dataElement) } } - dataClass.referenceTypes = referenceTypes.findAll {it.referenceClass?.id == dataClass.id} as List + dataClass.referenceTypes = referenceTypes.findAll{it.referenceClass?.id == dataClass.id } as List } - protected List dataTypeReferenceTypes() { + protected List dataTypeReferenceTypes() { dataTypes.findAll {it.isReferenceType()} } @@ -216,13 +224,13 @@ class DataModel extends Model implements ItemReferencer { */ static DataModel build( - Map args, - @DelegatesTo(value = DataModel, strategy = Closure.DELEGATE_FIRST) Closure closure = {}) { + Map args, + @DelegatesTo(value = DataModel, strategy = Closure.DELEGATE_FIRST) Closure closure = {}) { new DataModel(args).tap(closure) } static DataModel build( - @DelegatesTo(value = DataModel, strategy = Closure.DELEGATE_FIRST) Closure closure = {}) { + @DelegatesTo(value = DataModel, strategy = Closure.DELEGATE_FIRST) Closure closure = {}) { build [:], closure } diff --git a/mauro-domain/src/main/groovy/org/maurodata/domain/datamodel/ModelType.groovy b/mauro-domain/src/main/groovy/org/maurodata/domain/datamodel/ModelType.groovy new file mode 100644 index 000000000..bf31e43d5 --- /dev/null +++ b/mauro-domain/src/main/groovy/org/maurodata/domain/datamodel/ModelType.groovy @@ -0,0 +1,6 @@ +package org.maurodata.domain.datamodel + +enum ModelType { + SOURCE, + TARGET +} \ No newline at end of file diff --git a/mauro-domain/src/main/groovy/org/maurodata/plugin/importer/ModelImporterPlugin.groovy b/mauro-domain/src/main/groovy/org/maurodata/plugin/importer/ModelImporterPlugin.groovy index 1903c3cdc..de8b67def 100644 --- a/mauro-domain/src/main/groovy/org/maurodata/plugin/importer/ModelImporterPlugin.groovy +++ b/mauro-domain/src/main/groovy/org/maurodata/plugin/importer/ModelImporterPlugin.groovy @@ -47,13 +47,20 @@ trait ModelImporterPlugin extends it.displayLabel = it.createDisplayLabel() } } - importedModel.updateCreationProperties() - log.info '* start updateCreationProperties *' - importedModel.getAllContents().each {it.updateCreationProperties()} - log.info '* finish updateCreationProperties *' + } imported } + List updateCreationProperties(List models) { + models.each{ + it.updateCreationProperties() + log.info('* start updateCreationProperties *') + it.getAllContents().each {it.updateCreationProperties()} + log.info('* finish updateCreationProperties *') + } + models + } + @Override String getProviderType() { diff --git a/mauro-domain/src/main/groovy/org/maurodata/util/PathStringUtils.groovy b/mauro-domain/src/main/groovy/org/maurodata/util/PathStringUtils.groovy index 9f97ee4fe..73e21525a 100644 --- a/mauro-domain/src/main/groovy/org/maurodata/util/PathStringUtils.groovy +++ b/mauro-domain/src/main/groovy/org/maurodata/util/PathStringUtils.groovy @@ -39,6 +39,10 @@ class PathStringUtils { subPathOnly - BRANCH_DELIMITER } + static String getPathFrom(String start, String fullPath){ + start + fullPath.split(start)[1] + } + static String lastSubPath(String subPath) { splitBy(subPath, VERTICAL_BAR_ESCAPE).last() } @@ -47,7 +51,7 @@ class PathStringUtils { path.split(separator) } - static String getVersionFromPath(String fullPath) { + static String getBranchNameFromPath(String fullPath) { String[] parts = fullPath.split(VERTICAL_BAR_ESCAPE) String version parts.each { diff --git a/mauro-domain/src/test/groovy/org/maurodata/test/domain/util/PathStringUtilsTest.groovy b/mauro-domain/src/test/groovy/org/maurodata/test/domain/util/PathStringUtilsTest.groovy index c589a852d..1ed07af0c 100644 --- a/mauro-domain/src/test/groovy/org/maurodata/test/domain/util/PathStringUtilsTest.groovy +++ b/mauro-domain/src/test/groovy/org/maurodata/test/domain/util/PathStringUtilsTest.groovy @@ -8,6 +8,14 @@ import spock.lang.Unroll @MicronautTest class PathStringUtilsTest extends Specification { + void 'test getPathFrom'(){ + when: + String result = PathStringUtils.getPathFrom('dc', + "fo:rem recusandae eaque|dm:rem et eum\$main|dc:sunt eum possimus|de:corporis omnis labore") + then: + result == "dc:sunt eum possimus|de:corporis omnis labore" + } + @Unroll void 'test getItemSubPath, for #pathPrefix, #fullPath'() { when: @@ -30,9 +38,9 @@ class PathStringUtilsTest extends Specification { } @Unroll - void 'test getVersionFromPath for #fullPath'() { + void 'test getBranchNameFromPath for #fullPath'() { when: - String version = PathStringUtils.getVersionFromPath(fullPath) + String version = PathStringUtils.getBranchNameFromPath(fullPath) then: version == expectedVersion diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/AdministeredItemCacheableRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/AdministeredItemCacheableRepository.groovy index a9d21db06..28df90bb7 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/AdministeredItemCacheableRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/AdministeredItemCacheableRepository.groovy @@ -202,6 +202,9 @@ abstract class AdministeredItemCacheableRepository e ((DataClassRepository) repository).deleteExtensionRelationship(sourceDataClass.id, targetDataClass.id) } + List findAllByLabel(String label) { + ((DataClassRepository) repository).findAllByLabel(label) + } @Override Boolean handles(String domainType) { @@ -388,6 +391,11 @@ abstract class AdministeredItemCacheableRepository e invalidate(id) ((DataClassComponentRepository) repository).removeTargetDataClasses(id) } + + List findAllByParent(DataFlow dataFlow) { + ((DataClassComponentRepository) repository).findAllByDataFlow(dataFlow) + } + Boolean handles(String domainType) { domainClass.simpleName.equalsIgnoreCase(domainType) || (domainClass.simpleName + 's').equalsIgnoreCase(domainType) } @@ -435,6 +443,10 @@ abstract class AdministeredItemCacheableRepository e ((DataElementComponentRepository) repository).getTargetDataElements(id) } + List findAllByParent(DataClassComponent dataClassComponent ) { + ((DataElementComponentRepository) repository).findAllByDataClassComponent(dataClassComponent) + } + @Override Boolean handles(String domainType) { domainClass.simpleName.equalsIgnoreCase(domainType) || (domainClass.simpleName + 's').equalsIgnoreCase(domainType) diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/ModelCacheableRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/ModelCacheableRepository.groovy index dc6044902..de268eef0 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/ModelCacheableRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/cache/ModelCacheableRepository.groovy @@ -1,7 +1,5 @@ package org.maurodata.persistence.cache -import org.maurodata.domain.model.version.ModelVersion - import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import io.micronaut.cache.annotation.CacheConfig @@ -11,6 +9,7 @@ import org.maurodata.domain.classifier.ClassificationScheme import org.maurodata.domain.datamodel.DataModel import org.maurodata.domain.folder.Folder import org.maurodata.domain.model.Model +import org.maurodata.domain.model.version.ModelVersion import org.maurodata.domain.terminology.CodeSet import org.maurodata.domain.terminology.Terminology import org.maurodata.persistence.classifier.ClassificationSchemeRepository @@ -118,6 +117,9 @@ class ModelCacheableRepository extends AdministeredItemCacheabl super(dataModelRepository) } + List findAllByLabelAndBranchName(String label, String branchName) { + ((DataModelRepository) repository).findAllByLabelAndBranchName(label, branchName) + } @Override Boolean handles(String domainType) { return domainType != null && domainType.toLowerCase() in ['datamodel', 'datamodels'] diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataClassComponentContentRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataClassComponentContentRepository.groovy index 9a7b41bf9..a284f97b8 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataClassComponentContentRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataClassComponentContentRepository.groovy @@ -1,34 +1,38 @@ package org.maurodata.persistence.dataflow import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j import io.micronaut.core.annotation.NonNull import jakarta.inject.Inject import jakarta.inject.Singleton import org.maurodata.domain.dataflow.DataClassComponent +import org.maurodata.domain.dataflow.DataElementComponent import org.maurodata.domain.model.AdministeredItem import org.maurodata.persistence.cache.AdministeredItemCacheableRepository import org.maurodata.persistence.model.AdministeredItemContentRepository @CompileStatic +@Slf4j @Singleton class DataClassComponentContentRepository extends AdministeredItemContentRepository { @Inject - AdministeredItemCacheableRepository.DataClassComponentCacheableRepository dataClassComponentRepository + AdministeredItemCacheableRepository.DataClassComponentCacheableRepository dataClassComponentCacheableRepository @Inject DataElementComponentContentRepository dataElementComponentContentRepository @Inject - DataElementComponentRepository dataElementComponentRepository + AdministeredItemCacheableRepository.DataElementComponentCacheableRepository dataElementComponentCacheableRepository + @Override DataClassComponent readWithContentById(UUID id) { - DataClassComponent dataClassComponent = dataClassComponentRepository.findById(id) + DataClassComponent dataClassComponent = dataClassComponentCacheableRepository.findById(id) if (!dataClassComponent) return null - dataClassComponent.sourceDataClasses = dataClassComponentRepository.findAllSourceDataClasses(dataClassComponent.id) - dataClassComponent.targetDataClasses = dataClassComponentRepository.findAllTargetDataClasses(dataClassComponent.id) - dataClassComponent.dataElementComponents = dataElementComponentRepository.findAllByParent(dataClassComponent) + dataClassComponent.sourceDataClasses = dataClassComponentCacheableRepository.findAllSourceDataClasses(dataClassComponent.id) + dataClassComponent.targetDataClasses = dataClassComponentCacheableRepository.findAllTargetDataClasses(dataClassComponent.id) + dataClassComponent.dataElementComponents = dataElementComponentCacheableRepository.findAllByParent(dataClassComponent) dataClassComponent } @@ -36,31 +40,25 @@ class DataClassComponentContentRepository extends AdministeredItemContentReposit Long deleteWithContent(@NonNull AdministeredItem administeredItem) { if ((administeredItem as DataClassComponent).dataElementComponents) { dataElementComponentContentRepository.deleteAllSourceAndTargetDataElements((administeredItem as DataClassComponent).dataElementComponents) - dataElementComponentRepository.deleteAll( (administeredItem as DataClassComponent).dataElementComponents) + dataElementComponentCacheableRepository.deleteAll( (administeredItem as DataClassComponent).dataElementComponents) } if ((administeredItem as DataClassComponent).sourceDataClasses) { - dataClassComponentRepository.removeSourceDataClasses(administeredItem.id) + dataClassComponentCacheableRepository.removeSourceDataClasses(administeredItem.id) } if ((administeredItem as DataClassComponent).targetDataClasses) { - dataClassComponentRepository.removeTargetDataClasses(administeredItem.id) + dataClassComponentCacheableRepository.removeTargetDataClasses(administeredItem.id) } - dataClassComponentRepository.delete(administeredItem as DataClassComponent) + dataClassComponentCacheableRepository.delete(administeredItem as DataClassComponent) } @Override AdministeredItem saveWithContent(@NonNull AdministeredItem administeredItem) { - DataClassComponent saved = dataClassComponentRepository.save(administeredItem as DataClassComponent) - saved.sourceDataClasses.each{ - dataClassComponentRepository.addSourceDataClass(saved.id, it.id) - } - saved.targetDataClasses.each{ - dataClassComponentRepository.addTargetDataClass(saved.id, it.id) - } + DataClassComponent saved = (DataClassComponent) super.saveWithContent(administeredItem) saved.dataElementComponents.each { it.updateCreationProperties() it.dataClassComponent = saved it.parent = saved - dataElementComponentContentRepository.saveWithContent(it) + (DataElementComponent) super.saveWithContent(it) } saveAllFacets(saved) saved diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataElementComponentContentRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataElementComponentContentRepository.groovy index 5fdfb83c4..9852f3f77 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataElementComponentContentRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataElementComponentContentRepository.groovy @@ -1,6 +1,7 @@ package org.maurodata.persistence.dataflow import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j import io.micronaut.core.annotation.NonNull import jakarta.inject.Inject import jakarta.inject.Singleton @@ -10,50 +11,45 @@ import org.maurodata.persistence.cache.AdministeredItemCacheableRepository import org.maurodata.persistence.model.AdministeredItemContentRepository @CompileStatic +@Slf4j @Singleton class DataElementComponentContentRepository extends AdministeredItemContentRepository { @Inject - AdministeredItemCacheableRepository.DataElementComponentCacheableRepository dataElementComponentRepository + AdministeredItemCacheableRepository.DataElementComponentCacheableRepository dataElementComponentCacheableRepository @Override DataElementComponent readWithContentById(UUID id) { - DataElementComponent dataElementComponent = dataElementComponentRepository.readById(id) + DataElementComponent dataElementComponent = dataElementComponentCacheableRepository.readById(id) if (!dataElementComponent) return null - dataElementComponent.sourceDataElements = dataElementComponentRepository.getSourceDataElements(dataElementComponent.id) - dataElementComponent.targetDataElements = dataElementComponentRepository.getTargetDataElements(dataElementComponent.id) + dataElementComponent.sourceDataElements = dataElementComponentCacheableRepository.getSourceDataElements(dataElementComponent.id) + dataElementComponent.targetDataElements = dataElementComponentCacheableRepository.getTargetDataElements(dataElementComponent.id) dataElementComponent } @Override AdministeredItem saveWithContent(@NonNull AdministeredItem administeredItem) { - DataElementComponent saved = dataElementComponentRepository.save(administeredItem as DataElementComponent) - saved.sourceDataElements.each { - dataElementComponentRepository.addSourceDataElement(saved.id, it.id) - } - saved.targetDataElements.each { - dataElementComponentRepository.addTargetDataElement(saved.id, it.id) - } + DataElementComponent saved = dataElementComponentCacheableRepository.save(administeredItem as DataElementComponent) saveAllFacets(saved) saved } - // TODO methods here won't invalidate the cache + @Override Long deleteWithContent(@NonNull AdministeredItem administeredItem) { if ((administeredItem as DataElementComponent).sourceDataElements) { - dataElementComponentRepository.removeSourceDataElements(administeredItem.id) + dataElementComponentCacheableRepository.removeSourceDataElements(administeredItem.id) } if ((administeredItem as DataElementComponent).targetDataElements) { - dataElementComponentRepository.removeTargetDataElements(administeredItem.id) + dataElementComponentCacheableRepository.removeTargetDataElements(administeredItem.id) } - dataElementComponentRepository.delete(administeredItem as DataElementComponent) + dataElementComponentCacheableRepository.delete(administeredItem as DataElementComponent) } void deleteAllSourceAndTargetDataElements(List dataElementComponents) { dataElementComponents.each{ - dataElementComponentRepository.removeSourceDataElements(it.id) - dataElementComponentRepository.removeTargetDataElements(it.id) + dataElementComponentCacheableRepository.removeSourceDataElements(it.id) + dataElementComponentCacheableRepository.removeTargetDataElements(it.id) } } @@ -61,4 +57,4 @@ class DataElementComponentContentRepository extends AdministeredItemContentRepos Boolean handles(Class clazz) { return clazz.simpleName == 'DataElementComponent' } -} \ No newline at end of file +} diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataFlowContentRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataFlowContentRepository.groovy index 030d5f418..c62e4b207 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataFlowContentRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/dataflow/DataFlowContentRepository.groovy @@ -5,9 +5,9 @@ import io.micronaut.core.annotation.NonNull import jakarta.inject.Inject import jakarta.inject.Singleton import org.maurodata.domain.dataflow.DataClassComponent -import org.maurodata.domain.dataflow.DataElementComponent import org.maurodata.domain.dataflow.DataFlow import org.maurodata.domain.model.AdministeredItem +import org.maurodata.persistence.cache.AdministeredItemCacheableRepository import org.maurodata.persistence.model.AdministeredItemContentRepository @CompileStatic @@ -15,22 +15,23 @@ import org.maurodata.persistence.model.AdministeredItemContentRepository class DataFlowContentRepository extends AdministeredItemContentRepository { @Inject - DataFlowRepository dataFlowRepository + AdministeredItemCacheableRepository.DataFlowCacheableRepository dataFlowCacheableRepository @Inject DataClassComponentContentRepository dataClassComponentContentRepository + @Inject - DataClassComponentRepository dataClassComponentRepository + AdministeredItemCacheableRepository.DataElementComponentCacheableRepository dataElementComponentCacheableRepository @Inject - DataElementComponentRepository dataElementComponentRepository + AdministeredItemCacheableRepository.DataClassComponentCacheableRepository dataClassComponentCacheableRepository @Override DataFlow readWithContentById(UUID id) { - DataFlow dataFlow = dataFlowRepository.findById(id) - List dataClassComponents = dataClassComponentRepository.findAllByParent(dataFlow) + DataFlow dataFlow = dataFlowCacheableRepository.findById(id) + List dataClassComponents = dataClassComponentCacheableRepository.findAllByParent(dataFlow) dataClassComponents.each { - it.dataElementComponents = dataElementComponentRepository.findAllByParent(it) + it.dataElementComponents = dataElementComponentCacheableRepository.findAllByParent(it) } dataFlow.dataClassComponents = dataClassComponents dataFlow @@ -38,8 +39,7 @@ class DataFlowContentRepository extends AdministeredItemContentRepository { @Override DataFlow saveWithContent(AdministeredItem administeredItem) { - DataFlow saved = dataFlowRepository.save(administeredItem as DataFlow) - saveAllFacets(saved) + DataFlow saved = (DataFlow) super.saveWithContent(administeredItem) saved.dataClassComponents.each { it.updateCreationProperties() it.dataFlow = saved diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelContentRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelContentRepository.groovy index d96c82f27..9e1fdab8a 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelContentRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelContentRepository.groovy @@ -1,16 +1,20 @@ package org.maurodata.persistence.datamodel import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j import io.micronaut.core.annotation.NonNull import jakarta.inject.Inject import jakarta.inject.Singleton import org.maurodata.domain.datamodel.DataClass import org.maurodata.domain.datamodel.DataModel import org.maurodata.domain.datamodel.DataType +import org.maurodata.persistence.cache.AdministeredItemCacheableRepository +import org.maurodata.persistence.dataflow.DataFlowContentRepository import org.maurodata.persistence.model.ModelContentRepository @CompileStatic @Singleton +@Slf4j class DataModelContentRepository extends ModelContentRepository { @Inject @@ -28,6 +32,12 @@ class DataModelContentRepository extends ModelContentRepository { @Inject EnumerationValueRepository enumerationValueRepository + @Inject + AdministeredItemCacheableRepository.DataFlowCacheableRepository dataFlowCacheableRepository + + @Inject + DataFlowContentRepository dataFlowContentRepository + @Override DataModel findWithContentById(UUID id) { DataModel dataModel = dataModelRepository.findById(id) @@ -60,6 +70,13 @@ class DataModelContentRepository extends ModelContentRepository { dataElement.dataType = dataTypeMap[dataElement.dataType.id] } + //dataFlows + dataModel.sourceDataFlows = dataFlowCacheableRepository.findAllBySource(dataModel).collect { + dataFlowContentRepository.readWithContentById(it.id) + } + dataModel.targetDataFlows = dataFlowCacheableRepository.findAllByTarget(dataModel).collect { + dataFlowContentRepository.readWithContentById(it.id) + } dataModel } @@ -80,6 +97,11 @@ class DataModelContentRepository extends ModelContentRepository { } } saved.dataTypes = dataTypeRepository.updateAll(saved.dataTypes.findAll {it.isReferenceType()}) + saved.targetDataFlows.each {dataFlow -> + dataFlow.target = saved + dataFlow.updateCreationProperties() + dataFlowContentRepository.saveWithContent(dataFlow) + } saved } diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelRepository.groovy index 618dd8fef..ae77d682e 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/DataModelRepository.groovy @@ -34,7 +34,14 @@ abstract class DataModelRepository implements ModelRepository { List findAllByLabel(String pathIdentifier) { dataModelDTORepository.findAllByLabel(pathIdentifier) } - @Override + + + @Nullable + List findAllByLabelAndBranchName(String label, String branchName) { + dataModelDTORepository.findAllByLabelAndBranchName(label, branchName) + } + + Class getDomainClass() { DataModel } diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/dto/DataModelDTORepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/dto/DataModelDTORepository.groovy index 3ba4e1cf1..91381bfcd 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/dto/DataModelDTORepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/datamodel/dto/DataModelDTORepository.groovy @@ -19,7 +19,6 @@ abstract class DataModelDTORepository implements GenericRepository findAllByLabel(String label) + + @Nullable + @Query('SELECT * FROM datamodel.data_model WHERE label = :label AND branch_name = :branchName') + abstract List findAllByLabelAndBranchName(String label, String branchName) + } diff --git a/mauro-persistence/src/main/groovy/org/maurodata/persistence/folder/FolderContentRepository.groovy b/mauro-persistence/src/main/groovy/org/maurodata/persistence/folder/FolderContentRepository.groovy index f4ed97332..35b1857fa 100644 --- a/mauro-persistence/src/main/groovy/org/maurodata/persistence/folder/FolderContentRepository.groovy +++ b/mauro-persistence/src/main/groovy/org/maurodata/persistence/folder/FolderContentRepository.groovy @@ -52,31 +52,34 @@ class FolderContentRepository extends ModelContentRepository { Folder saved = folderRepository.save(folder) super.saveAllFacets(saved) - if (saved.childFolders) { - saved.childFolders.each {childFolder -> - saveWithContent(childFolder) - } + saved.childFolders.each {childFolder -> + childFolder.parent = saved + childFolder.updateCreationProperties() + childFolder.catalogueUser = folder.catalogueUser + saveWithContent(childFolder) } - if (saved.classificationSchemes) { - saved.classificationSchemes.each { - getModelContentRepository(it.class).saveWithContent(it) - } + + saved.classificationSchemes.each { + it.folder = saved + it.updateCreationProperties() + getModelContentRepository(it.class).saveWithContent(it) } - if (saved.terminologies) { - saved.terminologies.each { - getModelContentRepository(it.class).saveWithContent(it) - } + saved.terminologies.each { + it.folder = saved + it.updateCreationProperties() + getModelContentRepository(it.class).saveWithContent(it) } - if (saved.codeSets) { - saved.codeSets.each { - getModelContentRepository(it.class).saveWithContent(it) - } + + saved.codeSets.each { + it.folder = saved + it.updateCreationProperties() + getModelContentRepository(it.class).saveWithContent(it) } - if (saved.dataModels) { - saved.dataModels.each { - getModelContentRepository(it.class).saveWithContent(it) - } + saved.dataModels.each { + it.folder = saved + it.updateCreationProperties() + getModelContentRepository(it.class).saveWithContent(it) } saved }